feat : captcha widget.dart
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
enum ApiEnvironment {
|
||||
dam(url: 'https://api.dam.rasadyar.net');
|
||||
dam(url: 'https://api.dam.rasadyar.net/');
|
||||
|
||||
const ApiEnvironment({required this.url});
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@ class DioRemoteManager {
|
||||
DioRemote? _currentClient;
|
||||
ApiEnvironment? _currentEnv;
|
||||
|
||||
DioRemote setEnvironment(ApiEnvironment env) {
|
||||
Future<DioRemote> setEnvironment([
|
||||
ApiEnvironment env = ApiEnvironment.dam,
|
||||
]) async {
|
||||
if (_currentEnv != env) {
|
||||
_currentClient = DioRemote(env.baseUrl);
|
||||
await _currentClient?.init();
|
||||
_currentEnv = env;
|
||||
}
|
||||
return _currentClient!;
|
||||
@@ -30,10 +33,10 @@ class DioRemoteManager {
|
||||
Future<void> switchAuthEnvironment(ApiEnvironment env) async {
|
||||
final manager = diAuth.get<DioRemoteManager>();
|
||||
|
||||
final dioRemote = manager.setEnvironment(env);
|
||||
final dioRemote = await manager.setEnvironment(env);
|
||||
|
||||
if (diAuth.isRegistered<AuthRepositoryImpl>()) {
|
||||
await diAuth.unregister<AuthRepositoryImpl>();
|
||||
await diAuth.unregister<AuthRepositoryImpl>();
|
||||
}
|
||||
|
||||
diAuth.registerLazySingleton<AuthRepositoryImpl>(
|
||||
|
||||
@@ -12,14 +12,10 @@ Future<void> setupAuthDI() async {
|
||||
diAuth.registerLazySingleton(() => DioRemoteManager());
|
||||
|
||||
final manager = diAuth.get<DioRemoteManager>();
|
||||
final dioRemote = manager.setEnvironment(ApiEnvironment.dam);
|
||||
|
||||
diAuth.registerLazySingleton<AuthRepositoryImpl>(
|
||||
final dioRemote = await manager.setEnvironment(ApiEnvironment.dam);
|
||||
diAuth.registerCachedFactory<AuthRepositoryImpl>(
|
||||
() => AuthRepositoryImpl(dioRemote),
|
||||
);
|
||||
diAuth.registerLazySingleton(() => AuthService());
|
||||
diAuth.registerLazySingleton(() => TokenStorageService());
|
||||
|
||||
//hive
|
||||
//await diAuth.registerCachedFactoryAsync(() async=>await ,)
|
||||
}
|
||||
|
||||
@@ -34,17 +34,18 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
|
||||
@override
|
||||
Future<CaptchaResponseModel?> captcha() async {
|
||||
final response = await safeCall<DioResponse<CaptchaResponseModel>>(
|
||||
final response = await safeCall(
|
||||
call:
|
||||
() async => await _httpClient.post<CaptchaResponseModel>(
|
||||
'$_BASE_URL/login/',
|
||||
'captcha/',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
fromJson: CaptchaResponseModel.fromJson
|
||||
),
|
||||
onSuccess: (response) {
|
||||
iLog(response);
|
||||
},
|
||||
onError: (error, trace) {
|
||||
throw Exception('Error during sign in: $error');
|
||||
throw Exception('Error during captcha : $error');
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_auth/auth.dart';
|
||||
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
enum AuthType { useAndPass, otp }
|
||||
@@ -18,8 +20,7 @@ class AuthLogic extends GetxController {
|
||||
Rx<TextEditingController> phoneOtpNumberController =
|
||||
TextEditingController().obs;
|
||||
Rx<TextEditingController> otpCodeController = TextEditingController().obs;
|
||||
CaptchaController captchaController = CaptchaController();
|
||||
CaptchaController captchaOtpController = CaptchaController();
|
||||
|
||||
|
||||
RxnString phoneNumber = RxnString(null);
|
||||
RxnString password = RxnString(null);
|
||||
@@ -32,6 +33,8 @@ class AuthLogic extends GetxController {
|
||||
RxInt secondsRemaining = 120.obs;
|
||||
Timer? _timer;
|
||||
|
||||
AuthRepositoryImpl authRepository = diAuth.get<AuthRepositoryImpl>();
|
||||
|
||||
void startTimer() {
|
||||
_timer?.cancel();
|
||||
secondsRemaining.value = 120;
|
||||
@@ -55,6 +58,12 @@ class AuthLogic extends GetxController {
|
||||
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
// TODO: implement onReady
|
||||
@@ -66,4 +75,6 @@ class AuthLogic extends GetxController {
|
||||
_timer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_auth/presentation/widget/captcha/view.dart';
|
||||
import 'package:rasadyar_auth/presentation/widget/clear_button.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
import 'logic.dart';
|
||||
|
||||
|
||||
|
||||
class AuthPage extends GetView<AuthLogic> {
|
||||
const AuthPage({super.key});
|
||||
|
||||
@@ -21,7 +21,7 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
ObxValue((types) {
|
||||
switch (types.value) {
|
||||
case AuthType.otp:
|
||||
return otpForm();
|
||||
//return otpForm();
|
||||
case AuthType.useAndPass:
|
||||
return useAndPassFrom();
|
||||
}
|
||||
@@ -222,7 +222,7 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
}, controller.passwordController),
|
||||
SizedBox(height: 26),
|
||||
|
||||
CaptchaWidget(controller: controller.captchaController),
|
||||
CaptchaWidget(),
|
||||
|
||||
SizedBox(height: 23),
|
||||
RElevated(
|
||||
@@ -237,9 +237,6 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
initialEntryMode: PersianDatePickerEntryMode.calendarOnly,
|
||||
initialDatePickerMode: PersianDatePickerMode.year,
|
||||
);
|
||||
|
||||
if (data.value.currentState?.validate() == true &&
|
||||
controller.captchaController.validate()) {}
|
||||
},
|
||||
width: Get.width,
|
||||
height: 48,
|
||||
@@ -251,7 +248,7 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
}, controller.formKey);
|
||||
}
|
||||
|
||||
Widget otpForm() {
|
||||
/* Widget otpForm() {
|
||||
return ObxValue((status) {
|
||||
switch (status.value) {
|
||||
case OtpStatus.init:
|
||||
@@ -262,7 +259,7 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
return confirmCodeForm();
|
||||
}
|
||||
}, controller.otpStatus);
|
||||
}
|
||||
}*/
|
||||
|
||||
Widget sendCodeForm() {
|
||||
return ObxValue((data) {
|
||||
@@ -335,14 +332,13 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
|
||||
SizedBox(height: 26),
|
||||
|
||||
CaptchaWidget(controller: controller.captchaOtpController),
|
||||
CaptchaWidget(),
|
||||
|
||||
SizedBox(height: 23),
|
||||
RElevated(
|
||||
text: 'ارسال رمز یکبار مصرف',
|
||||
onPressed: () {
|
||||
if (data.value.currentState?.validate() == true &&
|
||||
controller.captchaOtpController.validate()) {
|
||||
if (data.value.currentState?.validate() == true) {
|
||||
controller.otpStatus.value = OtpStatus.sent;
|
||||
controller.startTimer();
|
||||
}
|
||||
@@ -473,7 +469,6 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
controller.otpStatus.value = OtpStatus.init;
|
||||
controller.captchaOtpController.clear();
|
||||
},
|
||||
text: ' ویرایش',
|
||||
style: AppFonts.yekan14.copyWith(
|
||||
@@ -489,8 +484,7 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
text: 'ورود',
|
||||
onPressed: () {
|
||||
if (controller.formKeyOtp.value.currentState?.validate() ==
|
||||
true &&
|
||||
controller.captchaOtpController.validate()) {}
|
||||
true) {}
|
||||
},
|
||||
width: Get.width,
|
||||
height: 48,
|
||||
@@ -514,11 +508,4 @@ class AuthPage extends GetView<AuthLogic> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget clearButton(VoidCallback onTap) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Icon(CupertinoIcons.multiply_circle, size: 24),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
8
packages/auth/lib/presentation/widget/clear_button.dart
Normal file
8
packages/auth/lib/presentation/widget/clear_button.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
Widget clearButton(VoidCallback onTap) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Icon(CupertinoIcons.multiply_circle, size: 24),
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||
import 'package:rasadyar_core/infrastructure/remote/interfaces/i_form_data.dart';
|
||||
|
||||
@@ -15,7 +16,9 @@ class DioRemote implements IHttpClient {
|
||||
@override
|
||||
Future<void> init() async {
|
||||
final dio = Dio(BaseOptions(baseUrl: baseUrl));
|
||||
dio.interceptors.add(PrettyDioLogger());
|
||||
if (kDebugMode) {
|
||||
dio.interceptors.add(PrettyDioLogger());
|
||||
}
|
||||
_dio = dio;
|
||||
}
|
||||
|
||||
@@ -40,6 +43,7 @@ class DioRemote implements IHttpClient {
|
||||
String path, {
|
||||
dynamic data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
T Function(Map<String, dynamic> json)? fromJson,
|
||||
Map<String, String>? headers,
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
@@ -52,6 +56,15 @@ class DioRemote implements IHttpClient {
|
||||
onSendProgress: onSendProgress,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
);
|
||||
|
||||
if (fromJson != null) {
|
||||
final rawData = response.data;
|
||||
final parsedData =
|
||||
rawData is Map<String, dynamic> ? fromJson(rawData) : null;
|
||||
response.data = parsedData;
|
||||
return DioResponse<T>(response);
|
||||
}
|
||||
|
||||
return DioResponse<T>(response);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/presentation/common/app_color.dart';
|
||||
import 'package:rasadyar_core/presentation/common/app_fonts.dart';
|
||||
|
||||
class CaptchaController {
|
||||
int? captchaCode;
|
||||
TextEditingController textController = TextEditingController();
|
||||
GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
late Function() refreshCaptcha;
|
||||
|
||||
bool validate() {
|
||||
if (formKey.currentState?.validate() == true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String get enteredText => textController.text;
|
||||
|
||||
bool isCorrect() {
|
||||
return textController.text == captchaCode.toString();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
textController.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class RandomLinePainter extends CustomPainter {
|
||||
final Random random = Random();
|
||||
final Paint linePaint;
|
||||
final List<Offset> points;
|
||||
|
||||
RandomLinePainter({
|
||||
required this.points,
|
||||
required Color lineColor,
|
||||
double strokeWidth = 2.0,
|
||||
}) : linePaint =
|
||||
Paint()
|
||||
..color = lineColor
|
||||
..strokeWidth = strokeWidth
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final path = Path();
|
||||
|
||||
if (points.isNotEmpty) {
|
||||
path.moveTo(points[0].dx, points[0].dy);
|
||||
|
||||
for (int i = 1; i < points.length; i++) {
|
||||
path.lineTo(points[i].dx, points[i].dy);
|
||||
}
|
||||
|
||||
canvas.drawPath(path, linePaint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(RandomLinePainter oldDelegate) => true;
|
||||
}
|
||||
|
||||
class CaptchaWidget extends StatefulWidget {
|
||||
final CaptchaController controller;
|
||||
final bool autoValidateMode;
|
||||
|
||||
const CaptchaWidget({
|
||||
required this.controller,
|
||||
this.autoValidateMode = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
_CaptchaWidgetState createState() => _CaptchaWidgetState();
|
||||
}
|
||||
|
||||
class _CaptchaWidgetState extends State<CaptchaWidget> {
|
||||
late List<Offset> points;
|
||||
late List<Offset> points1;
|
||||
late List<Offset> points2;
|
||||
bool isOnError = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
generateLines();
|
||||
getRandomSixDigitNumber();
|
||||
|
||||
// Set the refresh function in the controller
|
||||
widget.controller.refreshCaptcha = () {
|
||||
getRandomSixDigitNumber();
|
||||
generateLines();
|
||||
setState(() {});
|
||||
};
|
||||
}
|
||||
|
||||
void generateLines() {
|
||||
points = generateRandomLine();
|
||||
points1 = generateRandomLine();
|
||||
points2 = generateRandomLine();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
List<Offset> generateRandomLine() {
|
||||
final random = Random();
|
||||
int pointCount = random.nextInt(10) + 5;
|
||||
List<Offset> points = [];
|
||||
|
||||
double previousY = 0;
|
||||
|
||||
for (int i = 0; i < pointCount; i++) {
|
||||
double x = (i / (pointCount - 1)) * 135;
|
||||
|
||||
if (i == 0) {
|
||||
previousY = 24;
|
||||
} else {
|
||||
double change = (random.nextDouble() * 20) - 10;
|
||||
previousY = max(5, min(43, previousY + change));
|
||||
}
|
||||
|
||||
points.add(Offset(x, previousY));
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
void getRandomSixDigitNumber() {
|
||||
final random = Random();
|
||||
widget.controller.captchaCode = random.nextInt(900000) + 100000;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 135,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.whiteNormalHover,
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
CustomPaint(
|
||||
painter: RandomLinePainter(
|
||||
points: points,
|
||||
lineColor: Colors.blue,
|
||||
strokeWidth: 1.0,
|
||||
),
|
||||
size: const Size(double.infinity, double.infinity),
|
||||
),
|
||||
CustomPaint(
|
||||
painter: RandomLinePainter(
|
||||
points: points1,
|
||||
lineColor: Colors.green,
|
||||
strokeWidth: 1.0,
|
||||
),
|
||||
size: const Size(double.infinity, double.infinity),
|
||||
),
|
||||
CustomPaint(
|
||||
painter: RandomLinePainter(
|
||||
points: points2,
|
||||
lineColor: Colors.red,
|
||||
strokeWidth: 1.0,
|
||||
),
|
||||
size: const Size(double.infinity, double.infinity),
|
||||
),
|
||||
Text(
|
||||
widget.controller.captchaCode.toString(),
|
||||
style: AppFonts.yekan24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: widget.controller.refreshCaptcha,
|
||||
icon: Icon(CupertinoIcons.refresh, size: 16),
|
||||
),
|
||||
Expanded(
|
||||
child: Form(
|
||||
key: widget.controller.formKey,
|
||||
autovalidateMode:
|
||||
widget.autoValidateMode
|
||||
? AutovalidateMode.onUserInteraction
|
||||
: AutovalidateMode.disabled,
|
||||
child: TextFormField(
|
||||
controller: widget.controller.textController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
gapPadding: 11,
|
||||
),
|
||||
labelText: 'کد امنیتی',
|
||||
labelStyle: AppFonts.yekan13,
|
||||
errorStyle: AppFonts.yekan10.copyWith(
|
||||
color: AppColor.redNormal,
|
||||
fontSize: 8,
|
||||
),
|
||||
suffixIconConstraints: BoxConstraints(
|
||||
maxHeight: 24,
|
||||
minHeight: 24,
|
||||
maxWidth: 24,
|
||||
minWidth: 24,
|
||||
),
|
||||
|
||||
suffix:
|
||||
widget.controller.textController.text.trim().isNotEmpty
|
||||
? clearButton(() {
|
||||
widget.controller.textController.clear();
|
||||
setState(() {});
|
||||
})
|
||||
: null,
|
||||
counterText: '',
|
||||
),
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
decimal: false,
|
||||
signed: false,
|
||||
),
|
||||
maxLines: 1,
|
||||
maxLength: 6,
|
||||
onChanged: (value) {
|
||||
if (isOnError) {
|
||||
isOnError = !isOnError;
|
||||
widget.controller.formKey.currentState?.reset();
|
||||
widget.controller.textController.text = value;
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
isOnError = true;
|
||||
return 'کد امنیتی را وارد کنید';
|
||||
}
|
||||
if (value != widget.controller.captchaCode.toString()) {
|
||||
isOnError = true;
|
||||
return '⚠️کد امنیتی وارد شده اشتباه است';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
style: AppFonts.yekan13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget clearButton(VoidCallback onTap) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Icon(CupertinoIcons.multiply_circle, size: 18),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ export 'buttons/elevated.dart';
|
||||
export 'buttons/outline_elevated.dart';
|
||||
export 'buttons/outline_elevated_icon.dart';
|
||||
export 'buttons/text_button.dart';
|
||||
export 'captcha/captcha_widget.dart';
|
||||
export 'draggable_bottom_sheet/draggable_bottom_sheet.dart';
|
||||
export 'draggable_bottom_sheet/draggable_bottom_sheet_controller.dart';
|
||||
export 'draggable_bottom_sheet/bottom_sheet_manger.dart';
|
||||
|
||||
Reference in New Issue
Block a user