Commit 73074efa authored by DatHV's avatar DatHV
Browse files

update authen

parent e8a305af
......@@ -97,11 +97,20 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
context.showConfirmAlertDialog(message, cancel: cancel, confirm: confirm, callback: callback);
}
showAlertError(String content) {
showAlert({required DataAlertModel data, bool? barrierDismissibl}) {
Get.dialog(
CustomAlertDialog(
alertData: data,
),
barrierDismissible: barrierDismissibl ?? false,
);
}
showAlertError({required String content, bool? barrierDismissible, VoidCallback? onConfirmed}) {
Get.dialog(
CustomAlertDialog(
alertData: DataAlertModel(
background: "assets/images/bg_alert_header.png",
background: "assets/images/ic_pipi_03.png",
title: "",
content: content,
buttons: [
......@@ -109,6 +118,9 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
text: "Đã Hiểu",
onPressed: () {
Get.back();
if (onConfirmed != null) {
onConfirmed();
}
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
......@@ -117,6 +129,7 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
],
),
),
barrierDismissible: barrierDismissible ?? false,
);
}
......
......@@ -17,4 +17,6 @@ class APIPaths {
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";
static const String accountLoginForPasswordChange = "/accountLoginForPasswordChange/1.0.0";
static const String accountPasswordChange = "/accountPasswordChange/1.0.0";
}
......@@ -5,9 +5,9 @@ import 'package:mypoint_flutter_app/preference/data_preference.dart';
class ModifyRequestInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
String authKey = 'Authorization';
String? token = DataPreference.instance.token;
String? token = await DataPreference.instance.token;
if (token!= null) {
options.headers[authKey] = "Bearer $token";
}
......
......@@ -33,7 +33,7 @@ class RestfulAPIClient {
queryParameters: query,
data: body,
);
String? token = DataPreference.instance.token;
String? token = await DataPreference.instance.token;
if (token != null) {
option.headers["Authorization"] = "Bearer $token";
}
......
......@@ -175,6 +175,16 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
);
}
Future<BaseResponseModel<EmptyCodable>> accountPasswordChange(String phone, String password) async {
final body = {"login_name": phone, "password": password.toSha256(), "access_token": DataPreference.instance.token ?? ""};
return requestNormal(
APIPaths.accountPasswordChange,
Method.POST,
body,
(data) => EmptyCodable.fromJson(data as Json),
);
}
Future<BaseResponseModel<BiometricRegisterResponseModel>> accountBioCredential() async {
var deviceKey = await DeviceInfo.getDeviceId();
final body = {"deviceKey": deviceKey};
......@@ -185,4 +195,14 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
(data) => BiometricRegisterResponseModel.fromJson(data as Json),
);
}
Future<BaseResponseModel<EmptyCodable>> accountCheckForPasswordChange(String password) async {
final body = {"password": password.toSha256()};
return requestNormal(
APIPaths.accountLoginForPasswordChange,
Method.POST,
body,
(data) => EmptyCodable.fromJson(data as Json),
);
}
}
......@@ -10,16 +10,28 @@ class DataPreference {
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<bool> get logged async {
final currentToken = await token;
return (currentToken ?? "").isNotEmpty;
}
Future<String?> get token async {
if (_loginToken != null) {
return _loginToken?.accessToken;
} else {
await loadLoginToken();
return _loginToken?.accessToken;
}
}
Future<void> saveUserProfile(ProfileResponseModel profile) async {
_profile = profile;
final prefs = await SharedPreferences.getInstance();
profile = profile;
final jsonString = jsonEncode(profile.toJson());
await prefs.setString('user_profile', jsonString);
}
......@@ -34,8 +46,8 @@ class DataPreference {
}
Future<void> saveLoginToken(LoginTokenResponseModel token) async {
final prefs = await SharedPreferences.getInstance();
_loginToken = token;
final prefs = await SharedPreferences.getInstance();
final jsonString = jsonEncode(token.toJson());
await prefs.setString('login_token', jsonString);
}
......@@ -50,9 +62,9 @@ class DataPreference {
}
Future<void> clearLoginToken() async {
_loginToken = null;
final prefs = await SharedPreferences.getInstance();
await prefs.remove('login_token');
_loginToken = null;
}
Future<void> clearBioToken(String phone) async {
......
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../resouce/base_color.dart';
import '../../widgets/alert/data_alert_model.dart';
import '../main_tab_screen/main_tab_screen.dart';
import 'biometric_viewmodel.dart';
class BiometricAuthScreen extends StatefulWidget {
class BiometricAuthScreen extends BaseScreen {
const BiometricAuthScreen({super.key});
@override
State<BiometricAuthScreen> createState() => _BiometricAuthScreenState();
}
class _BiometricAuthScreenState extends State<BiometricAuthScreen> {
class _BiometricAuthScreenState extends BaseState<BiometricAuthScreen> with BasicState {
final controller = Get.put(BiometricViewModel());
@override
Widget build(BuildContext context) {
initState() {
super.initState();
controller.onShowAlertError = (message) {
showAlertError(
content: message,
barrierDismissible: true,
onConfirmed: () {
Get.offAll(MainTabScreen());
});
};
controller.registerBiometricResponse = (result) {
var title = result
? "Kích hoạt sinh trắc học thành công!"
: "Kích hoạt sinh trắc học thất bại!";
var type = controller.biometricType.value == BiometricType.face ? "Face ID" : "Touch ID";
var message = result
? "Từ bây giờ bạn có thể sử dụng $type để đăng nhập thay vì nhập mật khẩu."
: "Đã có lỗi xảy ra khi xác thực thông tin sinh trắc học. Vui lòng thử lại sau.";
DataAlertModel alertData = DataAlertModel(
background: result? "assets/images/ic_pipi_05.png" : "assets/images/ic_pipi_03.png",
title: title,
content: message,
buttons: [
AlertButton(
text: "Đã hiểu",
onPressed: () => Get.offAll(MainTabScreen()),
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
],
);
showAlert(data: alertData);
};
}
@override
Widget createBody() {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
......
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../preference/data_preference.dart';
import '../../resouce/base_color.dart';
import '../../widgets/back_button.dart';
import '../../widgets/support_button.dart';
import '../login/login_viewmodel.dart';
import 'change_pass_viewmodel.dart';
class ChangePassScreen extends BaseScreen {
const ChangePassScreen({super.key});
@override
State<ChangePassScreen> createState() => _ChangePassScreenState();
}
class _ChangePassScreenState extends BaseState<ChangePassScreen> with BasicState {
final TextEditingController _passController = TextEditingController();
final FocusNode _focusNode = FocusNode();
final viewModel = Get.put(ChangePassViewModel());
final _phone = DataPreference.instance.phone ?? "";
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_focusNode.requestFocus();
});
}
@override
void dispose() {
_focusNode.dispose();
_passController.dispose();
super.dispose();
}
@override
Widget createBody() {
return GestureDetector(
onTap: hideKeyboard,
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
centerTitle: true,
leading: CustomBackButton(),
actions: [SupportButton()],
),
backgroundColor: Colors.white,
body: SafeArea(
child: Stack(
children: [
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Đổi mật khẩu",
style: TextStyle(color: BaseColor.second600, fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildWelcomeText(),
const SizedBox(height: 16),
_buildPasswordField(),
_buildErrorText(),
const SizedBox(height: 8),
_buildActionRow(),
const SizedBox(height: 8),
],
),
),
SizedBox.expand(),
Positioned(left: 0, right: 0, bottom: 16, child: _buildContinueButton()),
],
),
),
),
);
}
Widget _buildWelcomeText() {
return RichText(
text: TextSpan(
style: const TextStyle(fontSize: 14, color: BaseColor.second500),
children: [
const TextSpan(text: "Nhập mật khẩu đăng nhập cho tài khoản "),
TextSpan(
text: _phone,
style: const TextStyle(fontWeight: FontWeight.w500, color: BaseColor.primary500),
),
],
),
);
}
Widget _buildPasswordField() {
return Obx(() {
return TextField(
controller: _passController,
focusNode: _focusNode,
keyboardType: TextInputType.number,
obscureText: !viewModel.isPasswordVisible.value,
onChanged: viewModel.onPasswordChanged,
decoration: InputDecoration(
hintText: "Nhập mật khẩu",
prefixIcon: const Icon(Icons.password, color: BaseColor.second500),
hintStyle: const TextStyle(color: BaseColor.second200),
fillColor: Colors.white,
filled: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.grey, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.grey, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.grey, width: 1),
),
suffixIcon: IconButton(
icon: Icon(
viewModel.isPasswordVisible.value ? Icons.visibility : Icons.visibility_off,
color: BaseColor.second500,
),
onPressed: viewModel.togglePasswordVisibility,
),
),
);
});
}
Widget _buildErrorText() {
return Obx(() {
if (viewModel.loginState.value == LoginState.error) {
return Padding(
padding: const EdgeInsets.only(top: 4),
child: Text("Sai mật khẩu, vui lòng thử lại!", style: TextStyle(color: BaseColor.primary400)),
);
}
return const SizedBox.shrink();
});
}
Widget _buildActionRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
viewModel.onForgotPassPressed(_phone);
},
child: const Text("Quên mật khẩu?", style: TextStyle(fontSize: 14, color: Color(0xFF3662FE))),
),
],
);
}
Widget _buildContinueButton() {
return Obx(() {
bool enabled = false;
Color color = BaseColor.second400;
switch (viewModel.loginState.value) {
case LoginState.typing:
if (viewModel.password.value.isNotEmpty) {
color = BaseColor.primary500;
enabled = true;
} else {
enabled = false;
color = BaseColor.second400;
}
break;
case LoginState.error:
case LoginState.idle:
enabled = false;
color = BaseColor.second400;
break;
}
return Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: color,
minimumSize: const Size.fromHeight(48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
onPressed: () {
enabled ? viewModel.accountCheckForPasswordChange() : null;
},
child: const Text(
"Tiếp tục",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
),
),
);
});
}
}
import 'package:get/get.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';
import '../create_pass/change_pass_repository.dart';
import '../create_pass/create_pass_screen.dart';
import '../login/login_viewmodel.dart';
import '../otp/forgot_pass_otp_repository.dart';
import '../otp/otp_screen.dart';
class ChangePassViewModel extends RestfulApiViewModel {
var isPasswordVisible = false.obs;
var password = "".obs;
var loginState = LoginState.idle.obs;
void Function(String message)? onShowAlertError;
void onPasswordChanged(String value) {
password.value = value;
if (value.isEmpty) {
loginState.value = LoginState.idle;
} else {
loginState.value = LoginState.typing;
}
}
void togglePasswordVisibility() {
isPasswordVisible.value = !isPasswordVisible.value;
}
void onForgotPassPressed(String phone) {
showLoading();
client.otpCreateNew(phone).then((value) {
hideLoading();
// TODO: handle error later
if (value.isSuccess) {
Get.to(OtpScreen(repository: ForgotPassOTPRepository(phone, value.data?.resendAfterSecond ?? Constants.otpTtl)));
}
});
}
void accountCheckForPasswordChange() {
showLoading();
final phone = DataPreference.instance.phone ?? "";
client.accountPasswordChange(phone, password.value).then((value) {
hideLoading();
if (value.isSuccess) {
Get.to(CreatePasswordScreen(repository: ChangePasswordRepository(phone)));
} else {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
}
});
}
}
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/screen/create_pass/signup_create_password_repository.dart';
import '../../base/base_response_model.dart';
import '../../base/restful_api_viewmodel.dart';
import '../login/login_screen.dart';
import '../splash/splash_screen_viewmodel.dart';
class ChangePasswordRepository extends RestfulApiViewModel implements ICreatePasswordRepository {
@override
late String phoneNumber;
ChangePasswordRepository(this.phoneNumber);
@override
Future<BaseResponseModel<EmptyCodable>> setPassword(String password) async {
showLoading();
return client.accountPasswordChange(phoneNumber, password).then((value) {
hideLoading();
if (value.status == "success" || value.code == 200) {
print("Change password success");
}
return value;
});
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/create_pass/signup_create_password_repository.dart';
import 'package:mypoint_flutter_app/screen/onboarding/onboarding_screen.dart';
......@@ -118,6 +119,7 @@ class CreatePasswordScreen extends StatelessWidget {
required ValueChanged<String> onChanged,
}) {
return TextField(
inputFormatters: [LengthLimitingTextInputFormatter(6)],
obscureText: obscureText,
onChanged: onChanged,
decoration: InputDecoration(
......
......@@ -3,21 +3,39 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import '../setting/setting_screen.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center, // ✅ căn giữa dọc
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(onPressed: () => _logout(context), child: const Text('Đăng xuất')),
ElevatedButton(onPressed: () => _showSetting(context), child: const Text('Setting')),
],
),
),
);
}
void _logout(BuildContext context) async {
final confirm = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Xác nhận'),
content: const Text('Bạn có chắc muốn đăng xuất?'),
actions: [
TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Hủy')),
TextButton(onPressed: () => Navigator.of(ctx).pop(true), child: const Text('Đăng xuất')),
],
),
builder:
(ctx) => AlertDialog(
title: const Text('Xác nhận'),
content: const Text('Bạn có chắc muốn đăng xuất?'),
actions: [
TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Hủy')),
TextButton(onPressed: () => Navigator.of(ctx).pop(true), child: const Text('Đăng xuất')),
],
),
);
if (confirm == true) {
......@@ -26,13 +44,7 @@ class HomeScreen extends StatelessWidget {
}
}
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () => _logout(context),
child: const Text('Đăng xuất'),
),
);
void _showSetting(BuildContext context) async {
Get.to(() => const SettingScreen());
}
}
\ No newline at end of file
}
......@@ -90,7 +90,7 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
loginVM.onShowAlertError = (message) {
if (message.isNotEmpty) {
showAlertError(message);
showAlertError(content: message);
}
};
......@@ -278,13 +278,10 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
color = BaseColor.second400;
}
break;
case LoginState.done:
color = BaseColor.primary500;
enabled = true;
break;
case LoginState.error:
case LoginState.idle:
color = BaseColor.second400;
enabled = false;
color = BaseColor.second400;
break;
}
......
......@@ -12,7 +12,7 @@ import '../../preference/data_preference.dart';
import '../main_tab_screen/main_tab_screen.dart';
// login_state_enum.dart
enum LoginState { idle, typing, done, error }
enum LoginState { idle, typing, error }
class LoginViewModel extends RestfulApiViewModel {
final BiometricManager _biometricManager = BiometricManager();
......
......@@ -46,7 +46,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
var errorCode = response?.errorCode ?? "";
var errorMessage = response?.errorMessage ?? Constants.commonError;
WidgetsBinding.instance.addPostFrameCallback((_) {
showAlertError(errorMessage);
showAlertError(content: errorMessage);
if (errorCode == ErrorCodes.deviceLock) {
// show alert error popupErrorCSKH
return;
......
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