Commit e8a305af authored by DatHV's avatar DatHV
Browse files

update auth logic

parent 5d865668
......@@ -5,6 +5,10 @@ import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/screen/login/login_screen.dart';
import '../../base/base_response_model.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../permission/biometric_manager.dart';
import '../../preference/data_preference.dart';
import '../biometric/biometric_screen.dart';
import '../main_tab_screen/main_tab_screen.dart';
import '../splash/splash_screen_viewmodel.dart';
abstract class ICreatePasswordRepository {
......@@ -15,8 +19,8 @@ abstract class ICreatePasswordRepository {
class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICreatePasswordRepository {
@override
late String phoneNumber;
SignUpCreatePasswordRepository(this.phoneNumber);
final BiometricManager _biometricManager = BiometricManager();
@override
Future<BaseResponseModel<EmptyCodable>> setPassword(String password) async {
......@@ -25,9 +29,41 @@ class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICre
hideLoading();
if (value.status == "success" || value.code == 200) {
print("signup success");
Get.off(() => LoginScreen(phoneNumber: phoneNumber));
_autoLogin(password);
}
return value;
});
}
void _autoLogin(String password) {
showLoading();
client.login(phoneNumber, password).then((response) async {
hideLoading();
if (response.isSuccess && response.data != null) {
await DataPreference.instance.saveLoginToken(response.data!);
_getUserProfile();
} else {
Get.off(() => LoginScreen(phoneNumber: phoneNumber));
}
});
}
void _getUserProfile() {
showLoading();
client.getUserProfile().then((value) async {
hideLoading();
final userProfile = value.data;
if (value.isSuccess && userProfile != null) {
await DataPreference.instance.saveUserProfile(userProfile);
if (await _biometricManager.canCheckBiometrics()) {
Get.to(BiometricAuthScreen());
} else {
Get.to(MainTabScreen());
}
} else {
DataPreference.instance.clearLoginToken();
Get.off(() => LoginScreen(phoneNumber: phoneNumber));
}
});
}
}
import 'package:flutter/material.dart';
class GameScreen extends StatelessWidget {
const GameScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('Games'));
}
}
\ No newline at end of file
// home_screen.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
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')),
],
),
);
if (confirm == true) {
DataPreference.instance.clearLoginToken();
Get.offAllNamed('/onboarding');
}
}
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () => _logout(context),
child: const Text('Đăng xuất'),
),
);
}
}
\ No newline at end of file
// login_screen.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../permission/biometric_manager.dart';
import '../../resouce/base_color.dart';
import '../../widgets/alert/custom_alert_dialog.dart';
import '../../widgets/alert/data_alert_model.dart';
import '../../widgets/back_button.dart';
import '../../widgets/support_button.dart';
import 'login_viewmodel.dart';
......@@ -21,10 +24,76 @@ class LoginScreen extends BaseScreen {
class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
final TextEditingController _phoneController = TextEditingController();
final FocusNode _focusNode = FocusNode();
final loginVM = Get.put(LoginViewModel());
@override
void initState() {
super.initState();
loginVM.onShowChangePass = (message) {
Get.dialog(
CustomAlertDialog(
alertData: DataAlertModel(
background: "assets/images/bg_alert_header.png",
title: "Cài đặt mật khẩu",
content: message,
buttons: [
AlertButton(
text: "Cài đặt ngay",
onPressed: () {
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
],
),
),
);
};
loginVM.onShowDeviceError = (message) {
loginVM.onChangePhonePressed();
};
loginVM.onShowInvalidAccount = (message) {
Get.dialog(
CustomAlertDialog(
alertData: DataAlertModel(
background: "assets/images/bg_alert_header.png",
title: "",
content: message,
buttons: [
AlertButton(
text: "Quên mật khẩu",
onPressed: () {
loginVM.onForgotPassPressed(widget.phoneNumber);
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
AlertButton(
text: "Đã hiểu",
onPressed: () {
Get.back();
},
bgColor: Colors.white,
textColor: Colors.black,
isPrimary: true,
),
],
),
),
);
};
loginVM.onShowAlertError = (message) {
if (message.isNotEmpty) {
showAlertError(message);
}
};
WidgetsBinding.instance.addPostFrameCallback((_) {
_focusNode.requestFocus();
});
......@@ -39,7 +108,7 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
@override
Widget createBody() {
final loginVM = Get.put(LoginViewModel());
// final loginVM = Get.find<LoginViewModel>();
return GestureDetector(
onTap: hideKeyboard,
child: Scaffold(
......@@ -185,7 +254,10 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
}
return Column(
children: [
IconButton(icon: Icon(icon, size: 36), onPressed: () => vm.onBiometricLoginPressed(Get.context!)),
IconButton(
icon: Icon(icon, size: 36),
onPressed: () => vm.onBiometricLoginPressed(widget.phoneNumber),
),
Text("Đăng nhập bằng $label"),
],
);
......@@ -225,7 +297,9 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
minimumSize: const Size.fromHeight(48),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
onPressed: enabled ? vm.onLoginPressed : null,
onPressed: () {
enabled ? vm.onLoginPressed(widget.phoneNumber) : null;
},
child: const Text(
"Đăng nhập",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
......
import 'dart:convert';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/screen/onboarding/onboarding_screen.dart';
import 'package:mypoint_flutter_app/screen/otp/forgot_pass_otp_repository.dart';
import 'package:mypoint_flutter_app/screen/otp/otp_screen.dart';
import '../../base/base_response_model.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../model/auth/login_token_response_model.dart';
import '../../permission/biometric_manager.dart';
import '../../preference/data_preference.dart';
import '../main_tab_screen/main_tab_screen.dart';
// login_state_enum.dart
enum LoginState { idle, typing, done, error }
......@@ -19,6 +22,11 @@ class LoginViewModel extends RestfulApiViewModel {
var password = "".obs;
var biometricType = BiometricTypeEnum.none.obs;
void Function(String message)? onShowAlertError;
void Function(String message)? onShowDeviceError;
void Function(String message)? onShowChangePass;
void Function(String message)? onShowInvalidAccount;
@override
void onInit() {
super.onInit();
......@@ -47,65 +55,88 @@ class LoginViewModel extends RestfulApiViewModel {
isPasswordVisible.value = !isPasswordVisible.value;
}
void onLoginPressed() {
void onLoginPressed(String phone) {
if (password.value.isEmpty) return;
// Ví dụ: Mật khẩu chuẩn là "123456"
if (password.value == "123456") {
loginState.value = LoginState.done;
debugPrint("Đăng nhập thành công!");
// TODO: Chuyển màn hình
} else {
loginState.value = LoginState.error;
debugPrint("Sai mật khẩu!");
}
showLoading();
client.login(phone, password.value).then((value) async {
hideLoading();
_handleLoginResponse(value, phone);
});
}
void _getUserProfile() {
showLoading();
client.getUserProfile().then((value) async {
hideLoading();
final userProfile = value.data;
if (value.isSuccess && userProfile != null) {
await DataPreference.instance.saveUserProfile(userProfile);
Get.to(MainTabScreen());
} else {
DataPreference.instance.clearLoginToken();
final mgs = value.errorMessage ?? Constants.commonError;
onShowAlertError?.call(mgs);
}
});
}
void onChangePhonePressed() {
Get.back();
}
void onForgotPassPressed(String phoneNumber) {
void onForgotPassPressed(String phone) {
showLoading();
client.otpCreateNew(phoneNumber).then((value) {
client.otpCreateNew(phone).then((value) {
hideLoading();
// TODO: handle error later
if (value.isSuccess) {
Get.to(
OtpScreen(
repository: ForgotPassOTPRepository(phoneNumber, value.data?.resendAfterSecond ?? Constants.otpTtl),
repository: ForgotPassOTPRepository(phone, value.data?.resendAfterSecond ?? Constants.otpTtl),
),
);
}
});
}
Future<void> _handleLoginResponse(BaseResponseModel<LoginTokenResponseModel> response, String phone) async {
if (response.isSuccess && response.data != null) {
await DataPreference.instance.saveLoginToken(response.data!);
_getUserProfile();
return;
}
final errorMsg = response.errorMessage ?? Constants.commonError;
final errorCode = response.errorCode;
if (errorCode == ErrorCodes.deviceUndefined) {
onShowDeviceError?.call(errorMsg);
} else if (errorCode == ErrorCodes.requiredChangePass) {
onShowChangePass?.call(errorMsg);
} else if (errorCode == ErrorCodes.invalidAccount) {
onShowInvalidAccount?.call(errorMsg);
} else {
if (errorCode == ErrorCodes.bioTokenInvalid) {
DataPreference.instance.clearBioToken(phone);
}
onShowAlertError?.call(errorMsg);
}
}
/// Xác thực đăng nhập bằng sinh trắc
Future<void> onBiometricLoginPressed(BuildContext context) async {
// Kiểm tra thiết bị hỗ trợ
Future<void> onBiometricLoginPressed(String phone) async {
final canUse = await canUseBiometrics();
if (!canUse || biometricType.value == BiometricTypeEnum.none) {
Get.snackbar("Thông báo", "Thiết bị không hỗ trợ sinh trắc học", snackPosition: SnackPosition.BOTTOM);
onShowAlertError?.call("Thiết bị không hỗ trợ sinh trắc học");
return;
}
// Tuỳ chọn: hiển thị dialog xác nhận trước khi gọi authenticate
final success = await _biometricManager.showCustomBiometricDialog(
context,
title: "Xác thực sinh trắc học",
content:
(biometricType.value == BiometricTypeEnum.faceId)
? "Bạn có muốn đăng nhập bằng Face ID không?"
: "Bạn có muốn đăng nhập bằng vân tay không?",
confirmText: "Đồng ý",
cancelText: "Huỷ",
);
if (success) {
loginState.value = LoginState.done;
debugPrint("Đăng nhập bằng sinh trắc thành công!");
// TODO: Chuyển màn hình
} else {
debugPrint("Xác thực thất bại hoặc người dùng huỷ.");
final bioToken = await DataPreference.instance.getBioToken(phone);
if (bioToken == null) {
onShowAlertError?.call("Tài khoản này chưa kích hoạt đăng nhập bằng sinh trắc học!\nVui lòng đăng nhập > cài đặt để kích hoạt tính năng");
return;
}
client.login(phone, password.value).then((value) async {
hideLoading();
_handleLoginResponse(value, phone);
});
}
}
import 'package:flutter/material.dart';
import '../game/games_screen.dart';
import '../home/home_screen.dart';
import '../personal/personal_screen.dart';
import '../shopping/shopping_screen.dart';
import '../voucher/voucher_screen.dart';
class MainTabScreen extends StatefulWidget {
const MainTabScreen({super.key});
@override
State<MainTabScreen> createState() => _MainTabScreenState();
}
class _MainTabScreenState extends State<MainTabScreen> {
int _currentIndex = 0;
final List<Widget> _pages = const [
HomeScreen(),
VoucherScreen(),
GameScreen(),
ShoppingScreen(),
PersonalScreen(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: Container(
decoration: const BoxDecoration(
color: Colors.red,
),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
child: SafeArea(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildTabItem(icon: Icons.home, label: 'Trang chủ', index: 0),
_buildTabItem(icon: Icons.star, label: 'Ưu đãi', index: 1),
_buildTabItem(icon: Icons.videogame_asset, label: 'Game', index: 2),
_buildTabItem(icon: Icons.shopping_cart, label: 'Mua sắm', index: 3),
_buildTabItem(icon: Icons.person, label: 'Cá nhân', index: 4),
],
),
),
),
);
}
Widget _buildTabItem({required IconData icon, required String label, required int index}) {
final isSelected = _currentIndex == index;
return GestureDetector(
onTap: () => setState(() => _currentIndex = index),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: Colors.white.withOpacity(isSelected ? 1 : 0.6)),
const SizedBox(height: 4),
Text(label, style: TextStyle(
color: Colors.white.withOpacity(isSelected ? 1 : 0.6),
fontSize: 12,
)),
],
),
);
}
}
......@@ -7,6 +7,7 @@ import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../configs/constants.dart';
import '../../resouce/base_color.dart';
import '../biometric/biometric_screen.dart';
import '../faqs/faqs_screen.dart';
import '../login/login_screen.dart';
import '../otp/otp_screen.dart';
......@@ -37,7 +38,6 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
_handleResponseCheckPhoneNumber(response.data);
_handleResponseError(response);
});
//
});
}
......@@ -50,8 +50,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
if (errorCode == ErrorCodes.deviceLock) {
// show alert error popupErrorCSKH
return;
}
//show alert error popupError
} //show alert error popupError
});
}
......
import 'package:flutter/material.dart';
class PersonalScreen extends StatelessWidget {
const PersonalScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('Cá nhân'));
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
class ShoppingScreen extends StatelessWidget {
const ShoppingScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('Mua sắm'));
}
}
\ No newline at end of file
......@@ -2,7 +2,6 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:mypoint_flutter_app/configs/api_paths.dart';
import 'package:mypoint_flutter_app/dio_http_service/api_helper.dart';
import 'package:mypoint_flutter_app/networking/api_service.dart';
......
import 'package:flutter/material.dart';
class VoucherScreen extends StatelessWidget {
const VoucherScreen({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('Ưu đãi'));
}
}
\ No newline at end of file
......@@ -36,7 +36,7 @@ class CustomAlertDialog extends StatelessWidget {
// fit: BoxFit.cover,
// ),
),
const SizedBox(height: 10),
const SizedBox(height: 2),
// Title
if (alertData.title != null)
Text(
......@@ -97,7 +97,7 @@ class CustomAlertDialog extends StatelessWidget {
onPressed: btn.onPressed,
child: Text(
btn.text,
style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold),
style: TextStyle(color: btn.textColor, fontSize: 14, fontWeight: FontWeight.bold),
),
),
),
......@@ -122,7 +122,7 @@ class CustomAlertDialog extends StatelessWidget {
onPressed: btn.onPressed,
child: Text(
btn.text,
style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold),
style: TextStyle(color: btn.textColor, fontSize: 14, fontWeight: FontWeight.bold),
),
),
),
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/resouce/define_image.dart';
import 'alert_style.dart';
import 'animation_transition.dart';
import 'dialog_button.dart';
class Alert {
final String? id;
final BuildContext context;
final AlertType? type;
final AlertStyle style;
final EdgeInsets? padding;
final Widget? image;
final String? urlImage;
final String? title;
final String? desc;
final Widget content;
final List<DialogButton>? buttons;
final Function? closeFunction;
final Widget? closeIcon;
final bool onWillPopActive;
final bool useRootNavigator;
final AlertAnimation? alertAnimation;
Alert({
required this.context,
this.id,
this.type,
this.style = const AlertStyle(),
this.padding,
this.image,
this.urlImage,
this.title,
this.desc,
this.content = const SizedBox(),
this.buttons,
this.closeFunction,
this.closeIcon,
this.onWillPopActive = false,
this.alertAnimation,
this.useRootNavigator = true,
});
/// Displays defined alert window
Future<bool?> show() async {
return await showGeneralDialog(
context: context,
pageBuilder: (BuildContext buildContext, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _buildDialog();
},
barrierDismissible: style.isOverlayTapDismiss,
barrierLabel:
MaterialLocalizations.of(context).modalBarrierDismissLabel,
barrierColor: style.overlayColor,
useRootNavigator: useRootNavigator,
transitionDuration: style.animationDuration,
transitionBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
alertAnimation == null
? _showAnimation(animation, secondaryAnimation, child)
: alertAnimation!(
context, animation, secondaryAnimation, child));
}
/// Dismisses the alert dialog.
Future<void> dismiss() async {
Navigator.of(context, rootNavigator: useRootNavigator).pop();
}
/// Alert dialog content widget
Widget _buildDialog() {
final Widget child = Align(
alignment: style.alertAlignment,
child: ConstrainedBox(
constraints: style.constraints ?? BoxConstraints.loose(Size.infinite),
child: SingleChildScrollView(
child: AlertDialog(
key: id == null ? null : Key(id!),
backgroundColor: style.backgroundColor ??
Theme.of(context).dialogTheme.backgroundColor,
shape: style.alertBorder ?? _defaultShape(),
insetPadding: style.alertPadding,
elevation: style.alertElevation,
titlePadding: const EdgeInsets.all(0.0),
title: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_getCloseButton(),
Padding(
padding: padding ??
EdgeInsets.fromLTRB(
20, (style.isCloseButton ? 0 : 10), 20, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Center(
child: _getImage(),
),
if (title != null)
_getTextWidget(
style.isTitleSelectable,
title,
style.titleStyle,
style.titleTextAlign,
style.titlePadding ??
EdgeInsets.only(
top: 15,
bottom: desc == null ? 0 : 10,
),
),
if (desc != null)
_getTextWidget(
style.isDescSelectable,
desc,
style.descStyle,
style.descTextAlign,
style.descPadding,
),
content,
],
),
)
],
),
),
),
contentPadding: style.buttonAreaPadding,
content: style.buttonsDirection == ButtonsDirection.row
? Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _getButtons(),
)
: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _getButtons(),
)),
),
),
);
return onWillPopActive
? PopScope(child: child) //WillPopScope(onWillPop: () async => false, child: child)
: child;
}
/// Returns Text Widget or Selectable Text Widget with Padding
/// based on isSelectable property.
Widget _getTextWidget(bool isSelectable, String? text, TextStyle textStyle,
TextAlign textAlign, EdgeInsets edgeInsets) {
return Padding(
padding: edgeInsets,
child: _getTextWidgetBySelectable(
isSelectable,
text,
textStyle,
textAlign,
),
);
}
/// Returns Text Widget or Selectable Text Widget
/// based on isSelectable property.
Widget _getTextWidgetBySelectable(bool isSelectable, String? text,
TextStyle textStyle, TextAlign textAlign) {
if (isSelectable) {
return SelectableText(
text ?? "",
style: textStyle,
textAlign: textAlign,
);
} else {
return Text(
text ?? "",
style: textStyle,
textAlign: textAlign,
);
}
}
/// Returns the close button on the top right
Widget _getCloseButton() {
return style.isCloseButton
? Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 10, 0),
child: GestureDetector(
onTap: () {
if (closeFunction == null) {
Navigator.of(context, rootNavigator: useRootNavigator).pop();
} else {
closeFunction!();
}
},
child: Container(
alignment: FractionalOffset.topRight,
child: closeIcon != null
? Container(child: closeIcon)
: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(icClose),
),
),
),
),
),
)
: Container();
}
/// Returns defined buttons. Default: Cancel Button
List<Widget> _getButtons() {
List<Widget> expandedButtons = [];
if (style.isButtonVisible) {
if (buttons != null) {
for (var button in buttons!) {
var buttonWidget = Padding(
padding: const EdgeInsets.only(left: 2, right: 2),
child: button,
);
if ((button.width != null && buttons!.length == 1) ||
style.buttonsDirection == ButtonsDirection.column) {
expandedButtons.add(buttonWidget);
} else {
expandedButtons.add(Expanded(
child: buttonWidget,
));
}
}
} else {
Widget cancelButton = DialogButton(
child: Text(
"CANCEL",
style: TextStyle(color: Colors.white, fontSize: 20),
),
onPressed: () => Navigator.pop(context),
);
if (style.buttonsDirection == ButtonsDirection.row) {
cancelButton = Expanded(
child: cancelButton,
);
}
expandedButtons.add(cancelButton);
}
}
return expandedButtons;
}
/// Returns alert default border style
ShapeBorder _defaultShape() {
return RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
side: BorderSide(
color: Colors.blueGrey,
),
);
}
/// Returns alert image for icon
Widget _getImage() {
Widget response;
if (urlImage != null && urlImage!.isNotEmpty) {
response = Image.network(urlImage!);
return response;
}
switch (type) {
case AlertType.success:
response = Image.asset("");
break;
case AlertType.error:
response = Image.asset("");
break;
case AlertType.info:
response = Image.asset("");
break;
case AlertType.warning:
response = Image.asset("");
break;
case AlertType.none:
response = Container();
break;
default:
response = image ?? Container();
break;
}
return response;
}
/// Shows alert with selected animation
_showAnimation(animation, secondaryAnimation, child) {
return AnimationTransition.fromBottom(animation, secondaryAnimation, child);
}
}
import 'package:flutter/material.dart';
enum AlertType { error, success, info, warning, none }
/// Buttons container
enum ButtonsDirection { row, column }
/// Defines Default Alert Window Padding
const EdgeInsets defaultAlertPadding = EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0);
typedef AlertAnimation = Widget Function(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
);
/// Alert style class for reusable customization of dialogs.
class AlertStyle {
/// The [animationDuration] parameter is used to set the animation transition time. Default: "200 ms"
final Duration animationDuration;
/// The [alertBorder] parameter sets border.
final ShapeBorder? alertBorder;
/// The [isButtonVisible] paramater is used to decide hide or display buttons
final bool isButtonVisible;
/// The [isCloseButton] parameter sets visibility of the close button. Default: "true"
final bool isCloseButton;
/// The [isOverlayTapDismiss] parameter sets closing the alert by clicking outside. Default: "true"
final bool isOverlayTapDismiss;
/// The [backgroundColor] parameter sets the background color.
final Color? backgroundColor;
/// The [overlayColor] parameter sets the background color of the outside. Default: "Color(0xDD000000)"
final Color overlayColor;
/// The [titleStyle] parameter sets alert title text style.
final TextStyle titleStyle;
/// The [descStyle] parameter sets alert desc text style.
final TextStyle descStyle;
/// The [titleTextAlign] parameter sets alignment of the title.
final TextAlign titleTextAlign;
/// The [descTextAlign] parameter sets alignment of the desc.
final TextAlign descTextAlign;
/// The [buttonAreaPadding] parameter sets button area padding.
final EdgeInsets buttonAreaPadding;
/// The [constraints] parameter sets Alert size.
final BoxConstraints? constraints;
/// The [buttonsDirection] parameter sets button container as Row or Col.
final ButtonsDirection buttonsDirection;
/// The [alertElevation] parameter sets elevation of alert dialog container.
final double? alertElevation;
/// The [alertPadding] parameter sets alert area padding.
final EdgeInsets alertPadding;
/// The [alertAlignment] parameter sets alert dialog alignment.
final AlignmentGeometry alertAlignment;
/// The [isTitleSelectable] parameter sets title text is selectable or not.
final bool isTitleSelectable;
/// The [isDescSelectable] parameter sets desc text is selectable or not.
final bool isDescSelectable;
/// The [titlePadding] parameter sets title area padding.
final EdgeInsets? titlePadding;
/// The [descPadding] parameter sets desc area padding.
final EdgeInsets descPadding;
/// Alert style constructor function
/// All properties are optional.
const AlertStyle({
this.animationDuration = const Duration(milliseconds: 200),
this.alertBorder,
this.isButtonVisible = true,
this.isCloseButton = true,
this.isOverlayTapDismiss = true,
this.backgroundColor,
this.overlayColor = Colors.black87,
this.titleStyle = const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontStyle: FontStyle.normal),
this.titleTextAlign = TextAlign.center,
this.descStyle = const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
fontStyle: FontStyle.normal),
this.descTextAlign = TextAlign.center,
this.buttonAreaPadding = const EdgeInsets.all(20.0),
this.constraints,
this.buttonsDirection = ButtonsDirection.row,
this.alertElevation,
this.alertPadding = defaultAlertPadding,
this.alertAlignment = Alignment.center,
this.isTitleSelectable = false,
this.isDescSelectable = false,
this.titlePadding,
this.descPadding = const EdgeInsets.all(0.0),
});
}
import 'package:flutter/material.dart';
/// Predefined functions for transition animations
///
/// Exp: AnimationTransition.fromRight(animation, secondaryAnimation, child);
class AnimationTransition {
/// Slide animation, from right to left (SlideTransition)
static fromRight(Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
/// Slide animation, from left to right (SlideTransition)
static fromLeft(Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
/// Slide animation, from top to bottom (SlideTransition)
static fromTop(Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, -1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
/// Slide animation, from top to bottom (SlideTransition)
static fromBottom(Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
/// Scale animation, from in to out (ScaleTransition)
static grow(Animation<double> animation, Animation<double> secondaryAnimation,
Widget child) {
return ScaleTransition(
scale: Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: animation,
curve: Interval(
0.00,
0.50,
curve: Curves.linear,
),
),
),
child: child,
);
}
/// Scale animation, from out to in (ScaleTransition)
static shrink(Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return ScaleTransition(
scale: Tween<double>(
begin: 1.2,
end: 1.0,
).animate(
CurvedAnimation(
parent: animation,
curve: Interval(0.5, 1.0),
// curve: Interval(0,
// 0.50,
// 1.00,
// curve: Curves.linear,
// ),
),
),
child: child,
);
}
}
/*
* rflutter_alert
* Created by Ratel
* https://ratel.com.tr
*
* Copyright (c) 2018 Ratel, LLC. All rights reserved.
* See LICENSE for distribution and usage details.
*/
import 'package:flutter/material.dart';
/// Used for defining alert buttons.
///
/// [child] and [onPressed] parameters are required.
class DialogButton extends StatelessWidget {
final Widget? child;
final double? width;
final double height;
final Color? color;
final Color? highlightColor;
final Color? splashColor;
final Gradient? gradient;
final BorderRadius radius;
final VoidCallback? onPressed;
final BoxBorder border;
final EdgeInsets padding;
final EdgeInsets margin;
/// DialogButton constructor
const DialogButton({
super.key,
required this.child,
this.width,
this.height = 50.0,
this.color,
this.highlightColor,
this.splashColor,
this.gradient,
this.radius = const BorderRadius.all(Radius.circular(20)),
this.border = const Border.fromBorderSide(
BorderSide(
color: Color(0x00000000),
width: 0,
style: BorderStyle.solid,
),
),
this.padding = const EdgeInsets.only(left: 6, right: 6),
this.margin = const EdgeInsets.all(6),
required this.onPressed,
});
/// Creates alert buttons based on constructor params
@override
Widget build(BuildContext context) {
return Container(
margin: margin,
width: width,
height: height,
decoration: BoxDecoration(
color: color ?? Theme.of(context).colorScheme.secondary,
gradient: gradient,
borderRadius: radius,
border: border,
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: radius,
highlightColor: highlightColor ?? Theme.of(context).highlightColor,
splashColor: splashColor ?? Theme.of(context).splashColor,
onTap: onPressed,
child: Padding(
padding: padding,
child: Center(
child: child,
),
),
),
),
);
}
}
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