Commit e8a305af authored by DatHV's avatar DatHV
Browse files

update auth logic

parent 5d865668
......@@ -4,14 +4,12 @@ import 'package:flutter_svg/svg.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/context_extensions.dart';
import 'package:mypoint_flutter_app/widgets/alert/src/alert.dart';
import '../configs/callbacks.dart';
import '../resouce/base_color.dart';
import '../resouce/define_image.dart';
import '../resouce/text_style.dart';
import '../widgets/alert/custom_alert_dialog.dart';
import '../widgets/alert/data_alert_model.dart';
import '../widgets/alert/src/dialog_button.dart';
abstract class BaseScreen extends StatefulWidget {
const BaseScreen({super.key});
......
......@@ -13,4 +13,8 @@ class APIPaths {
static const String websiteFolderGetPageList = "/websiteFolderGetPageList/1.0.0";
static const String otpVerifyForDoingNextEvent = "/otpVerifyForDoingNextEvent/1.0.0";
static const String accountPasswordReset = "/accountPasswordReset/1.0.0";
static const String login = "/iam/v1/authentication/account-login";
static const String loginWithBiometric = "/iam/v1/authentication/bio-login";
static const String getUserInfo = "/user/api/v2.0/mypoint/me";
static const String bioCredential = "/iam/v1/account/me/bio-credential";
}
class Constants {
static get commonError => "Có lỗi xảy ra. Vui lòng thử lại!";
static get commonError => "Hệ thống không thể xử lý yêu cầu hiện tại. Vui lòng thử lại sau hoặc liên hệ hotline 1900599863 để được trợ giúp.";
static var otpTtl = 180;
// device key
}
class ErrorCodes {
static var deviceLock = "ERR_DEVICE_LOCK";
static const String deviceLock = "ERR_DEVICE_LOCK";
static const String deviceUndefined = "ERR_DEVICE_UNDEFINED";
static const String requiredChangePass = "ERR_ACCOUNT_LOGIN_PASSWORD_CHANGE_WEAK";
static const String invalidAccount = "ERR_INVALID_ACCOUNT";
static const String bioTokenInvalid = "ERR_INVALID_BIO_TOKEN";
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/screen/login/login_screen.dart';
import 'package:mypoint_flutter_app/screen/main_tab_screen/main_tab_screen.dart';
import 'package:mypoint_flutter_app/screen/onboarding/onboarding_screen.dart';
import 'package:mypoint_flutter_app/screen/onboarding/onboarding_viewmodel.dart';
import 'package:mypoint_flutter_app/screen/splash/splash_screen.dart';
void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await DataPreference.instance.loadLoginToken();
Get.put(OnboardingViewModel());
runApp(const MyApp());
}
......@@ -15,11 +21,17 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/login',
theme: ThemeData(
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.deepPurple),
primaryColor: Colors.deepPurple,
),
home: SplashScreen(),
getPages: [
GetPage(name: '/login', page: () => const LoginScreen(phoneNumber: '091212121',)),
GetPage(name: '/main', page: () => const MainTabScreen()),
GetPage(name: '/onboarding', page: () => const OnboardingScreen()),
],
);
}
}
\ No newline at end of file
import 'package:json_annotation/json_annotation.dart';
part 'biometric_register_response_model.g.dart';
@JsonSerializable()
class BiometricRegisterResponseModel {
final String? bioToken;
BiometricRegisterResponseModel({this.bioToken});
factory BiometricRegisterResponseModel.fromJson(Map<String, dynamic> json) => _$BiometricRegisterResponseModelFromJson(json);
Map<String, dynamic> toJson() => _$BiometricRegisterResponseModelToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'biometric_register_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BiometricRegisterResponseModel _$BiometricRegisterResponseModelFromJson(
Map<String, dynamic> json,
) => BiometricRegisterResponseModel(bioToken: json['bioToken'] as String?);
Map<String, dynamic> _$BiometricRegisterResponseModelToJson(
BiometricRegisterResponseModel instance,
) => <String, dynamic>{'bioToken': instance.bioToken};
import 'package:json_annotation/json_annotation.dart';
part 'customer_balance_model.g.dart';
@JsonSerializable()
class CustomerBalanceModel {
@JsonKey(name: 'amount_active')
final String? amountActive;
CustomerBalanceModel({this.amountActive});
factory CustomerBalanceModel.fromJson(Map<String, dynamic> json) => _$CustomerBalanceModelFromJson(json);
Map<String, dynamic> toJson() => _$CustomerBalanceModelToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'customer_balance_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CustomerBalanceModel _$CustomerBalanceModelFromJson(
Map<String, dynamic> json,
) => CustomerBalanceModel(amountActive: json['amount_active'] as String?);
Map<String, dynamic> _$CustomerBalanceModelToJson(
CustomerBalanceModel instance,
) => <String, dynamic>{'amount_active': instance.amountActive};
// profile_response_model.dart
import 'package:json_annotation/json_annotation.dart';
import 'working_site_model.dart';
import 'worker_site_model.dart';
import 'user_agreement_model.dart';
part 'profile_response_model.g.dart';
@JsonSerializable()
class ProfileResponseModel {
@JsonKey(name: 'worker_site')
final WorkerSiteModel? workerSite;
@JsonKey(name: 'working_site')
final WorkingSiteModel? workingSite;
@JsonKey(name: 'user_agreements')
final UserAgreementModel? userAgreements;
@JsonKey(name: 'force_reset_password')
String? forceResetPassword;
@JsonKey(name: 'remaining_login_fail')
final String? remainingLoginFail;
@JsonKey(name: 'unlock_after_time')
final String? unlockAfter;
ProfileResponseModel({
this.workerSite,
this.workingSite,
this.userAgreements,
this.forceResetPassword,
this.remainingLoginFail,
this.unlockAfter,
});
factory ProfileResponseModel.fromJson(Map<String, dynamic> json) => _$ProfileResponseModelFromJson(json);
Map<String, dynamic> toJson() => _$ProfileResponseModelToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'profile_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ProfileResponseModel _$ProfileResponseModelFromJson(
Map<String, dynamic> json,
) => ProfileResponseModel(
workerSite:
json['worker_site'] == null
? null
: WorkerSiteModel.fromJson(
json['worker_site'] as Map<String, dynamic>,
),
workingSite:
json['working_site'] == null
? null
: WorkingSiteModel.fromJson(
json['working_site'] as Map<String, dynamic>,
),
userAgreements:
json['user_agreements'] == null
? null
: UserAgreementModel.fromJson(
json['user_agreements'] as Map<String, dynamic>,
),
forceResetPassword: json['force_reset_password'] as String?,
remainingLoginFail: json['remaining_login_fail'] as String?,
unlockAfter: json['unlock_after_time'] as String?,
);
Map<String, dynamic> _$ProfileResponseModelToJson(
ProfileResponseModel instance,
) => <String, dynamic>{
'worker_site': instance.workerSite,
'working_site': instance.workingSite,
'user_agreements': instance.userAgreements,
'force_reset_password': instance.forceResetPassword,
'remaining_login_fail': instance.remainingLoginFail,
'unlock_after_time': instance.unlockAfter,
};
import 'package:json_annotation/json_annotation.dart';
part 'user_agreement_model.g.dart';
@JsonSerializable()
class UserAgreementModel {
@JsonKey(name: 'working_site_id')
final int? workingSiteId;
final String? username;
@JsonKey(name: 'agree_new_decree')
final bool? agreeNewDecree;
@JsonKey(name: 'hide_delete_account')
final bool? hideDeleteAccount;
UserAgreementModel({
this.workingSiteId,
this.username,
this.agreeNewDecree,
this.hideDeleteAccount,
});
factory UserAgreementModel.fromJson(Map<String, dynamic> json) => _$UserAgreementModelFromJson(json);
Map<String, dynamic> toJson() => _$UserAgreementModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_agreement_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
UserAgreementModel _$UserAgreementModelFromJson(Map<String, dynamic> json) =>
UserAgreementModel(
workingSiteId: (json['working_site_id'] as num?)?.toInt(),
username: json['username'] as String?,
agreeNewDecree: json['agree_new_decree'] as bool?,
hideDeleteAccount: json['hide_delete_account'] as bool?,
);
Map<String, dynamic> _$UserAgreementModelToJson(UserAgreementModel instance) =>
<String, dynamic>{
'working_site_id': instance.workingSiteId,
'username': instance.username,
'agree_new_decree': instance.agreeNewDecree,
'hide_delete_account': instance.hideDeleteAccount,
};
import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/model/auth/customer_balance_model.dart';
import 'customer_balance_model.dart';
part 'working_site_model.g.dart';
@JsonSerializable()
class WorkingSiteModel {
final String? id;
final String? name;
final String? avatar;
@JsonKey(name: 'customer_balance')
final CustomerBalanceModel? customerBalanceModel;
@JsonKey(name: 'primary_membership')
// final PrimaryMembership? primaryMembership;
WorkingSiteModel({
this.id,
this.name,
this.avatar,
this.customerBalanceModel,
// this.primaryMembership,
});
factory WorkingSiteModel.fromJson(Map<String, dynamic> json) => _$WorkingSiteModelFromJson(json);
Map<String, dynamic> toJson() => _$WorkingSiteModelToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'working_site_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
WorkingSiteModel _$WorkingSiteModelFromJson(Map<String, dynamic> json) =>
WorkingSiteModel(
id: json['id'] as String?,
name: json['name'] as String?,
avatar: json['avatar'] as String?,
customerBalanceModel:
json['customer_balance'] == null
? null
: CustomerBalanceModel.fromJson(
json['customer_balance'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$WorkingSiteModelToJson(WorkingSiteModel instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'avatar': instance.avatar,
'customer_balance': instance.customerBalanceModel,
};
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/base/base_response_model.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import '../configs/callbacks.dart';
import '../configs/constants.dart';
import 'model_maker.dart';
......@@ -32,6 +33,10 @@ class RestfulAPIClient {
queryParameters: query,
data: body,
);
String? token = DataPreference.instance.token;
if (token != null) {
option.headers["Authorization"] = "Bearer $token";
}
try {
final result = await _dio.fetch<Map<String, dynamic>>(option);
final json = result.data;
......
......@@ -4,7 +4,11 @@ import 'package:mypoint_flutter_app/base/base_response_model.dart';
import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import '../configs/device_info.dart';
import '../model/auth/biometric_register_response_model.dart';
import '../model/auth/login_token_response_model.dart';
import '../model/auth/profile_response_model.dart';
import '../model/update_response_object.dart';
import '../screen/faqs/faqs_model.dart';
import '../screen/onboarding/model/check_phone_response_model.dart';
......@@ -19,7 +23,7 @@ import 'model_maker.dart';
extension RestfullAPIClientAllApi on RestfulAPIClient {
Future<BaseResponseModel<UpdateResponseObject>> checkUpdateApp() async {
String version = Platform.version;
final body = {"operating_system": "iOS", "software_model": "MyPoint", "version": version, "build_number": "1"};
final body = {"operating_system": "iOS", "software_model": "MyPoint", "version": "1.12.1", "build_number": "1"};
return requestNormal(
APIPaths.checkUpdate,
Method.POST,
......@@ -75,6 +79,31 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
return requestNormal(APIPaths.signup, Method.POST, body, (data) => EmptyCodable.fromJson(data as Json));
}
Future<BaseResponseModel<LoginTokenResponseModel>> login(String phone, String password) async {
var deviceKey = await DeviceInfo.getDeviceId();
final body = {
"username": phone,
"password": password.toSha256(),
"device_key": deviceKey,
"workspace_code": "8854"};
return requestNormal(APIPaths.login, Method.POST, body, (data) => LoginTokenResponseModel.fromJson(data as Json));
}
Future<BaseResponseModel<LoginTokenResponseModel>> loginWithBiometric(String phone) async {
var deviceKey = await DeviceInfo.getDeviceId();
final body = {
"username": phone,
"bioToken": DataPreference.instance.getBioToken(phone) ?? "",
"deviceKey": deviceKey,
"workspaceCode": "8854"};
return requestNormal(APIPaths.login, Method.POST, body, (data) => LoginTokenResponseModel.fromJson(data as Json));
}
Future<BaseResponseModel<ProfileResponseModel>> getUserProfile() async {
var deviceKey = await DeviceInfo.getDeviceId();
return requestNormal(APIPaths.getUserInfo, Method.GET, {}, (data) => ProfileResponseModel.fromJson(data as Json));
}
Future<BaseResponseModel<CreateOTPResponseModel>> otpCreateNew(String ownerId) async {
var deviceKey = await DeviceInfo.getDeviceId();
final body = {"owner_id": ownerId, "ttl": Constants.otpTtl, "resend_after_second": Constants.otpTtl};
......@@ -145,4 +174,15 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
(data) => EmptyCodable.fromJson(data as Json),
);
}
Future<BaseResponseModel<BiometricRegisterResponseModel>> accountBioCredential() async {
var deviceKey = await DeviceInfo.getDeviceId();
final body = {"deviceKey": deviceKey};
return requestNormal(
APIPaths.bioCredential,
Method.POST,
body,
(data) => BiometricRegisterResponseModel.fromJson(data as Json),
);
}
}
......@@ -2,6 +2,10 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../resouce/base_color.dart';
import '../widgets/alert/custom_alert_dialog.dart';
import '../widgets/alert/data_alert_model.dart';
enum BiometricTypeEnum {
none,
fingerprint,
......@@ -60,24 +64,31 @@ class BiometricManager {
BuildContext context, {
String title = "Sử dụng sinh trắc học",
String content = "Bạn có muốn đăng nhập bằng vân tay/Face ID không?",
String confirmText = "Đồng ý",
String cancelText = "Huỷ",
}) async {
final result = await Get.dialog<bool>(
AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
CustomAlertDialog(
alertData: DataAlertModel(
background: "assets/images/bg_alert_header.png",
title: title,
content: content,
buttons: [
AlertButton(
text: "Huỷ",
onPressed: () => Get.back(result: false),
child: Text(cancelText),
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
TextButton(
AlertButton(
text: "Đồng ý",
onPressed: () => Get.back(result: true),
child: Text(confirmText),
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
],
),
),
);
if (result == true) {
// Chỉ khi user chọn Đồng ý thì mới gọi authenticateBiometric
......
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import '../model/auth/login_token_response_model.dart';
import '../model/auth/profile_response_model.dart';
class DataPreference {
static final DataPreference _instance = DataPreference._internal();
static DataPreference get instance => _instance;
DataPreference._internal();
String? get token => "";
LoginTokenResponseModel? _loginToken;
ProfileResponseModel? _profile;
ProfileResponseModel? get profile => _profile;
LoginTokenResponseModel? get loginToken => _loginToken;
String? get token => _loginToken?.accessToken;
String? get phone => _profile?.workerSite?.phoneNumber;
bool get logged => (token ?? "").isNotEmpty;
Future<void> saveUserProfile(ProfileResponseModel profile) async {
final prefs = await SharedPreferences.getInstance();
profile = profile;
final jsonString = jsonEncode(profile.toJson());
await prefs.setString('user_profile', jsonString);
}
Future<void> loadProfile() async {
final prefs = await SharedPreferences.getInstance();
final jsonString = prefs.getString('user_profile');
if (jsonString != null) {
final jsonMap = jsonDecode(jsonString);
_profile = ProfileResponseModel.fromJson(jsonMap);
}
}
Future<void> saveLoginToken(LoginTokenResponseModel token) async {
final prefs = await SharedPreferences.getInstance();
_loginToken = token;
final jsonString = jsonEncode(token.toJson());
await prefs.setString('login_token', jsonString);
}
Future<void> loadLoginToken() async {
final prefs = await SharedPreferences.getInstance();
final jsonString = prefs.getString('login_token');
if (jsonString != null) {
final jsonMap = jsonDecode(jsonString);
_loginToken = LoginTokenResponseModel.fromJson(jsonMap);
}
}
Future<void> clearLoginToken() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('login_token');
_loginToken = null;
}
Future<void> clearBioToken(String phone) async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('biometric_login_token_$phone');
}
Future<void> saveBioToken(String bioToken) async {
if (phone == null) return;
final prefs = await SharedPreferences.getInstance();
final jsonString = jsonEncode(bioToken);
await prefs.setString('biometric_login_token_$phone', jsonString);
}
Future<String?> getBioToken(String phone) async {
final prefs = await SharedPreferences.getInstance();
final jsonString = prefs.getString('biometric_login_token_$phone');
if (jsonString != null) {
return jsonDecode(jsonString);
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../main_tab_screen/main_tab_screen.dart';
import 'biometric_viewmodel.dart';
class BiometricAuthScreen extends StatefulWidget {
......@@ -27,33 +28,42 @@ class _BiometricAuthScreenState extends State<BiometricAuthScreen> {
foregroundColor: Colors.black,
elevation: 0,
),
body: Obx(() {
if (!controller.isAvailable.value) {
return const Center(child: Text("Thiết bị không hỗ trợ sinh trắc học."));
}
String title = controller.biometricType.value == BiometricType.face ? "Kích hoạt xác thực Face ID" : "Kích hoạt xác thực vân tay";
String description = controller.biometricType.value == BiometricType.face
? "Kích hoạt xác thực Face ID để đăng nhập nhanh không cần mật khẩu.\nBạn có muốn thực hiện không?"
: "Kích hoạt xác thực vân tay để đăng nhập nhanh không cần mật khẩu.\nBạn có muốn thực hiện không?";
IconData icon = controller.biometricType.value == BiometricType.face ? Icons.face : Icons.fingerprint;
return Center(
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 80, color: Colors.black54),
const Spacer(),
Obx(() {
final icon = controller.biometricType.value == BiometricType.face
? Icons.face
: Icons.fingerprint;
return Icon(icon, size: 80, color: Colors.black54);
}),
const SizedBox(height: 20),
Text(title, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Obx(() {
final title = controller.biometricType.value == BiometricType.face
? "Kích hoạt xác thực Face ID"
: "Kích hoạt xác thực vân tay";
return Text(title,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold));
}),
const SizedBox(height: 10),
Text(description, textAlign: TextAlign.center, style: const TextStyle(fontSize: 16, color: Colors.black54)),
const SizedBox(height: 80),
/// Nút kích hoạt
Obx(() => SizedBox(
Obx(() {
final description = controller.biometricType.value == BiometricType.face
? "Kích hoạt xác thực Face ID để đăng nhập nhanh không cần mật khẩu.\nBạn có muốn thực hiện không?"
: "Kích hoạt xác thực vân tay để đăng nhập nhanh không cần mật khẩu.\nBạn có muốn thực hiện không?";
return Text(description,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16, color: Colors.black54));
}),
// const SizedBox(height: 80),
const Spacer(),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: controller.isAuthenticating.value ? null : controller.authenticate,
onPressed: () => controller.registerBiometric(),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15),
backgroundColor: Colors.redAccent,
......@@ -61,23 +71,20 @@ class _BiometricAuthScreenState extends State<BiometricAuthScreen> {
borderRadius: BorderRadius.circular(10),
),
),
child: controller.isAuthenticating.value
? const CircularProgressIndicator(color: Colors.white)
: const Text("Kích hoạt", style: TextStyle(color: Colors.white, fontSize: 18)),
child: const Text("Kích hoạt",
style: TextStyle(color: Colors.white, fontSize: 18)),
),
),
)),
const SizedBox(height: 10),
/// Nút để sau
TextButton(
onPressed: () => Get.back(),
onPressed: () => Get.to(MainTabScreen()),
child: const Text("Để sau", style: TextStyle(fontSize: 16, color: Colors.black54)),
),
const SizedBox(height: 80),
],
),
),
);
}),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import '../../preference/data_preference.dart';
class BiometricViewModel extends GetxController {
class BiometricViewModel extends RestfulApiViewModel {
final LocalAuthentication _localAuth = LocalAuthentication();
var biometricType = Rxn<BiometricType>(); // Loại sinh trắc học (Face ID / Touch ID)
var isAvailable = false.obs; // Kiểm tra thiết bị có hỗ trợ sinh trắc học không
var isAuthenticating = false.obs; // Trạng thái xác thực
void Function(String message)? onShowAlertError;
void Function(bool result)? registerBiometricResponse;
@override
void onInit() {
......@@ -15,13 +17,10 @@ class BiometricViewModel extends GetxController {
checkBiometricType();
}
/// Kiểm tra loại sinh trắc học có thể sử dụng
Future<void> checkBiometricType() async {
try {
bool canCheckBiometrics = await _localAuth.canCheckBiometrics;
List<BiometricType> availableBiometrics = await _localAuth.getAvailableBiometrics();
isAvailable.value = canCheckBiometrics;
if (availableBiometrics.contains(BiometricType.face)) {
biometricType.value = BiometricType.face;
} else if (availableBiometrics.contains(BiometricType.fingerprint)) {
......@@ -32,28 +31,17 @@ class BiometricViewModel extends GetxController {
}
}
/// Xác thực sinh trắc học
Future<void> authenticate() async {
isAuthenticating.value = true;
try {
bool authenticated = await _localAuth.authenticate(
localizedReason: "Xác thực để kích hoạt đăng nhập nhanh",
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
),
);
if (authenticated) {
Get.snackbar("Thành công", "Xác thực sinh trắc học thành công!",
backgroundColor: Colors.green, colorText: Colors.white);
Future<void> registerBiometric() async {
showLoading();
client.accountBioCredential().then((value) async {
hideLoading();
if (value.isSuccess && value.data?.bioToken != null) {
await DataPreference.instance.saveBioToken(value.data!.bioToken!);
registerBiometricResponse?.call(true);
} else {
Get.snackbar("Thất bại", "Xác thực không thành công!",
backgroundColor: Colors.red, colorText: Colors.white);
}
} catch (e) {
print("Lỗi xác thực: $e");
final mgs = value.errorMessage ?? Constants.commonError;
onShowAlertError?.call(mgs);
}
isAuthenticating.value = false;
});
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment