Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Hoàng Văn Đạt
mypoint_flutter_app
Commits
e8a305af
Commit
e8a305af
authored
Apr 17, 2025
by
DatHV
Browse files
update auth logic
parent
5d865668
Changes
36
Show whitespace changes
Inline
Side-by-side
lib/base/base_screen.dart
View file @
e8a305af
...
...
@@ -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
});
...
...
lib/configs/api_paths.dart
View file @
e8a305af
...
...
@@ -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"
;
}
lib/configs/constants.dart
View file @
e8a305af
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
lib/main.dart
View file @
e8a305af
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
lib/model/auth/biometric_register_response_model.dart
0 → 100644
View file @
e8a305af
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
);
}
lib/model/auth/biometric_register_response_model.g.dart
0 → 100644
View file @
e8a305af
// 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
};
lib/model/auth/customer_balance_model.dart
0 → 100644
View file @
e8a305af
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
);
}
lib/model/auth/customer_balance_model.g.dart
0 → 100644
View file @
e8a305af
// 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
};
lib/model/auth/profile_response_model.dart
View file @
e8a305af
// 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
);
}
lib/model/auth/profile_response_model.g.dart
0 → 100644
View file @
e8a305af
// 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
,
};
lib/model/auth/user_agreement_model.dart
0 → 100644
View file @
e8a305af
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
lib/model/auth/user_agreement_model.g.dart
0 → 100644
View file @
e8a305af
// 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
,
};
lib/model/auth/working_site_model.dart
0 → 100644
View file @
e8a305af
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
);
}
lib/model/auth/working_site_model.g.dart
0 → 100644
View file @
e8a305af
// 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
,
};
lib/networking/restful_api.dart
View file @
e8a305af
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
;
...
...
lib/networking/restful_api_request.dart
View file @
e8a305af
...
...
@@ -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
),
);
}
}
lib/permission/biometric_manager.dart
View file @
e8a305af
...
...
@@ -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
...
...
lib/preference/data_preference.dart
View file @
e8a305af
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
lib/screen/biometric/biometric_screen.dart
View file @
e8a305af
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.
\n
Bạ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.
\n
Bạ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.
\n
Bạ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.
\n
Bạ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:
con
troller
.
isAuthenticating
.
value
?
const
CircularProgressIndicator
(
color:
Colors
.
white
)
:
const
Text
(
"Kích hoạt"
,
style:
TextStyle
(
color:
Colors
.
white
,
fontSize:
18
)
),
child:
con
st
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
),
],
),
),
);
}),
),
);
}
}
lib/screen/biometric/biometric_viewmodel.dart
View file @
e8a305af
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)
v
ar
isAvailable
=
false
.
obs
;
// Kiểm tra thiết bị có hỗ trợ sinh trắc học không
v
ar
isAuthenticating
=
false
.
obs
;
// Trạng thái xác thực
v
oid
Function
(
String
message
)?
onShowAlertError
;
v
oid
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
Prev
1
2
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment