feat : auth live stock

This commit is contained in:
2025-08-03 11:55:57 +03:30
parent 2800634cab
commit 223ccbd807
29 changed files with 3833 additions and 19 deletions

View File

@@ -4,6 +4,8 @@ import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_inspection/injection/inspection_di.dart'; import 'package:rasadyar_inspection/injection/inspection_di.dart';
import 'package:rasadyar_inspection/inspection.dart'; import 'package:rasadyar_inspection/inspection.dart';
import 'package:rasadyar_livestock/injection/live_stock_di.dart';
import 'package:rasadyar_livestock/presentation/routes/app_pages.dart';
class CustomNavigationObserver extends NavigatorObserver { class CustomNavigationObserver extends NavigatorObserver {
bool _isWorkDone = false; bool _isWorkDone = false;
@@ -16,7 +18,7 @@ class CustomNavigationObserver extends NavigatorObserver {
void didPush(Route route, Route? previousRoute) async { void didPush(Route route, Route? previousRoute) async {
super.didPush(route, previousRoute); super.didPush(route, previousRoute);
final routeName = route.settings.name; final routeName = route.settings.name;
if (!_isWorkDone &&( routeName == ChickenRoutes.init || routeName == ChickenRoutes.auth)) { if (!_isWorkDone && (routeName == ChickenRoutes.init || routeName == ChickenRoutes.auth)) {
_isWorkDone = true; _isWorkDone = true;
await setupChickenDI(); await setupChickenDI();
} else if (!_isWorkDone && } else if (!_isWorkDone &&
@@ -24,6 +26,10 @@ class CustomNavigationObserver extends NavigatorObserver {
_isWorkDone = true; _isWorkDone = true;
await setupInspectionDI(); await setupInspectionDI();
} else if (!_isWorkDone &&
(routeName == LiveStockRoutes.init || routeName == LiveStockRoutes.auth)) {
_isWorkDone = true;
await setupLiveStockDI();
} }
tLog('CustomNavigationObserver: didPush - $routeName'); tLog('CustomNavigationObserver: didPush - $routeName');
} }

View File

@@ -5,7 +5,7 @@ class ModulesLogic extends GetxController {
List<ModuleModel> moduleList = [ List<ModuleModel> moduleList = [
ModuleModel(title: 'بازرسی', icon: Assets.icons.inspection.path, module: Module.inspection), ModuleModel(title: 'بازرسی', icon: Assets.icons.inspection.path, module: Module.inspection),
// ModuleModel(title: 'دام', icon: Assets.icons.liveStock.path, module: Module.liveStocks), ModuleModel(title: 'دام', icon: Assets.icons.liveStock.path, module: Module.liveStocks),
ModuleModel(title: 'مرغ', icon: Assets.icons.liveStock.path, module: Module.chicken), ModuleModel(title: 'مرغ', icon: Assets.icons.liveStock.path, module: Module.chicken),
]; ];

View File

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
class DioErrorHandler {
void handle(DioException error) {
switch (error.response?.statusCode) {
case 401:
_handleGeneric(error);
break;
case 403:
_handleGeneric(error);
break;
case 410:
_handle410();
break;
default:
_handleGeneric(error);
}
}
//wrong password/user name => "detail": "No active account found with the given credentials" - 401
void _handle410() {
Get.showSnackbar(_errorSnackBar('نام کاربری یا رمز عبور اشتباه است'));
}
//wrong captcha => "detail": "Captcha code is incorrect" - 403
void _handle403() {}
void _handleGeneric(DioException error) {
Get.showSnackbar(
_errorSnackBar(
error.response?.data.keys.first == 'is_user'
? 'کاربر با این شماره تلفن وجود ندارد'
: error.response?.data[error.response?.data.keys.first] ??
'خطا در برقراری ارتباط با سرور',
),
);
}
GetSnackBar _errorSnackBar(String message) {
return GetSnackBar(
titleText: Text(
'خطا',
style: AppFonts.yekan14.copyWith(color: Colors.white),
),
messageText: Text(
message,
style: AppFonts.yekan12.copyWith(color: Colors.white),
),
backgroundColor: AppColor.error,
margin: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: 12,
duration: Duration(milliseconds: 3500),
snackPosition: SnackPosition.TOP,
);
}
}

View File

@@ -0,0 +1,16 @@
import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart';
abstract class AuthRemoteDataSource {
Future<AuthResponseModel?> login({required Map<String, dynamic> authRequest});
Future<CaptchaResponseModel?> captcha();
Future<void> logout();
Future<bool> hasAuthenticated();
Future<AuthResponseModel?> loginWithRefreshToken({required Map<String, dynamic> authRequest});
}

View File

@@ -0,0 +1,71 @@
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart';
import 'auth_remote.dart';
class AuthRemoteDataSourceImp extends AuthRemoteDataSource {
final DioRemote _httpClient;
final String _BASE_URL = 'auth/api/v1/';
AuthRemoteDataSourceImp(this._httpClient);
@override
Future<AuthResponseModel?> login({required Map<String, dynamic> authRequest}) async {
var res = await _httpClient.post<AuthResponseModel?>(
'${_BASE_URL}login/',
data: authRequest,
fromJson: AuthResponseModel.fromJson,
headers: {'Content-Type': 'application/json'},
);
return res.data;
}
@override
Future<CaptchaResponseModel?> captcha() async {
var res = await _httpClient.post<CaptchaResponseModel?>(
'captcha/',
fromJson: CaptchaResponseModel.fromJson,
);
return res.data;
}
@override
Future<AuthResponseModel?> loginWithRefreshToken({
required Map<String, dynamic> authRequest,
}) async {
var res = await _httpClient.post<AuthResponseModel>(
'$_BASE_URL/login/',
data: authRequest,
headers: {'Content-Type': 'application/json'},
);
return res.data;
}
@override
Future<void> logout() {
// TODO: implement logout
throw UnimplementedError();
}
@override
Future<bool> hasAuthenticated() async {
final response = await _httpClient.get<bool>(
'$_BASE_URL/login/',
headers: {'Content-Type': 'application/json'},
);
return response.data ?? false;
}
/* @override
Future<UserProfileModel?> getUserInfo(String phoneNumber) async {
var res = await _httpClient.post<UserProfileModel?>(
'https://userbackend.rasadyaar.ir/api/send_otp/',
data: {"mobile": phoneNumber, "state": ""},
fromJson: UserProfileModel.fromJson,
headers: {'Content-Type': 'application/json'},
);
return res.data;
}*/
}

View File

@@ -0,0 +1,34 @@
import 'package:rasadyar_core/core.dart';
part 'login_request_model.freezed.dart';
part 'login_request_model.g.dart';
@freezed
abstract class LoginRequestModel with _$LoginRequestModel {
const factory LoginRequestModel({
String? username,
String? password,
String? captchaCode,
String? captchaKey,
}) = _LoginRequestModel;
factory LoginRequestModel.createWithCaptcha({
required String username,
required String password,
required String captchaCode,
required String captchaKey,
}) {
return LoginRequestModel(
username: username,
password: password,
captchaCode: captchaCode,
captchaKey: 'rest_captcha_$captchaKey.0',
);
}
factory LoginRequestModel.fromJson(Map<String, dynamic> json) =>
_$LoginRequestModelFromJson(json);
const LoginRequestModel._();
}

View File

@@ -0,0 +1,286 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'login_request_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LoginRequestModel {
String? get username; String? get password; String? get captchaCode; String? get captchaKey;
/// Create a copy of LoginRequestModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LoginRequestModelCopyWith<LoginRequestModel> get copyWith => _$LoginRequestModelCopyWithImpl<LoginRequestModel>(this as LoginRequestModel, _$identity);
/// Serializes this LoginRequestModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LoginRequestModel&&(identical(other.username, username) || other.username == username)&&(identical(other.password, password) || other.password == password)&&(identical(other.captchaCode, captchaCode) || other.captchaCode == captchaCode)&&(identical(other.captchaKey, captchaKey) || other.captchaKey == captchaKey));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,username,password,captchaCode,captchaKey);
@override
String toString() {
return 'LoginRequestModel(username: $username, password: $password, captchaCode: $captchaCode, captchaKey: $captchaKey)';
}
}
/// @nodoc
abstract mixin class $LoginRequestModelCopyWith<$Res> {
factory $LoginRequestModelCopyWith(LoginRequestModel value, $Res Function(LoginRequestModel) _then) = _$LoginRequestModelCopyWithImpl;
@useResult
$Res call({
String? username, String? password, String? captchaCode, String? captchaKey
});
}
/// @nodoc
class _$LoginRequestModelCopyWithImpl<$Res>
implements $LoginRequestModelCopyWith<$Res> {
_$LoginRequestModelCopyWithImpl(this._self, this._then);
final LoginRequestModel _self;
final $Res Function(LoginRequestModel) _then;
/// Create a copy of LoginRequestModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? username = freezed,Object? password = freezed,Object? captchaCode = freezed,Object? captchaKey = freezed,}) {
return _then(_self.copyWith(
username: freezed == username ? _self.username : username // ignore: cast_nullable_to_non_nullable
as String?,password: freezed == password ? _self.password : password // ignore: cast_nullable_to_non_nullable
as String?,captchaCode: freezed == captchaCode ? _self.captchaCode : captchaCode // ignore: cast_nullable_to_non_nullable
as String?,captchaKey: freezed == captchaKey ? _self.captchaKey : captchaKey // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [LoginRequestModel].
extension LoginRequestModelPatterns on LoginRequestModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LoginRequestModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LoginRequestModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LoginRequestModel value) $default,){
final _that = this;
switch (_that) {
case _LoginRequestModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LoginRequestModel value)? $default,){
final _that = this;
switch (_that) {
case _LoginRequestModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? username, String? password, String? captchaCode, String? captchaKey)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LoginRequestModel() when $default != null:
return $default(_that.username,_that.password,_that.captchaCode,_that.captchaKey);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? username, String? password, String? captchaCode, String? captchaKey) $default,) {final _that = this;
switch (_that) {
case _LoginRequestModel():
return $default(_that.username,_that.password,_that.captchaCode,_that.captchaKey);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? username, String? password, String? captchaCode, String? captchaKey)? $default,) {final _that = this;
switch (_that) {
case _LoginRequestModel() when $default != null:
return $default(_that.username,_that.password,_that.captchaCode,_that.captchaKey);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LoginRequestModel extends LoginRequestModel {
const _LoginRequestModel({this.username, this.password, this.captchaCode, this.captchaKey}): super._();
factory _LoginRequestModel.fromJson(Map<String, dynamic> json) => _$LoginRequestModelFromJson(json);
@override final String? username;
@override final String? password;
@override final String? captchaCode;
@override final String? captchaKey;
/// Create a copy of LoginRequestModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LoginRequestModelCopyWith<_LoginRequestModel> get copyWith => __$LoginRequestModelCopyWithImpl<_LoginRequestModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LoginRequestModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LoginRequestModel&&(identical(other.username, username) || other.username == username)&&(identical(other.password, password) || other.password == password)&&(identical(other.captchaCode, captchaCode) || other.captchaCode == captchaCode)&&(identical(other.captchaKey, captchaKey) || other.captchaKey == captchaKey));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,username,password,captchaCode,captchaKey);
@override
String toString() {
return 'LoginRequestModel(username: $username, password: $password, captchaCode: $captchaCode, captchaKey: $captchaKey)';
}
}
/// @nodoc
abstract mixin class _$LoginRequestModelCopyWith<$Res> implements $LoginRequestModelCopyWith<$Res> {
factory _$LoginRequestModelCopyWith(_LoginRequestModel value, $Res Function(_LoginRequestModel) _then) = __$LoginRequestModelCopyWithImpl;
@override @useResult
$Res call({
String? username, String? password, String? captchaCode, String? captchaKey
});
}
/// @nodoc
class __$LoginRequestModelCopyWithImpl<$Res>
implements _$LoginRequestModelCopyWith<$Res> {
__$LoginRequestModelCopyWithImpl(this._self, this._then);
final _LoginRequestModel _self;
final $Res Function(_LoginRequestModel) _then;
/// Create a copy of LoginRequestModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? username = freezed,Object? password = freezed,Object? captchaCode = freezed,Object? captchaKey = freezed,}) {
return _then(_LoginRequestModel(
username: freezed == username ? _self.username : username // ignore: cast_nullable_to_non_nullable
as String?,password: freezed == password ? _self.password : password // ignore: cast_nullable_to_non_nullable
as String?,captchaCode: freezed == captchaCode ? _self.captchaCode : captchaCode // ignore: cast_nullable_to_non_nullable
as String?,captchaKey: freezed == captchaKey ? _self.captchaKey : captchaKey // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'login_request_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_LoginRequestModel _$LoginRequestModelFromJson(Map<String, dynamic> json) =>
_LoginRequestModel(
username: json['username'] as String?,
password: json['password'] as String?,
captchaCode: json['captcha_code'] as String?,
captchaKey: json['captcha_key'] as String?,
);
Map<String, dynamic> _$LoginRequestModelToJson(_LoginRequestModel instance) =>
<String, dynamic>{
'username': instance.username,
'password': instance.password,
'captcha_code': instance.captchaCode,
'captcha_key': instance.captchaKey,
};

View File

@@ -0,0 +1,20 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_response_model.freezed.dart';
part 'auth_response_model.g.dart';
@freezed
abstract class AuthResponseModel with _$AuthResponseModel {
const factory AuthResponseModel({
String? refresh,
String? access,
bool? otpStatus,
}) = _AuthResponseModel;
factory AuthResponseModel.fromJson(Map<String, dynamic> json) =>
_$AuthResponseModelFromJson(json);
}

View File

@@ -0,0 +1,283 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'auth_response_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AuthResponseModel {
String? get refresh; String? get access; bool? get otpStatus;
/// Create a copy of AuthResponseModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$AuthResponseModelCopyWith<AuthResponseModel> get copyWith => _$AuthResponseModelCopyWithImpl<AuthResponseModel>(this as AuthResponseModel, _$identity);
/// Serializes this AuthResponseModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthResponseModel&&(identical(other.refresh, refresh) || other.refresh == refresh)&&(identical(other.access, access) || other.access == access)&&(identical(other.otpStatus, otpStatus) || other.otpStatus == otpStatus));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,refresh,access,otpStatus);
@override
String toString() {
return 'AuthResponseModel(refresh: $refresh, access: $access, otpStatus: $otpStatus)';
}
}
/// @nodoc
abstract mixin class $AuthResponseModelCopyWith<$Res> {
factory $AuthResponseModelCopyWith(AuthResponseModel value, $Res Function(AuthResponseModel) _then) = _$AuthResponseModelCopyWithImpl;
@useResult
$Res call({
String? refresh, String? access, bool? otpStatus
});
}
/// @nodoc
class _$AuthResponseModelCopyWithImpl<$Res>
implements $AuthResponseModelCopyWith<$Res> {
_$AuthResponseModelCopyWithImpl(this._self, this._then);
final AuthResponseModel _self;
final $Res Function(AuthResponseModel) _then;
/// Create a copy of AuthResponseModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? refresh = freezed,Object? access = freezed,Object? otpStatus = freezed,}) {
return _then(_self.copyWith(
refresh: freezed == refresh ? _self.refresh : refresh // ignore: cast_nullable_to_non_nullable
as String?,access: freezed == access ? _self.access : access // ignore: cast_nullable_to_non_nullable
as String?,otpStatus: freezed == otpStatus ? _self.otpStatus : otpStatus // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
/// Adds pattern-matching-related methods to [AuthResponseModel].
extension AuthResponseModelPatterns on AuthResponseModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _AuthResponseModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _AuthResponseModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _AuthResponseModel value) $default,){
final _that = this;
switch (_that) {
case _AuthResponseModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _AuthResponseModel value)? $default,){
final _that = this;
switch (_that) {
case _AuthResponseModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? refresh, String? access, bool? otpStatus)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AuthResponseModel() when $default != null:
return $default(_that.refresh,_that.access,_that.otpStatus);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? refresh, String? access, bool? otpStatus) $default,) {final _that = this;
switch (_that) {
case _AuthResponseModel():
return $default(_that.refresh,_that.access,_that.otpStatus);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? refresh, String? access, bool? otpStatus)? $default,) {final _that = this;
switch (_that) {
case _AuthResponseModel() when $default != null:
return $default(_that.refresh,_that.access,_that.otpStatus);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _AuthResponseModel implements AuthResponseModel {
const _AuthResponseModel({this.refresh, this.access, this.otpStatus});
factory _AuthResponseModel.fromJson(Map<String, dynamic> json) => _$AuthResponseModelFromJson(json);
@override final String? refresh;
@override final String? access;
@override final bool? otpStatus;
/// Create a copy of AuthResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$AuthResponseModelCopyWith<_AuthResponseModel> get copyWith => __$AuthResponseModelCopyWithImpl<_AuthResponseModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$AuthResponseModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthResponseModel&&(identical(other.refresh, refresh) || other.refresh == refresh)&&(identical(other.access, access) || other.access == access)&&(identical(other.otpStatus, otpStatus) || other.otpStatus == otpStatus));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,refresh,access,otpStatus);
@override
String toString() {
return 'AuthResponseModel(refresh: $refresh, access: $access, otpStatus: $otpStatus)';
}
}
/// @nodoc
abstract mixin class _$AuthResponseModelCopyWith<$Res> implements $AuthResponseModelCopyWith<$Res> {
factory _$AuthResponseModelCopyWith(_AuthResponseModel value, $Res Function(_AuthResponseModel) _then) = __$AuthResponseModelCopyWithImpl;
@override @useResult
$Res call({
String? refresh, String? access, bool? otpStatus
});
}
/// @nodoc
class __$AuthResponseModelCopyWithImpl<$Res>
implements _$AuthResponseModelCopyWith<$Res> {
__$AuthResponseModelCopyWithImpl(this._self, this._then);
final _AuthResponseModel _self;
final $Res Function(_AuthResponseModel) _then;
/// Create a copy of AuthResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? refresh = freezed,Object? access = freezed,Object? otpStatus = freezed,}) {
return _then(_AuthResponseModel(
refresh: freezed == refresh ? _self.refresh : refresh // ignore: cast_nullable_to_non_nullable
as String?,access: freezed == access ? _self.access : access // ignore: cast_nullable_to_non_nullable
as String?,otpStatus: freezed == otpStatus ? _self.otpStatus : otpStatus // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
// dart format on

View File

@@ -0,0 +1,21 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_AuthResponseModel _$AuthResponseModelFromJson(Map<String, dynamic> json) =>
_AuthResponseModel(
refresh: json['refresh'] as String?,
access: json['access'] as String?,
otpStatus: json['otp_status'] as bool?,
);
Map<String, dynamic> _$AuthResponseModelToJson(_AuthResponseModel instance) =>
<String, dynamic>{
'refresh': instance.refresh,
'access': instance.access,
'otp_status': instance.otpStatus,
};

View File

@@ -0,0 +1,17 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'captcha_response_model.freezed.dart';
part 'captcha_response_model.g.dart';
@freezed
abstract class CaptchaResponseModel with _$CaptchaResponseModel {
const factory CaptchaResponseModel({
String? captchaKey,
String? captchaImage,
String? imageType,
String? imageDecode,
}) = _CaptchaResponseModel;
factory CaptchaResponseModel.fromJson(Map<String, dynamic> json) =>
_$CaptchaResponseModelFromJson(json);
}

View File

@@ -0,0 +1,286 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'captcha_response_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$CaptchaResponseModel {
String? get captchaKey; String? get captchaImage; String? get imageType; String? get imageDecode;
/// Create a copy of CaptchaResponseModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$CaptchaResponseModelCopyWith<CaptchaResponseModel> get copyWith => _$CaptchaResponseModelCopyWithImpl<CaptchaResponseModel>(this as CaptchaResponseModel, _$identity);
/// Serializes this CaptchaResponseModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CaptchaResponseModel&&(identical(other.captchaKey, captchaKey) || other.captchaKey == captchaKey)&&(identical(other.captchaImage, captchaImage) || other.captchaImage == captchaImage)&&(identical(other.imageType, imageType) || other.imageType == imageType)&&(identical(other.imageDecode, imageDecode) || other.imageDecode == imageDecode));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,captchaKey,captchaImage,imageType,imageDecode);
@override
String toString() {
return 'CaptchaResponseModel(captchaKey: $captchaKey, captchaImage: $captchaImage, imageType: $imageType, imageDecode: $imageDecode)';
}
}
/// @nodoc
abstract mixin class $CaptchaResponseModelCopyWith<$Res> {
factory $CaptchaResponseModelCopyWith(CaptchaResponseModel value, $Res Function(CaptchaResponseModel) _then) = _$CaptchaResponseModelCopyWithImpl;
@useResult
$Res call({
String? captchaKey, String? captchaImage, String? imageType, String? imageDecode
});
}
/// @nodoc
class _$CaptchaResponseModelCopyWithImpl<$Res>
implements $CaptchaResponseModelCopyWith<$Res> {
_$CaptchaResponseModelCopyWithImpl(this._self, this._then);
final CaptchaResponseModel _self;
final $Res Function(CaptchaResponseModel) _then;
/// Create a copy of CaptchaResponseModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? captchaKey = freezed,Object? captchaImage = freezed,Object? imageType = freezed,Object? imageDecode = freezed,}) {
return _then(_self.copyWith(
captchaKey: freezed == captchaKey ? _self.captchaKey : captchaKey // ignore: cast_nullable_to_non_nullable
as String?,captchaImage: freezed == captchaImage ? _self.captchaImage : captchaImage // ignore: cast_nullable_to_non_nullable
as String?,imageType: freezed == imageType ? _self.imageType : imageType // ignore: cast_nullable_to_non_nullable
as String?,imageDecode: freezed == imageDecode ? _self.imageDecode : imageDecode // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [CaptchaResponseModel].
extension CaptchaResponseModelPatterns on CaptchaResponseModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _CaptchaResponseModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _CaptchaResponseModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _CaptchaResponseModel value) $default,){
final _that = this;
switch (_that) {
case _CaptchaResponseModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _CaptchaResponseModel value)? $default,){
final _that = this;
switch (_that) {
case _CaptchaResponseModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? captchaKey, String? captchaImage, String? imageType, String? imageDecode)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _CaptchaResponseModel() when $default != null:
return $default(_that.captchaKey,_that.captchaImage,_that.imageType,_that.imageDecode);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? captchaKey, String? captchaImage, String? imageType, String? imageDecode) $default,) {final _that = this;
switch (_that) {
case _CaptchaResponseModel():
return $default(_that.captchaKey,_that.captchaImage,_that.imageType,_that.imageDecode);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? captchaKey, String? captchaImage, String? imageType, String? imageDecode)? $default,) {final _that = this;
switch (_that) {
case _CaptchaResponseModel() when $default != null:
return $default(_that.captchaKey,_that.captchaImage,_that.imageType,_that.imageDecode);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _CaptchaResponseModel implements CaptchaResponseModel {
const _CaptchaResponseModel({this.captchaKey, this.captchaImage, this.imageType, this.imageDecode});
factory _CaptchaResponseModel.fromJson(Map<String, dynamic> json) => _$CaptchaResponseModelFromJson(json);
@override final String? captchaKey;
@override final String? captchaImage;
@override final String? imageType;
@override final String? imageDecode;
/// Create a copy of CaptchaResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$CaptchaResponseModelCopyWith<_CaptchaResponseModel> get copyWith => __$CaptchaResponseModelCopyWithImpl<_CaptchaResponseModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$CaptchaResponseModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CaptchaResponseModel&&(identical(other.captchaKey, captchaKey) || other.captchaKey == captchaKey)&&(identical(other.captchaImage, captchaImage) || other.captchaImage == captchaImage)&&(identical(other.imageType, imageType) || other.imageType == imageType)&&(identical(other.imageDecode, imageDecode) || other.imageDecode == imageDecode));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,captchaKey,captchaImage,imageType,imageDecode);
@override
String toString() {
return 'CaptchaResponseModel(captchaKey: $captchaKey, captchaImage: $captchaImage, imageType: $imageType, imageDecode: $imageDecode)';
}
}
/// @nodoc
abstract mixin class _$CaptchaResponseModelCopyWith<$Res> implements $CaptchaResponseModelCopyWith<$Res> {
factory _$CaptchaResponseModelCopyWith(_CaptchaResponseModel value, $Res Function(_CaptchaResponseModel) _then) = __$CaptchaResponseModelCopyWithImpl;
@override @useResult
$Res call({
String? captchaKey, String? captchaImage, String? imageType, String? imageDecode
});
}
/// @nodoc
class __$CaptchaResponseModelCopyWithImpl<$Res>
implements _$CaptchaResponseModelCopyWith<$Res> {
__$CaptchaResponseModelCopyWithImpl(this._self, this._then);
final _CaptchaResponseModel _self;
final $Res Function(_CaptchaResponseModel) _then;
/// Create a copy of CaptchaResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? captchaKey = freezed,Object? captchaImage = freezed,Object? imageType = freezed,Object? imageDecode = freezed,}) {
return _then(_CaptchaResponseModel(
captchaKey: freezed == captchaKey ? _self.captchaKey : captchaKey // ignore: cast_nullable_to_non_nullable
as String?,captchaImage: freezed == captchaImage ? _self.captchaImage : captchaImage // ignore: cast_nullable_to_non_nullable
as String?,imageType: freezed == imageType ? _self.imageType : imageType // ignore: cast_nullable_to_non_nullable
as String?,imageDecode: freezed == imageDecode ? _self.imageDecode : imageDecode // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'captcha_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_CaptchaResponseModel _$CaptchaResponseModelFromJson(
Map<String, dynamic> json,
) => _CaptchaResponseModel(
captchaKey: json['captcha_key'] as String?,
captchaImage: json['captcha_image'] as String?,
imageType: json['image_type'] as String?,
imageDecode: json['image_decode'] as String?,
);
Map<String, dynamic> _$CaptchaResponseModelToJson(
_CaptchaResponseModel instance,
) => <String, dynamic>{
'captcha_key': instance.captchaKey,
'captcha_image': instance.captchaImage,
'image_type': instance.imageType,
'image_decode': instance.imageDecode,
};

View File

@@ -0,0 +1,73 @@
import 'package:rasadyar_core/core.dart';
part 'user_profile_model.freezed.dart';
part 'user_profile_model.g.dart';
@freezed
abstract class UserProfileModel with _$UserProfileModel {
const factory UserProfileModel({
required User user,
required Role role,
required List<Permission> permissions,
}) = _UserProfileModel;
factory UserProfileModel.fromJson(Map<String, dynamic> json) => _$UserProfileModelFromJson(json);
}
@freezed
abstract class User with _$User {
const factory User({
required int id,
required String username,
required String password,
required String firstName,
required String lastName,
required bool isActive,
required String mobile,
required String phone,
required String nationalCode,
required DateTime birthdate,
required String nationality,
required String ownership,
required String address,
required String photo,
required int province,
required int city,
required bool otpStatus,
required String cityName,
required String provinceName,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
@freezed
abstract class Role with _$Role {
const factory Role({
required int id,
required String roleName,
required String description,
required RoleType type,
required List<dynamic> permissions,
}) = _Role;
factory Role.fromJson(Map<String, dynamic> json) => _$RoleFromJson(json);
}
@freezed
abstract class RoleType with _$RoleType {
const factory RoleType({String? key, required String name}) = _RoleType;
factory RoleType.fromJson(Map<String, dynamic> json) => _$RoleTypeFromJson(json);
}
@freezed
abstract class Permission with _$Permission {
const factory Permission({required String pageName, required List<String> pageAccess}) =
_Permission;
factory Permission.fromJson(Map<String, dynamic> json) => _$PermissionFromJson(json);
}

View File

@@ -0,0 +1,104 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_profile_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_UserProfileModel _$UserProfileModelFromJson(Map<String, dynamic> json) =>
_UserProfileModel(
user: User.fromJson(json['user'] as Map<String, dynamic>),
role: Role.fromJson(json['role'] as Map<String, dynamic>),
permissions: (json['permissions'] as List<dynamic>)
.map((e) => Permission.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$UserProfileModelToJson(_UserProfileModel instance) =>
<String, dynamic>{
'user': instance.user,
'role': instance.role,
'permissions': instance.permissions,
};
_User _$UserFromJson(Map<String, dynamic> json) => _User(
id: (json['id'] as num).toInt(),
username: json['username'] as String,
password: json['password'] as String,
firstName: json['first_name'] as String,
lastName: json['last_name'] as String,
isActive: json['is_active'] as bool,
mobile: json['mobile'] as String,
phone: json['phone'] as String,
nationalCode: json['national_code'] as String,
birthdate: DateTime.parse(json['birthdate'] as String),
nationality: json['nationality'] as String,
ownership: json['ownership'] as String,
address: json['address'] as String,
photo: json['photo'] as String,
province: (json['province'] as num).toInt(),
city: (json['city'] as num).toInt(),
otpStatus: json['otp_status'] as bool,
cityName: json['city_name'] as String,
provinceName: json['province_name'] as String,
);
Map<String, dynamic> _$UserToJson(_User instance) => <String, dynamic>{
'id': instance.id,
'username': instance.username,
'password': instance.password,
'first_name': instance.firstName,
'last_name': instance.lastName,
'is_active': instance.isActive,
'mobile': instance.mobile,
'phone': instance.phone,
'national_code': instance.nationalCode,
'birthdate': instance.birthdate.toIso8601String(),
'nationality': instance.nationality,
'ownership': instance.ownership,
'address': instance.address,
'photo': instance.photo,
'province': instance.province,
'city': instance.city,
'otp_status': instance.otpStatus,
'city_name': instance.cityName,
'province_name': instance.provinceName,
};
_Role _$RoleFromJson(Map<String, dynamic> json) => _Role(
id: (json['id'] as num).toInt(),
roleName: json['role_name'] as String,
description: json['description'] as String,
type: RoleType.fromJson(json['type'] as Map<String, dynamic>),
permissions: json['permissions'] as List<dynamic>,
);
Map<String, dynamic> _$RoleToJson(_Role instance) => <String, dynamic>{
'id': instance.id,
'role_name': instance.roleName,
'description': instance.description,
'type': instance.type,
'permissions': instance.permissions,
};
_RoleType _$RoleTypeFromJson(Map<String, dynamic> json) =>
_RoleType(key: json['key'] as String?, name: json['name'] as String);
Map<String, dynamic> _$RoleTypeToJson(_RoleType instance) => <String, dynamic>{
'key': instance.key,
'name': instance.name,
};
_Permission _$PermissionFromJson(Map<String, dynamic> json) => _Permission(
pageName: json['page_name'] as String,
pageAccess: (json['page_access'] as List<dynamic>)
.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$PermissionToJson(_Permission instance) =>
<String, dynamic>{
'page_name': instance.pageName,
'page_access': instance.pageAccess,
};

View File

@@ -0,0 +1,14 @@
import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart';
abstract class AuthRepository {
Future<AuthResponseModel?> login({required Map<String, dynamic> authRequest});
Future<CaptchaResponseModel?> captcha();
Future<void> logout();
Future<bool> hasAuthenticated();
Future<AuthResponseModel?> loginWithRefreshToken({required Map<String, dynamic> authRequest});
}

View File

@@ -0,0 +1,37 @@
import 'package:rasadyar_livestock/data/data_source/remote/auth/auth_remote.dart';
import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart';
import 'auth_repository.dart';
class AuthRepositoryImp implements AuthRepository {
final AuthRemoteDataSource authRemote;
AuthRepositoryImp(this.authRemote);
@override
Future<AuthResponseModel?> login({required Map<String, dynamic> authRequest}) async =>
await authRemote.login(authRequest: authRequest);
@override
Future<CaptchaResponseModel?> captcha() async {
return await authRemote.captcha();
}
@override
Future<AuthResponseModel?> loginWithRefreshToken({
required Map<String, dynamic> authRequest,
}) async {
return await authRemote.loginWithRefreshToken(authRequest: authRequest);
}
@override
Future<void> logout() async {
await authRemote.logout();
}
@override
Future<bool> hasAuthenticated() async {
return await authRemote.hasAuthenticated();
}
}

View File

@@ -0,0 +1,59 @@
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/data/common/dio_exception_handeler.dart';
import 'package:rasadyar_livestock/data/data_source/remote/auth/auth_remote.dart';
import 'package:rasadyar_livestock/data/data_source/remote/auth/auth_remote_imp.dart';
import 'package:rasadyar_livestock/data/repository/auth/auth_repository_imp.dart';
import 'package:rasadyar_livestock/presentation/routes/app_pages.dart';
GetIt get diLiveStock => GetIt.instance;
Future setupLiveStockDI() async {
diLiveStock.registerSingleton(DioErrorHandler());
var tokenService = Get.find<TokenStorageService>();
if (tokenService.baseurl.value == null) {
await tokenService.saveBaseUrl('https://api.dam.rasadyar.net/');
}
diLiveStock.registerLazySingleton<AppInterceptor>(
() => AppInterceptor(
refreshTokenCallback: () async {
var authRepository = diLiveStock.get<AuthRepositoryImp>();
var hasAuthenticated = await authRepository.hasAuthenticated();
if (hasAuthenticated) {
var newToken = await authRepository.loginWithRefreshToken(
authRequest: {'refresh': tokenService.refreshToken.value},
);
return newToken?.access;
}
return null;
},
saveTokenCallback: (String newToken) async {
await tokenService.saveAccessToken(newToken);
},
clearTokenCallback: () async {
await tokenService.deleteTokens();
Get.offAllNamed(LiveStockRoutes.auth, arguments: Module.liveStocks);
},
authArguments: Module.liveStocks,
),
);
// Register the DioRemote client
diLiveStock.registerLazySingleton<DioRemote>(
() => DioRemote(
baseUrl: tokenService.baseurl.value,
interceptors: diLiveStock.get<AppInterceptor>(),
),
);
var dioRemoteClient = diLiveStock.get<DioRemote>();
await dioRemoteClient.init();
// Register the AuthRemote data source implementation
diLiveStock.registerLazySingleton<AuthRemoteDataSource>(
() => AuthRemoteDataSourceImp(diLiveStock.get<DioRemote>()),
);
// Register the AuthRepository implementation
diLiveStock.registerLazySingleton<AuthRepositoryImp>(
() => AuthRepositoryImp(diLiveStock.get<AuthRemoteDataSource>()),
);
}

View File

@@ -0,0 +1,188 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/data/common/dio_exception_handeler.dart';
import 'package:rasadyar_livestock/data/model/request/login_request/login_request_model.dart';
import 'package:rasadyar_livestock/data/model/response/auth/auth_response_model.dart';
import 'package:rasadyar_livestock/data/repository/auth/auth_repository_imp.dart';
import 'package:rasadyar_livestock/injection/live_stock_di.dart';
import 'package:rasadyar_livestock/presentation/routes/app_pages.dart';
import 'package:rasadyar_livestock/presentation/widgets/captcha/logic.dart';
enum AuthType { useAndPass, otp }
enum AuthStatus { init }
enum OtpStatus { init, sent, verified, reSend }
class AuthLogic extends GetxController with GetTickerProviderStateMixin {
GlobalKey<FormState> formKey = GlobalKey<FormState>();
late AnimationController _textAnimationController;
late Animation<double> textAnimation;
RxBool showCard = false.obs;
Rx<GlobalKey<FormState>> formKeyOtp = GlobalKey<FormState>().obs;
Rx<GlobalKey<FormState>> formKeySentOtp = GlobalKey<FormState>().obs;
Rx<TextEditingController> usernameController = TextEditingController().obs;
Rx<TextEditingController> passwordController = TextEditingController().obs;
Rx<TextEditingController> phoneOtpNumberController = TextEditingController().obs;
Rx<TextEditingController> otpCodeController = TextEditingController().obs;
var captchaController = Get.find<CaptchaWidgetLogic>();
RxnString phoneNumber = RxnString(null);
RxBool isLoading = false.obs;
RxBool isDisabled = true.obs;
TokenStorageService tokenStorageService = Get.find<TokenStorageService>();
Rx<AuthType> authType = AuthType.useAndPass.obs;
Rx<AuthStatus> authStatus = AuthStatus.init.obs;
Rx<OtpStatus> otpStatus = OtpStatus.init.obs;
RxInt secondsRemaining = 120.obs;
Timer? _timer;
AuthRepositoryImp authRepository = diLiveStock.get<AuthRepositoryImp>();
final Module _module = Get.arguments;
@override
void onInit() {
super.onInit();
_textAnimationController =
AnimationController(vsync: this, duration: const Duration(milliseconds: 1200))
..repeat(reverse: true, count: 2).whenComplete(() {
showCard.value = true;
});
textAnimation = CurvedAnimation(parent: _textAnimationController, curve: Curves.easeInOut);
}
@override
void onReady() {
super.onReady();
//_textAnimationController.forward();
}
@override
void onClose() {
_timer?.cancel();
super.onClose();
}
void startTimer() {
_timer?.cancel();
secondsRemaining.value = 120;
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (secondsRemaining.value > 0) {
secondsRemaining.value--;
} else {
timer.cancel();
}
});
}
void stopTimer() {
_timer?.cancel();
}
String get timeFormatted {
final minutes = secondsRemaining.value ~/ 60;
final seconds = secondsRemaining.value % 60;
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
bool _isFormValid() {
final isCaptchaValid = captchaController.formKey.currentState?.validate() ?? false;
final isFormValid = formKey.currentState?.validate() ?? false;
return isCaptchaValid && isFormValid;
}
LoginRequestModel _buildLoginRequest() {
final phone = usernameController.value.text;
final pass = passwordController.value.text;
final code = captchaController.textController.value.text;
final key = captchaController.captchaKey.value;
return LoginRequestModel.createWithCaptcha(
username: phone,
password: pass,
captchaCode: code,
captchaKey: key!,
);
}
Future<void> submitLoginForm() async {
if (!_isFormValid()) return;
final loginRequestModel = _buildLoginRequest();
isLoading.value = true;
await safeCall<AuthResponseModel?>(
call: () async => authRepository.login(authRequest: loginRequestModel.toJson()),
onSuccess: (result) async {
await tokenStorageService.saveModule(_module);
await tokenStorageService.saveRefreshToken(result?.refresh ?? '');
await tokenStorageService.saveAccessToken(result?.access ?? '');
Get.offAllNamed(LiveStockRoutes.init);
},
onError: (error, stackTrace) {
if (error is DioException) {
diLiveStock.get<DioErrorHandler>().handle(error);
}
captchaController.getCaptcha();
},
);
isLoading.value = false;
}
Future<void> submitLoginForm2() async {
if (!_isFormValid()) return;
//AuthRepositoryImpl authTmp = diAuth.get<AuthRepositoryImpl>(instanceName: 'newUrl');
isLoading.value = true;
/* await safeCall<UserProfileModel?>(
call: () => authTmp.login(
authRequest: {
"username": usernameController.value.text,
"password": passwordController.value.text,
},
),
onSuccess: (result) async {
await tokenStorageService.saveModule(_module);
await tokenStorageService.saveAccessToken(result?.accessToken ?? '');
await tokenStorageService.saveRefreshToken(result?.accessToken ?? '');
},
onError: (error, stackTrace) {
if (error is DioException) {
// diAuth.get<DioErrorHandler>().handle(error);
}
captchaController.getCaptcha();
},
);*/
isLoading.value = false;
}
Future<void> getUserInfo(String value) async {
isLoading.value = true;
/*await safeCall<UserInfoModel?>(
call: () async => await authRepository.getUserInfo(value),
onSuccess: (result) async {
if (result != null) {
//await newSetupAuthDI(result.backend ?? '');
await tokenStorageService.saveApiKey(result.apiKey ?? '');
await tokenStorageService.saveBaseUrl(result.backend ?? '');
}
},
onError: (error, stackTrace) {
if (error is DioException) {
// diAuth.get<DioErrorHandler>().handle(error);
}
captchaController.getCaptcha();
},
);*/
isLoading.value = false;
}
}

View File

@@ -0,0 +1,541 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/presentation/widgets/captcha/view.dart';
import 'logic.dart';
class AuthPage extends GetView<AuthLogic> {
const AuthPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: [
Assets.vec.bgAuthSvg.svg(fit: BoxFit.fill),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.r),
child: FadeTransition(
opacity: controller.textAnimation,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 12,
children: [
Text(
'به سامانه رصدیار خوش آمدید!',
textAlign: TextAlign.right,
style: AppFonts.yekan25Bold.copyWith(color: Colors.white),
),
Text(
'سامانه رصد و پایش زنجیره تامین، تولید و توزیع کالا های اساسی',
textAlign: TextAlign.center,
style: AppFonts.yekan16.copyWith(color: Colors.white),
),
],
),
),
),
Obx(() {
final screenHeight = MediaQuery.of(context).size.height;
final targetTop = (screenHeight - 676) / 2;
return AnimatedPositioned(
duration: const Duration(milliseconds: 1200),
curve: Curves.linear,
top: controller.showCard.value ? targetTop : screenHeight,
left: 10.r,
right: 10.r,
child: Container(
width: 381.w,
height: 676.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
child: Column(
children: [
SizedBox(height: 50.h),
LogoWidget(),
SizedBox(height: 20.h),
useAndPassFrom(),
SizedBox(height: 24.h),
RichText(
text: TextSpan(
children: [
TextSpan(
text: 'مطالعه بیانیه ',
style: AppFonts.yekan16.copyWith(color: AppColor.darkGreyDark),
),
TextSpan(
recognizer: TapGestureRecognizer()
..onTap = () {
Get.bottomSheet(
privacyPolicyWidget(),
isScrollControlled: true,
enableDrag: true,
ignoreSafeArea: false,
);
},
text: 'حریم خصوصی',
style: AppFonts.yekan16.copyWith(color: AppColor.blueNormal),
),
],
),
),
],
),
),
);
}),
],
),
);
}
Widget useAndPassFrom() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 30.r),
child: Form(
key: controller.formKey,
child: AutofillGroup(
child: Column(
children: [
RTextField(
label: 'نام کاربری',
maxLength: 11,
maxLines: 1,
controller: controller.usernameController.value,
keyboardType: TextInputType.number,
initText: controller.usernameController.value.text,
autofillHints: [AutofillHints.username],
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppColor.textColor, width: 1),
),
onChanged: (value) async {
controller.usernameController.value.text = value;
controller.usernameController.refresh();
},
prefixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 6, 8),
child: Assets.vec.callSvg.svg(width: 12, height: 12),
),
suffixIcon: controller.usernameController.value.text.trim().isNotEmpty
? clearButton(() {
controller.usernameController.value.clear();
controller.usernameController.refresh();
})
: null,
validator: (value) {
if (value == null || value.isEmpty) {
return '⚠️ شماره موبایل را وارد کنید';
} else if (value.length < 10) {
return '⚠️ شماره موبایل باید 11 رقم باشد';
}
return null;
},
style: AppFonts.yekan13,
errorStyle: AppFonts.yekan13.copyWith(color: AppColor.redNormal),
labelStyle: AppFonts.yekan13,
boxConstraints: const BoxConstraints(
maxHeight: 40,
minHeight: 40,
maxWidth: 40,
minWidth: 40,
),
),
const SizedBox(height: 26),
ObxValue(
(passwordController) => RTextField(
label: 'رمز عبور',
filled: false,
obscure: true,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppColor.textColor, width: 1),
),
controller: passwordController.value,
autofillHints: [AutofillHints.password],
variant: RTextFieldVariant.password,
initText: passwordController.value.text,
onChanged: (value) {
passwordController.refresh();
},
validator: (value) {
if (value == null || value.isEmpty) {
return '⚠️ رمز عبور را وارد کنید';
}
return null;
},
style: AppFonts.yekan13,
errorStyle: AppFonts.yekan13.copyWith(color: AppColor.redNormal),
labelStyle: AppFonts.yekan13,
prefixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 8, 8),
child: Assets.vec.keySvg.svg(width: 12, height: 12),
),
boxConstraints: const BoxConstraints(
maxHeight: 34,
minHeight: 34,
maxWidth: 34,
minWidth: 34,
),
),
controller.passwordController,
),
SizedBox(height: 26),
CaptchaWidget(),
SizedBox(height: 23),
Obx(() {
return RElevated(
text: 'ورود',
isLoading: controller.isLoading.value,
onPressed: controller.isDisabled.value
? null
: () async {
await controller.submitLoginForm();
},
width: Get.width,
height: 48,
);
}),
],
),
),
),
);
}
Widget privacyPolicyWidget() {
return BaseBottomSheet(
child: Column(
spacing: 5,
children: [
Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 3,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'بيانيه حريم خصوصی',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
Text(
'اطلاعات مربوط به هر شخص، حریم خصوصی وی محسوب می‌شود. حفاظت و حراست از اطلاعات شخصی در سامانه رصد یار، نه تنها موجب حفظ امنیت کاربران می‌شود، بلکه باعث اعتماد بیشتر و مشارکت آنها در فعالیت‌های جاری می‌گردد. هدف از این بیانیه، آگاه ساختن شما درباره ی نوع و نحوه ی استفاده از اطلاعاتی است که در هنگام استفاده از سامانه رصد یار ، از جانب شما دریافت می‌گردد. شرکت هوشمند سازان خود را ملزم به رعایت حریم خصوصی همه شهروندان و کاربران سامانه دانسته و آن دسته از اطلاعات کاربران را که فقط به منظور ارائه خدمات کفایت می‌کند، دریافت کرده و از انتشار آن یا در اختیار قرار دادن آن به دیگران خودداری مینماید.',
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark, height: 1.8),
),
],
),
),
Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'چگونگی جمع آوری و استفاده از اطلاعات کاربران',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
Text(
'''الف: اطلاعاتی که شما خود در اختيار این سامانه قرار می‌دهيد، شامل موارد زيرهستند:
اقلام اطلاعاتی شامل شماره تلفن همراه، تاریخ تولد، کد پستی و کد ملی کاربران را دریافت مینماییم که از این اقلام، صرفا جهت احراز هویت کاربران استفاده خواهد شد.
ب: برخی اطلاعات ديگر که به صورت خودکار از شما دريافت میشود شامل موارد زير می‌باشد:
⦁ دستگاهی که از طریق آن سامانه رصد یار را مشاهده می‌نمایید( تلفن همراه، تبلت، رایانه).
⦁ نام و نسخه سیستم عامل و browser کامپیوتر شما.
⦁ اطلاعات صفحات بازدید شده.
⦁ تعداد بازدیدهای روزانه در درگاه.
⦁ هدف ما از دریافت این اطلاعات استفاده از آنها در تحلیل عملکرد کاربران درگاه می باشد تا بتوانیم در خدمت رسانی بهتر عمل کنیم.
''',
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark, height: 1.8),
),
],
),
),
Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'امنیت اطلاعات',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
Text(
'متعهدیم که امنیت اطلاعات شما را تضمین نماییم و برای جلوگیری از هر نوع دسترسی غیرمجاز و افشای اطلاعات شما از همه شیوه‌‌های لازم استفاده می‌کنیم تا امنیت اطلاعاتی را که به صورت آنلاین گردآوری می‌کنیم، حفظ شود. لازم به ذکر است در سامانه ما، ممکن است به سایت های دیگری لینک شوید، وقتی که شما از طریق این لینک‌ها از سامانه ما خارج می‌شوید، توجه داشته باشید که ما بر دیگر سایت ها کنترل نداریم و سازمان تعهدی بر حفظ حریم شخصی آنان در سایت مقصد نخواهد داشت و مراجعه کنندگان میبایست به بیانیه حریم شخصی آن سایت ها مراجعه نمایند.',
style: AppFonts.yekan14.copyWith(color: AppColor.bgDark, height: 1.8),
),
],
),
),
],
),
);
}
/*
Widget sendCodeForm() {
return ObxValue((data) {
return Form(
key: data.value,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 50),
child: Column(
children: [
SizedBox(height: 26),
ObxValue((phoneController) {
return TextFormField(
controller: phoneController.value,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
gapPadding: 11,
),
labelText: 'شماره موبایل',
labelStyle: AppFonts.yekan13,
errorStyle: AppFonts.yekan13.copyWith(
color: AppColor.redNormal,
),
prefixIconConstraints: BoxConstraints(
maxHeight: 40,
minHeight: 40,
maxWidth: 40,
minWidth: 40,
),
prefixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 6, 8),
child: vecWidget(Assets.vecCallSvg),
),
suffix:
phoneController.value.text.trim().isNotEmpty
? clearButton(() {
phoneController.value.clear();
phoneController.refresh();
})
: null,
counterText: '',
),
keyboardType: TextInputType.numberWithOptions(
decimal: false,
signed: false,
),
maxLines: 1,
maxLength: 11,
onChanged: (value) {
if (controller.isOnError.value) {
controller.isOnError.value = !controller.isOnError.value;
data.value.currentState?.reset();
data.refresh();
phoneController.value.text = value;
}
phoneController.refresh();
},
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null) {
return '⚠️ شماره موبایل را وارد کنید';
} else if (value.length < 11) {
return '⚠️ شماره موبایل باید 11 رقم باشد';
}
return null;
},
style: AppFonts.yekan13,
);
}, controller.phoneOtpNumberController),
SizedBox(height: 26),
CaptchaWidget(),
SizedBox(height: 23),
RElevated(
text: 'ارسال رمز یکبار مصرف',
onPressed: () {
if (data.value.currentState?.validate() == true) {
controller.otpStatus.value = OtpStatus.sent;
controller.startTimer();
}
},
width: Get.width,
height: 48,
),
],
),
),
);
}, controller.formKeyOtp);
}
Widget confirmCodeForm() {
return ObxValue((data) {
return Form(
key: data.value,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 50),
child: Column(
children: [
SizedBox(height: 26),
ObxValue((passwordController) {
return TextFormField(
controller: passwordController.value,
obscureText: controller.hidePassword.value,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
gapPadding: 11,
),
labelText: 'رمز عبور',
labelStyle: AppFonts.yekan13,
errorStyle: AppFonts.yekan13.copyWith(
color: AppColor.redNormal,
),
prefixIconConstraints: BoxConstraints(
maxHeight: 34,
minHeight: 34,
maxWidth: 34,
minWidth: 34,
),
prefixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 8, 8),
child: vecWidget(Assets.vecKeySvg),
),
suffix:
passwordController.value.text.trim().isNotEmpty
? GestureDetector(
onTap: () {
controller.hidePassword.value =
!controller.hidePassword.value;
},
child: Icon(
controller.hidePassword.value
? CupertinoIcons.eye
: CupertinoIcons.eye_slash,
),
)
: null,
counterText: '',
),
textInputAction: TextInputAction.done,
keyboardType: TextInputType.visiblePassword,
maxLines: 1,
onChanged: (value) {
if (controller.isOnError.value) {
controller.isOnError.value = !controller.isOnError.value;
data.value.currentState?.reset();
passwordController.value.text = value;
}
passwordController.refresh();
},
validator: (value) {
if (value == null || value.isEmpty) {
return '⚠️ رمز عبور را وارد کنید'; // "Please enter the password"
}
return null;
},
style: AppFonts.yekan13,
);
}, controller.passwordController),
SizedBox(height: 23),
ObxValue((timer) {
if (timer.value == 0) {
return TextButton(
onPressed: () {
controller.otpStatus.value = OtpStatus.reSend;
controller.startTimer();
},
child: Text(
style: AppFonts.yekan13.copyWith(
color: AppColor.blueNormal,
),
'ارسال مجدد کد یکبار مصرف',
),
);
} else {
return Text(
'اعتبار رمز ارسال شده ${controller.timeFormatted}',
style: AppFonts.yekan13,
);
}
}, controller.secondsRemaining),
RichText(
text: TextSpan(
children: [
TextSpan(
text: ' کد ارسال شده به شماره ',
style: AppFonts.yekan14.copyWith(
color: AppColor.darkGreyDark,
),
),
TextSpan(
text: controller.phoneOtpNumberController.value.text,
style: AppFonts.yekan13Bold.copyWith(
color: AppColor.darkGreyDark,
),
),
TextSpan(
recognizer:
TapGestureRecognizer()
..onTap = () {
controller.otpStatus.value = OtpStatus.init;
},
text: ' ویرایش',
style: AppFonts.yekan14.copyWith(
color: AppColor.blueNormal,
),
),
],
),
),
SizedBox(height: 23),
RElevated(
text: 'ورود',
onPressed: () {
if (controller.formKeyOtp.value.currentState?.validate() ==
true) {}
},
width: Get.width,
height: 48,
),
],
),
),
);
}, controller.formKeySentOtp);
}*/
}

View File

@@ -8,6 +8,7 @@ import 'package:rasadyar_livestock/presentation/routes/app_pages.dart';
class RootLogic extends GetxController { class RootLogic extends GetxController {
List<Widget> pages = [ List<Widget> pages = [
MapPage(),
Navigator( Navigator(
key: Get.nestedKey(0), key: Get.nestedKey(0),
initialRoute: LiveStockRoutes.requests, initialRoute: LiveStockRoutes.requests,
@@ -22,7 +23,7 @@ class RootLogic extends GetxController {
} }
}, },
), ),
MapPage(),
ProfilePage(), ProfilePage(),
]; ];
RxInt currentIndex = 1.obs; RxInt currentIndex = 1.obs;

View File

@@ -9,7 +9,6 @@ class RootPage extends GetView<RootLogic> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ObxValue((currentIndex) { return ObxValue((currentIndex) {
return PopScope( return PopScope(
canPop: false, canPop: false,
onPopInvokedWithResult: (didPop, result) { onPopInvokedWithResult: (didPop, result) {
@@ -43,28 +42,39 @@ class RootPage extends GetView<RootLogic> {
index: currentIndex.value, index: currentIndex.value,
sizing: StackFit.expand, sizing: StackFit.expand,
), ),
extendBody: true,
bottomNavigationBar: RBottomNavigation( bottomNavigationBar: RBottomNavigation(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
items: [ items: [
RBottomNavigationItem( RBottomNavigationItem(
icon: Assets.vec.filterSvg.path,
label: 'درخواست‌ها',
isSelected: currentIndex.value == 0,
onTap: () => controller.changePage(0),
),
RBottomNavigationItem(
icon: Assets.vec.mapSvg.path,
label: 'نقشه', label: 'نقشه',
icon: Assets.vec.mapSvg.path,
isSelected: currentIndex.value == 0,
onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(0);
},
),
RBottomNavigationItem(
label: 'درخواست‌ها',
icon: Assets.vec.settingSvg.path,
isSelected: currentIndex.value == 1, isSelected: currentIndex.value == 1,
onTap: () => controller.changePage(1), onTap: () {
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(1);
},
), ),
RBottomNavigationItem( RBottomNavigationItem(
icon: Assets.vec.profileUserSvg.path,
label: 'پروفایل', label: 'پروفایل',
icon: Assets.vec.profileCircleSvg.path,
isSelected: currentIndex.value == 2, isSelected: currentIndex.value == 2,
onTap: () => controller.changePage(2), onTap: () {
Get.nestedKey(1)?.currentState?.popUntil((route) => route.isFirst);
Get.nestedKey(0)?.currentState?.popUntil((route) => route.isFirst);
controller.changePage(2);
},
), ),
], ],
), ),

View File

@@ -1,5 +1,7 @@
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_core/presentation/widget/map/logic.dart'; import 'package:rasadyar_core/presentation/widget/map/logic.dart';
import 'package:rasadyar_livestock/presentation/page/auth/logic.dart';
import 'package:rasadyar_livestock/presentation/page/auth/view.dart';
import 'package:rasadyar_livestock/presentation/page/map/logic.dart'; import 'package:rasadyar_livestock/presentation/page/map/logic.dart';
import 'package:rasadyar_livestock/presentation/page/profile/logic.dart'; import 'package:rasadyar_livestock/presentation/page/profile/logic.dart';
import 'package:rasadyar_livestock/presentation/page/request_tagging/logic.dart'; import 'package:rasadyar_livestock/presentation/page/request_tagging/logic.dart';
@@ -9,6 +11,7 @@ import 'package:rasadyar_livestock/presentation/page/root/logic.dart';
import 'package:rasadyar_livestock/presentation/page/root/view.dart'; import 'package:rasadyar_livestock/presentation/page/root/view.dart';
import 'package:rasadyar_livestock/presentation/page/tagging/logic.dart'; import 'package:rasadyar_livestock/presentation/page/tagging/logic.dart';
import 'package:rasadyar_livestock/presentation/page/tagging/view.dart'; import 'package:rasadyar_livestock/presentation/page/tagging/view.dart';
import 'package:rasadyar_livestock/presentation/widgets/captcha/logic.dart';
part 'app_routes.dart'; part 'app_routes.dart';
@@ -16,6 +19,14 @@ sealed class LiveStockPages {
LiveStockPages._(); LiveStockPages._();
static final pages = [ static final pages = [
GetPage(
name: LiveStockRoutes.auth,
page: () => AuthPage(),
binding: BindingsBuilder(() {
Get.lazyPut(() => AuthLogic());
Get.lazyPut(() => CaptchaWidgetLogic());
}),
),
GetPage( GetPage(
name: LiveStockRoutes.init, name: LiveStockRoutes.init,
page: () => RootPage(), page: () => RootPage(),
@@ -40,7 +51,6 @@ sealed class LiveStockPages {
),*/ ),*/
], ],
), ),
GetPage( GetPage(
name: LiveStockRoutes.requestTagging, name: LiveStockRoutes.requestTagging,
page: () => RequestTaggingPage(), page: () => RequestTaggingPage(),

View File

@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/data/model/response/captcha/captcha_response_model.dart';
import 'package:rasadyar_livestock/data/repository/auth/auth_repository_imp.dart';
import 'package:rasadyar_livestock/injection/live_stock_di.dart';
class CaptchaWidgetLogic extends GetxController with StateMixin<CaptchaResponseModel> {
TextEditingController textController = TextEditingController();
RxnString captchaKey = RxnString();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
AuthRepositoryImp authRepository = diLiveStock.get<AuthRepositoryImp>();
@override
void onInit() {
super.onInit();
getCaptcha();
}
@override
void onClose() {
textController.clear();
textController.dispose();
super.onClose();
}
Future<void> getCaptcha() async {
change(null, status: RxStatus.loading());
textController.clear();
formKey.currentState?.reset();
await Future.delayed(Duration(milliseconds: 200));
await safeCall(
call: () async => authRepository.captcha(),
onSuccess: (result) {
if (result == null) {
change(null, status: RxStatus.error('Failed to load captcha'));
return;
}
captchaKey.value = result.captchaKey;
change(result, status: RxStatus.success());
},
onError: (error, stackTrace) {},
);
change(value, status: RxStatus.success());
}
}

View File

@@ -0,0 +1,105 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_livestock/presentation/page/auth/logic.dart';
import 'logic.dart';
class CaptchaWidget extends GetView<CaptchaWidgetLogic> {
const CaptchaWidget({super.key});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: controller.getCaptcha,
child: Container(
width: 135,
height: 50,
clipBehavior: Clip.antiAliasWithSaveLayer,
decoration: BoxDecoration(
color: AppColor.whiteNormalHover,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: controller.obx(
(state) => Image.memory(base64Decode(state?.captchaImage ?? ''), fit: BoxFit.cover),
onLoading: const Center(
child: CupertinoActivityIndicator(color: AppColor.blueNormal),
),
onError: (error) {
return Center(
child: Text('خطا ', style: AppFonts.yekan13.copyWith(color: Colors.red)),
);
},
),
),
),
const SizedBox(width: 8),
Expanded(
child: Form(
key: controller.formKey,
autovalidateMode: AutovalidateMode.disabled,
child: RTextField(
label: 'کد امنیتی',
controller: controller.textController,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: AppColor.textColor, width: 1),
),
keyboardType: TextInputType.numberWithOptions(decimal: false, signed: false),
maxLines: 1,
maxLength: 6,
suffixIcon: (controller.textController.text.trim().isNotEmpty ?? false)
? clearButton(() => controller.textController.clear())
: null,
validator: (value) {
if (value == null || value.isEmpty) {
return 'کد امنیتی را وارد کنید';
}
return null;
},
onChanged: (pass) {
if (pass.length == 6) {
if (controller.formKey.currentState?.validate() ?? false) {
Get.find<AuthLogic>().isDisabled.value = false;
}
}
},
style: AppFonts.yekan13,
),
),
),
],
);
}
}
class _CaptchaLinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final random = Random();
final paint1 = Paint()
..color = Colors.deepOrange
..strokeWidth = 2;
final paint2 = Paint()
..color = Colors.blue
..strokeWidth = 2;
// First line: top-left to bottom-right
canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), paint1);
// Second line: bottom-left to top-right
canvas.drawLine(Offset(0, size.height), Offset(size.width, 0), paint2);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -1,6 +1,6 @@
name: rasadyar_livestock name: rasadyar_livestock
description: A starting point for Dart libraries or applications. description: A starting point for Dart libraries or applications.
version: 1.0.0 version: 2.0.0
publish_to: 'none' publish_to: 'none'
# repository: https://github.com/my_org/my_repo # repository: https://github.com/my_org/my_repo

View File

@@ -1272,7 +1272,7 @@ packages:
path: "packages/livestock" path: "packages/livestock"
relative: true relative: true
source: path source: path
version: "1.0.0" version: "2.0.0"
rxdart: rxdart:
dependency: transitive dependency: transitive
description: description: