Merge branch 'refs/heads/feature/chicken' into feature/inspectionV1.0.2

# Conflicts:
#	packages/auth/lib/presentation/pages/auth/view.dart
#	packages/auth/pubspec.yaml
#	packages/core/lib/presentation/widget/app_bar/r_app_bar.dart
#	packages/core/lib/presentation/widget/inputs/r_input.dart
This commit is contained in:
2025-07-24 12:07:43 +03:30
365 changed files with 52723 additions and 1865 deletions

View File

@@ -5,10 +5,14 @@ class DioErrorHandler {
void handle(DioException error) {
switch (error.response?.statusCode) {
case 401:
_handle401();
_handleGeneric(error);
break;
case 403:
_handle403();
_handleGeneric(error);
break;
case 410:
_handle410();
break;
default:
_handleGeneric(error);
@@ -16,21 +20,22 @@ class DioErrorHandler {
}
//wrong password/user name => "detail": "No active account found with the given credentials" - 401
void _handle401() {
Get.showSnackbar(
_errorSnackBar('نام کاربری یا رمز عبور اشتباه است'),
);
void _handle410() {
Get.showSnackbar(_errorSnackBar('نام کاربری یا رمز عبور اشتباه است'));
}
//wrong captcha => "detail": "Captcha code is incorrect" - 403
void _handle403() {
Get.showSnackbar(
_errorSnackBar('کد امنیتی اشتباه است'),
);
}
void _handle403() {}
void _handleGeneric(DioException error) {
// General error handling
Get.showSnackbar(
_errorSnackBar(
error.response?.data.keys.first == 'is_user'
? 'کاربر با این شماره تلفن وجود ندارد'
: error.response?.data[error.response?.data.keys.first] ??
'خطا در برقراری ارتباط با سرور',
),
);
}
GetSnackBar _errorSnackBar(String message) {

View File

@@ -4,15 +4,21 @@ import 'package:rasadyar_core/core.dart';
import '../di/auth_di.dart';
import 'constant.dart';
/*
class DioRemoteManager {
DioRemote? _currentClient;
ApiEnvironment? _currentEnv;
Future<DioRemote> setEnvironment([
ApiEnvironment env = ApiEnvironment.dam,
]) async {
Future<DioRemote> setEnvironment([ApiEnvironment env = ApiEnvironment.dam]) async {
if (_currentEnv != env) {
_currentClient = DioRemote(env.baseUrl);
_currentClient = DioRemote(
baseUrl: env.baseUrl,
interceptors: AppInterceptor(
refreshTokenCallback: () async{
return null;
},
),
);
await _currentClient?.init();
_currentEnv = env;
}
@@ -39,7 +45,6 @@ Future<void> switchAuthEnvironment(ApiEnvironment env) async {
await diAuth.unregister<AuthRepositoryImpl>();
}
diAuth.registerLazySingleton<AuthRepositoryImpl>(
() => AuthRepositoryImpl(dioRemote),
);
diAuth.registerLazySingleton<AuthRepositoryImpl>(() => AuthRepositoryImpl(dioRemote));
}
*/

View File

@@ -1,5 +1,5 @@
import 'package:rasadyar_auth/data/common/constant.dart';
import 'package:rasadyar_auth/data/common/dio_error_handler.dart';
import 'package:rasadyar_auth/data/models/response/auth/auth_response_model.dart';
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
import 'package:rasadyar_core/core.dart';
@@ -9,13 +9,54 @@ import '../common/dio_manager.dart';
GetIt diAuth = GetIt.instance;
Future<void> setupAuthDI() async {
diAuth.registerLazySingleton(() => DioRemoteManager());
diAuth.registerLazySingleton<AppInterceptor>(
() => AppInterceptor(
refreshTokenCallback: () async {
var tokenService = Get.find<TokenStorageService>();
final authRepo = diAuth.get<AuthRepositoryImpl>();
final manager = diAuth.get<DioRemoteManager>();
final dioRemote = await manager.setEnvironment(ApiEnvironment.dam);
diAuth.registerCachedFactory<AuthRepositoryImpl>(
() => AuthRepositoryImpl(dioRemote),
final refreshToken = tokenService.refreshToken.value;
if (refreshToken == null) return null;
final result = await authRepo.loginWithRefreshToken(
authRequest: {"refresh_token": refreshToken},
);
if (result is AuthResponseModel) {
await tokenService.saveAccessToken(result.access!);
return result.access;
}
return null;
},
saveTokenCallback: (String newToken) async {
//
},
clearTokenCallback: () async {
//await tokenService.clearTokens(); // حذف همه توکن‌ها
},
),
);
diAuth.registerLazySingleton<DioRemote>(
() => DioRemote(interceptors: diAuth.get<AppInterceptor>()),
);
final dioRemote = diAuth.get<DioRemote>();
await dioRemote.init();
diAuth.registerSingleton<AuthRepositoryImpl>(AuthRepositoryImpl(dioRemote));
diAuth.registerLazySingleton<DioErrorHandler>(() => DioErrorHandler());
}
Future<void> newSetupAuthDI(String newUrl) async {
diAuth.registerLazySingleton<DioRemote>(
() => DioRemote(baseUrl: newUrl, interceptors: diAuth.get<AppInterceptor>()),
instanceName: 'newRemote',
);
final dioRemote = diAuth.get<DioRemote>(instanceName: 'newRemote');
await dioRemote.init();
diAuth.registerSingleton<AuthRepositoryImpl>(
AuthRepositoryImpl(dioRemote),
instanceName: 'newUrl',
);
}

View File

@@ -1,9 +1,9 @@
import 'package:rasadyar_auth/auth.dart';
import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_core/utils/local/local_utils.dart';
part 'user_local_model.g.dart';
@HiveType(typeId: 0)
@HiveType(typeId: authUserLocalModelTypeId)
class UserLocalModel extends HiveObject {
@HiveField(0)
String? username;
@@ -19,6 +19,12 @@ class UserLocalModel extends HiveObject {
@HiveField(5)
Module? module;
@HiveField(6)
String? backend;
@HiveField(7)
String? apiKey;
UserLocalModel({
this.username,
this.password,
@@ -26,6 +32,8 @@ class UserLocalModel extends HiveObject {
this.refreshToken,
this.name,
this.module,
this.backend,
this.apiKey,
});
UserLocalModel copyWith({
@@ -35,6 +43,8 @@ class UserLocalModel extends HiveObject {
String? refreshToken,
String? name,
Module? module,
String? backend,
String? apiKey,
}) {
return UserLocalModel(
username: username ?? this.username,
@@ -43,14 +53,18 @@ class UserLocalModel extends HiveObject {
refreshToken: refreshToken ?? this.refreshToken,
name: name ?? this.name,
module: module ?? this.module,
backend: backend ?? this.backend,
apiKey: apiKey ?? this.apiKey,
);
}
}
@HiveType(typeId: 1)
@HiveType(typeId: authModuleTypeId)
enum Module {
@HiveField(0)
liveStocks,
@HiveField(1)
inspection,
@HiveField(2)
chicken,
}

View File

@@ -23,13 +23,15 @@ class UserLocalModelAdapter extends TypeAdapter<UserLocalModel> {
refreshToken: fields[3] as String?,
name: fields[4] as String?,
module: fields[5] as Module?,
backend: fields[6] as String?,
apiKey: fields[7] as String?,
);
}
@override
void write(BinaryWriter writer, UserLocalModel obj) {
writer
..writeByte(6)
..writeByte(8)
..writeByte(0)
..write(obj.username)
..writeByte(1)
@@ -41,7 +43,11 @@ class UserLocalModelAdapter extends TypeAdapter<UserLocalModel> {
..writeByte(4)
..write(obj.name)
..writeByte(5)
..write(obj.module);
..write(obj.module)
..writeByte(6)
..write(obj.backend)
..writeByte(7)
..write(obj.apiKey);
}
@override
@@ -66,6 +72,8 @@ class ModuleAdapter extends TypeAdapter<Module> {
return Module.liveStocks;
case 1:
return Module.inspection;
case 2:
return Module.chicken;
default:
return Module.liveStocks;
}
@@ -78,6 +86,8 @@ class ModuleAdapter extends TypeAdapter<Module> {
writer.writeByte(0);
case Module.inspection:
writer.writeByte(1);
case Module.chicken:
writer.writeByte(2);
}
}

View File

@@ -0,0 +1,18 @@
import 'package:rasadyar_core/core.dart';
part 'user_info_model.freezed.dart';
part 'user_info_model.g.dart';
@freezed
abstract class UserInfoModel with _$UserInfoModel {
const factory UserInfoModel({
bool? isUser,
String? address,
String? backend,
String? apiKey,
}) = _UserInfoModel ;
factory UserInfoModel.fromJson(Map<String, dynamic> json) =>
_$UserInfoModelFromJson(json);
}

View File

@@ -0,0 +1,157 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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 'user_info_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$UserInfoModel {
bool? get isUser; String? get address; String? get backend; String? get apiKey;
/// Create a copy of UserInfoModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$UserInfoModelCopyWith<UserInfoModel> get copyWith => _$UserInfoModelCopyWithImpl<UserInfoModel>(this as UserInfoModel, _$identity);
/// Serializes this UserInfoModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is UserInfoModel&&(identical(other.isUser, isUser) || other.isUser == isUser)&&(identical(other.address, address) || other.address == address)&&(identical(other.backend, backend) || other.backend == backend)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,isUser,address,backend,apiKey);
@override
String toString() {
return 'UserInfoModel(isUser: $isUser, address: $address, backend: $backend, apiKey: $apiKey)';
}
}
/// @nodoc
abstract mixin class $UserInfoModelCopyWith<$Res> {
factory $UserInfoModelCopyWith(UserInfoModel value, $Res Function(UserInfoModel) _then) = _$UserInfoModelCopyWithImpl;
@useResult
$Res call({
bool? isUser, String? address, String? backend, String? apiKey
});
}
/// @nodoc
class _$UserInfoModelCopyWithImpl<$Res>
implements $UserInfoModelCopyWith<$Res> {
_$UserInfoModelCopyWithImpl(this._self, this._then);
final UserInfoModel _self;
final $Res Function(UserInfoModel) _then;
/// Create a copy of UserInfoModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isUser = freezed,Object? address = freezed,Object? backend = freezed,Object? apiKey = freezed,}) {
return _then(_self.copyWith(
isUser: freezed == isUser ? _self.isUser : isUser // ignore: cast_nullable_to_non_nullable
as bool?,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable
as String?,backend: freezed == backend ? _self.backend : backend // ignore: cast_nullable_to_non_nullable
as String?,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _UserInfoModel implements UserInfoModel {
const _UserInfoModel({this.isUser, this.address, this.backend, this.apiKey});
factory _UserInfoModel.fromJson(Map<String, dynamic> json) => _$UserInfoModelFromJson(json);
@override final bool? isUser;
@override final String? address;
@override final String? backend;
@override final String? apiKey;
/// Create a copy of UserInfoModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$UserInfoModelCopyWith<_UserInfoModel> get copyWith => __$UserInfoModelCopyWithImpl<_UserInfoModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$UserInfoModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UserInfoModel&&(identical(other.isUser, isUser) || other.isUser == isUser)&&(identical(other.address, address) || other.address == address)&&(identical(other.backend, backend) || other.backend == backend)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,isUser,address,backend,apiKey);
@override
String toString() {
return 'UserInfoModel(isUser: $isUser, address: $address, backend: $backend, apiKey: $apiKey)';
}
}
/// @nodoc
abstract mixin class _$UserInfoModelCopyWith<$Res> implements $UserInfoModelCopyWith<$Res> {
factory _$UserInfoModelCopyWith(_UserInfoModel value, $Res Function(_UserInfoModel) _then) = __$UserInfoModelCopyWithImpl;
@override @useResult
$Res call({
bool? isUser, String? address, String? backend, String? apiKey
});
}
/// @nodoc
class __$UserInfoModelCopyWithImpl<$Res>
implements _$UserInfoModelCopyWith<$Res> {
__$UserInfoModelCopyWithImpl(this._self, this._then);
final _UserInfoModel _self;
final $Res Function(_UserInfoModel) _then;
/// Create a copy of UserInfoModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isUser = freezed,Object? address = freezed,Object? backend = freezed,Object? apiKey = freezed,}) {
return _then(_UserInfoModel(
isUser: freezed == isUser ? _self.isUser : isUser // ignore: cast_nullable_to_non_nullable
as bool?,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable
as String?,backend: freezed == backend ? _self.backend : backend // ignore: cast_nullable_to_non_nullable
as String?,apiKey: freezed == apiKey ? _self.apiKey : apiKey // 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 'user_info_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_UserInfoModel _$UserInfoModelFromJson(Map<String, dynamic> json) =>
_UserInfoModel(
isUser: json['is_user'] as bool?,
address: json['address'] as String?,
backend: json['backend'] as String?,
apiKey: json['api_key'] as String?,
);
Map<String, dynamic> _$UserInfoModelToJson(_UserInfoModel instance) =>
<String, dynamic>{
'is_user': instance.isUser,
'address': instance.address,
'backend': instance.backend,
'api_key': instance.apiKey,
};

View File

@@ -0,0 +1,30 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_profile_model.freezed.dart';
part 'user_profile_model.g.dart';
@freezed
abstract class UserProfileModel with _$UserProfileModel {
const factory UserProfileModel({
String? accessToken,
String? expiresIn,
String? scope,
String? expireTime,
String? mobile,
String? fullname,
String? firstname,
String? lastname,
String? city,
String? province,
String? nationalCode,
String? nationalId,
String? birthday,
String? image,
int? baseOrder,
List<String>? role,
}) = _UserProfileModel;
factory UserProfileModel.fromJson(Map<String, dynamic> json) =>
_$UserProfileModelFromJson(json);
}

View File

@@ -0,0 +1,201 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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 'user_profile_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$UserProfileModel {
String? get accessToken; String? get expiresIn; String? get scope; String? get expireTime; String? get mobile; String? get fullname; String? get firstname; String? get lastname; String? get city; String? get province; String? get nationalCode; String? get nationalId; String? get birthday; String? get image; int? get baseOrder; List<String>? get role;
/// Create a copy of UserProfileModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$UserProfileModelCopyWith<UserProfileModel> get copyWith => _$UserProfileModelCopyWithImpl<UserProfileModel>(this as UserProfileModel, _$identity);
/// Serializes this UserProfileModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is UserProfileModel&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.expiresIn, expiresIn) || other.expiresIn == expiresIn)&&(identical(other.scope, scope) || other.scope == scope)&&(identical(other.expireTime, expireTime) || other.expireTime == expireTime)&&(identical(other.mobile, mobile) || other.mobile == mobile)&&(identical(other.fullname, fullname) || other.fullname == fullname)&&(identical(other.firstname, firstname) || other.firstname == firstname)&&(identical(other.lastname, lastname) || other.lastname == lastname)&&(identical(other.city, city) || other.city == city)&&(identical(other.province, province) || other.province == province)&&(identical(other.nationalCode, nationalCode) || other.nationalCode == nationalCode)&&(identical(other.nationalId, nationalId) || other.nationalId == nationalId)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.image, image) || other.image == image)&&(identical(other.baseOrder, baseOrder) || other.baseOrder == baseOrder)&&const DeepCollectionEquality().equals(other.role, role));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,accessToken,expiresIn,scope,expireTime,mobile,fullname,firstname,lastname,city,province,nationalCode,nationalId,birthday,image,baseOrder,const DeepCollectionEquality().hash(role));
@override
String toString() {
return 'UserProfileModel(accessToken: $accessToken, expiresIn: $expiresIn, scope: $scope, expireTime: $expireTime, mobile: $mobile, fullname: $fullname, firstname: $firstname, lastname: $lastname, city: $city, province: $province, nationalCode: $nationalCode, nationalId: $nationalId, birthday: $birthday, image: $image, baseOrder: $baseOrder, role: $role)';
}
}
/// @nodoc
abstract mixin class $UserProfileModelCopyWith<$Res> {
factory $UserProfileModelCopyWith(UserProfileModel value, $Res Function(UserProfileModel) _then) = _$UserProfileModelCopyWithImpl;
@useResult
$Res call({
String? accessToken, String? expiresIn, String? scope, String? expireTime, String? mobile, String? fullname, String? firstname, String? lastname, String? city, String? province, String? nationalCode, String? nationalId, String? birthday, String? image, int? baseOrder, List<String>? role
});
}
/// @nodoc
class _$UserProfileModelCopyWithImpl<$Res>
implements $UserProfileModelCopyWith<$Res> {
_$UserProfileModelCopyWithImpl(this._self, this._then);
final UserProfileModel _self;
final $Res Function(UserProfileModel) _then;
/// Create a copy of UserProfileModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = freezed,Object? expiresIn = freezed,Object? scope = freezed,Object? expireTime = freezed,Object? mobile = freezed,Object? fullname = freezed,Object? firstname = freezed,Object? lastname = freezed,Object? city = freezed,Object? province = freezed,Object? nationalCode = freezed,Object? nationalId = freezed,Object? birthday = freezed,Object? image = freezed,Object? baseOrder = freezed,Object? role = freezed,}) {
return _then(_self.copyWith(
accessToken: freezed == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
as String?,expiresIn: freezed == expiresIn ? _self.expiresIn : expiresIn // ignore: cast_nullable_to_non_nullable
as String?,scope: freezed == scope ? _self.scope : scope // ignore: cast_nullable_to_non_nullable
as String?,expireTime: freezed == expireTime ? _self.expireTime : expireTime // ignore: cast_nullable_to_non_nullable
as String?,mobile: freezed == mobile ? _self.mobile : mobile // ignore: cast_nullable_to_non_nullable
as String?,fullname: freezed == fullname ? _self.fullname : fullname // ignore: cast_nullable_to_non_nullable
as String?,firstname: freezed == firstname ? _self.firstname : firstname // ignore: cast_nullable_to_non_nullable
as String?,lastname: freezed == lastname ? _self.lastname : lastname // ignore: cast_nullable_to_non_nullable
as String?,city: freezed == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
as String?,province: freezed == province ? _self.province : province // ignore: cast_nullable_to_non_nullable
as String?,nationalCode: freezed == nationalCode ? _self.nationalCode : nationalCode // ignore: cast_nullable_to_non_nullable
as String?,nationalId: freezed == nationalId ? _self.nationalId : nationalId // ignore: cast_nullable_to_non_nullable
as String?,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable
as String?,image: freezed == image ? _self.image : image // ignore: cast_nullable_to_non_nullable
as String?,baseOrder: freezed == baseOrder ? _self.baseOrder : baseOrder // ignore: cast_nullable_to_non_nullable
as int?,role: freezed == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
as List<String>?,
));
}
}
/// @nodoc
@JsonSerializable()
class _UserProfileModel implements UserProfileModel {
const _UserProfileModel({this.accessToken, this.expiresIn, this.scope, this.expireTime, this.mobile, this.fullname, this.firstname, this.lastname, this.city, this.province, this.nationalCode, this.nationalId, this.birthday, this.image, this.baseOrder, final List<String>? role}): _role = role;
factory _UserProfileModel.fromJson(Map<String, dynamic> json) => _$UserProfileModelFromJson(json);
@override final String? accessToken;
@override final String? expiresIn;
@override final String? scope;
@override final String? expireTime;
@override final String? mobile;
@override final String? fullname;
@override final String? firstname;
@override final String? lastname;
@override final String? city;
@override final String? province;
@override final String? nationalCode;
@override final String? nationalId;
@override final String? birthday;
@override final String? image;
@override final int? baseOrder;
final List<String>? _role;
@override List<String>? get role {
final value = _role;
if (value == null) return null;
if (_role is EqualUnmodifiableListView) return _role;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
/// Create a copy of UserProfileModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$UserProfileModelCopyWith<_UserProfileModel> get copyWith => __$UserProfileModelCopyWithImpl<_UserProfileModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$UserProfileModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UserProfileModel&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.expiresIn, expiresIn) || other.expiresIn == expiresIn)&&(identical(other.scope, scope) || other.scope == scope)&&(identical(other.expireTime, expireTime) || other.expireTime == expireTime)&&(identical(other.mobile, mobile) || other.mobile == mobile)&&(identical(other.fullname, fullname) || other.fullname == fullname)&&(identical(other.firstname, firstname) || other.firstname == firstname)&&(identical(other.lastname, lastname) || other.lastname == lastname)&&(identical(other.city, city) || other.city == city)&&(identical(other.province, province) || other.province == province)&&(identical(other.nationalCode, nationalCode) || other.nationalCode == nationalCode)&&(identical(other.nationalId, nationalId) || other.nationalId == nationalId)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.image, image) || other.image == image)&&(identical(other.baseOrder, baseOrder) || other.baseOrder == baseOrder)&&const DeepCollectionEquality().equals(other._role, _role));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,accessToken,expiresIn,scope,expireTime,mobile,fullname,firstname,lastname,city,province,nationalCode,nationalId,birthday,image,baseOrder,const DeepCollectionEquality().hash(_role));
@override
String toString() {
return 'UserProfileModel(accessToken: $accessToken, expiresIn: $expiresIn, scope: $scope, expireTime: $expireTime, mobile: $mobile, fullname: $fullname, firstname: $firstname, lastname: $lastname, city: $city, province: $province, nationalCode: $nationalCode, nationalId: $nationalId, birthday: $birthday, image: $image, baseOrder: $baseOrder, role: $role)';
}
}
/// @nodoc
abstract mixin class _$UserProfileModelCopyWith<$Res> implements $UserProfileModelCopyWith<$Res> {
factory _$UserProfileModelCopyWith(_UserProfileModel value, $Res Function(_UserProfileModel) _then) = __$UserProfileModelCopyWithImpl;
@override @useResult
$Res call({
String? accessToken, String? expiresIn, String? scope, String? expireTime, String? mobile, String? fullname, String? firstname, String? lastname, String? city, String? province, String? nationalCode, String? nationalId, String? birthday, String? image, int? baseOrder, List<String>? role
});
}
/// @nodoc
class __$UserProfileModelCopyWithImpl<$Res>
implements _$UserProfileModelCopyWith<$Res> {
__$UserProfileModelCopyWithImpl(this._self, this._then);
final _UserProfileModel _self;
final $Res Function(_UserProfileModel) _then;
/// Create a copy of UserProfileModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = freezed,Object? expiresIn = freezed,Object? scope = freezed,Object? expireTime = freezed,Object? mobile = freezed,Object? fullname = freezed,Object? firstname = freezed,Object? lastname = freezed,Object? city = freezed,Object? province = freezed,Object? nationalCode = freezed,Object? nationalId = freezed,Object? birthday = freezed,Object? image = freezed,Object? baseOrder = freezed,Object? role = freezed,}) {
return _then(_UserProfileModel(
accessToken: freezed == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
as String?,expiresIn: freezed == expiresIn ? _self.expiresIn : expiresIn // ignore: cast_nullable_to_non_nullable
as String?,scope: freezed == scope ? _self.scope : scope // ignore: cast_nullable_to_non_nullable
as String?,expireTime: freezed == expireTime ? _self.expireTime : expireTime // ignore: cast_nullable_to_non_nullable
as String?,mobile: freezed == mobile ? _self.mobile : mobile // ignore: cast_nullable_to_non_nullable
as String?,fullname: freezed == fullname ? _self.fullname : fullname // ignore: cast_nullable_to_non_nullable
as String?,firstname: freezed == firstname ? _self.firstname : firstname // ignore: cast_nullable_to_non_nullable
as String?,lastname: freezed == lastname ? _self.lastname : lastname // ignore: cast_nullable_to_non_nullable
as String?,city: freezed == city ? _self.city : city // ignore: cast_nullable_to_non_nullable
as String?,province: freezed == province ? _self.province : province // ignore: cast_nullable_to_non_nullable
as String?,nationalCode: freezed == nationalCode ? _self.nationalCode : nationalCode // ignore: cast_nullable_to_non_nullable
as String?,nationalId: freezed == nationalId ? _self.nationalId : nationalId // ignore: cast_nullable_to_non_nullable
as String?,birthday: freezed == birthday ? _self.birthday : birthday // ignore: cast_nullable_to_non_nullable
as String?,image: freezed == image ? _self.image : image // ignore: cast_nullable_to_non_nullable
as String?,baseOrder: freezed == baseOrder ? _self.baseOrder : baseOrder // ignore: cast_nullable_to_non_nullable
as int?,role: freezed == role ? _self._role : role // ignore: cast_nullable_to_non_nullable
as List<String>?,
));
}
}
// dart format on

View File

@@ -0,0 +1,47 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_profile_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_UserProfileModel _$UserProfileModelFromJson(Map<String, dynamic> json) =>
_UserProfileModel(
accessToken: json['access_token'] as String?,
expiresIn: json['expires_in'] as String?,
scope: json['scope'] as String?,
expireTime: json['expire_time'] as String?,
mobile: json['mobile'] as String?,
fullname: json['fullname'] as String?,
firstname: json['firstname'] as String?,
lastname: json['lastname'] as String?,
city: json['city'] as String?,
province: json['province'] as String?,
nationalCode: json['national_code'] as String?,
nationalId: json['national_id'] as String?,
birthday: json['birthday'] as String?,
image: json['image'] as String?,
baseOrder: (json['base_order'] as num?)?.toInt(),
role: (json['role'] as List<dynamic>?)?.map((e) => e as String).toList(),
);
Map<String, dynamic> _$UserProfileModelToJson(_UserProfileModel instance) =>
<String, dynamic>{
'access_token': instance.accessToken,
'expires_in': instance.expiresIn,
'scope': instance.scope,
'expire_time': instance.expireTime,
'mobile': instance.mobile,
'fullname': instance.fullname,
'firstname': instance.firstname,
'lastname': instance.lastname,
'city': instance.city,
'province': instance.province,
'national_code': instance.nationalCode,
'national_id': instance.nationalId,
'birthday': instance.birthday,
'image': instance.image,
'base_order': instance.baseOrder,
'role': instance.role,
};

View File

@@ -1,12 +1,11 @@
import 'package:rasadyar_auth/data/models/response/user_info/user_info_model.dart';
import '../models/response/auth/auth_response_model.dart';
import '../models/response/captcha/captcha_response_model.dart';
import '../models/response/user_profile_model/user_profile_model.dart';
abstract class AuthRepository {
Future<AuthResponseModel?> login({
required Map<String, dynamic> authRequest,
});
Future<UserProfileModel?> login({required Map<String, dynamic> authRequest});
Future<CaptchaResponseModel?> captcha();
@@ -14,10 +13,9 @@ abstract class AuthRepository {
Future<bool> hasAuthenticated();
Future<AuthResponseModel?> loginWithRefreshToken({
required Map<String, dynamic> authRequest,
});
Future<UserInfoModel?> getUserInfo(String phoneNumber);
}

View File

@@ -1,3 +1,5 @@
import 'package:rasadyar_auth/data/models/response/user_info/user_info_model.dart';
import 'package:rasadyar_auth/data/models/response/user_profile_model/user_profile_model.dart';
import 'package:rasadyar_core/core.dart';
import '../models/response/auth/auth_response_model.dart';
@@ -11,13 +13,13 @@ class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl(this._httpClient);
@override
Future<AuthResponseModel?> login({
Future<UserProfileModel?> login({
required Map<String, dynamic> authRequest,
}) async {
var res = await _httpClient.post<AuthResponseModel>(
'$_BASE_URL/login/',
var res = await _httpClient.post<UserProfileModel?>(
'/api/login/',
data: authRequest,
fromJson: AuthResponseModel.fromJson,
fromJson: UserProfileModel.fromJson,
headers: {'Content-Type': 'application/json'},
);
return res.data;
@@ -59,4 +61,19 @@ class AuthRepositoryImpl implements AuthRepository {
return response.data ?? false;
}
@override
Future<UserInfoModel?> getUserInfo(String phoneNumber) async {
var res = await _httpClient.post<UserInfoModel?>(
'https://userbackend.rasadyaar.ir/api/send_otp/',
data: {
"mobile": phoneNumber,
"state": ""
},
fromJson: UserInfoModel.fromJson,
headers: {'Content-Type': 'application/json'},
);
return res.data;
}
}

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_auth/data/di/auth_di.dart';
import 'package:rasadyar_auth/data/models/local/user_local/user_local_model.dart';
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
import 'package:rasadyar_core/core.dart';
@@ -14,7 +14,7 @@ class AuthMiddleware extends GetMiddleware {
final accessToken = tokenService.accessToken.value;
if (refreshToken == null || accessToken == null) {
return RouteSettings(name: AuthPaths.moduleList);
return RouteSettings(name: AuthPaths.auth, arguments: Module.chicken);
}
return super.redirect(route);
}

View File

@@ -5,9 +5,12 @@ import 'package:rasadyar_auth/hive_registrar.g.dart';
import 'package:rasadyar_core/core.dart';
class TokenStorageService extends GetxService {
static const String _boxName = 'secureBox';
static const String _tokenBoxName = 'TokenBox';
static const String _appBoxName = 'AppBox';
static const String _accessTokenKey = 'accessToken';
static const String _refreshTokenKey = 'refreshToken';
static const String _baseUrlKey = 'baseUrl';
static const String _apiKey = 'apiKey';
static const String _moduleKey = 'moduleSelected';
final FlutterSecureStorage _secureStorage = FlutterSecureStorage();
@@ -15,6 +18,7 @@ class TokenStorageService extends GetxService {
RxnString accessToken = RxnString();
RxnString refreshToken = RxnString();
RxnString baseurl = RxnString();
Rxn<Module> appModule = Rxn(null);
Future<void> init() async {
@@ -22,41 +26,61 @@ class TokenStorageService extends GetxService {
Hive.registerAdapters();
final String? encryptedKey = await _secureStorage.read(key: 'hive_enc_key');
final encryptionKey = encryptedKey != null ? base64Url.decode(encryptedKey) : Hive.generateSecureKey();
final encryptionKey = encryptedKey != null
? base64Url.decode(encryptedKey)
: Hive.generateSecureKey();
if (encryptedKey == null) {
await _secureStorage.write(key: 'hive_enc_key', value: base64UrlEncode(encryptionKey));
}
await _localStorage.init();
await _localStorage.openBox(_boxName, encryptionCipher: HiveAesCipher(encryptionKey));
await _localStorage.openBox(_tokenBoxName, encryptionCipher: HiveAesCipher(encryptionKey));
await _localStorage.openBox(_appBoxName);
accessToken.value = _localStorage.read<String?>(boxName: _boxName, key: _accessTokenKey);
refreshToken.value = _localStorage.read<String?>(boxName: _boxName, key: _refreshTokenKey);
appModule.value = _localStorage.read<Module?>(boxName: _boxName, key: _moduleKey);
accessToken.value = _localStorage.read<String?>(boxName: _tokenBoxName, key: _accessTokenKey);
refreshToken.value = _localStorage.read<String?>(boxName: _tokenBoxName, key: _refreshTokenKey);
appModule.value = _localStorage.read<Module?>(boxName: _appBoxName, key: _moduleKey);
baseurl.value = _localStorage.read<String?>(boxName: _appBoxName, key: _baseUrlKey);
}
Future<void> saveAccessToken(String token) async {
await _localStorage.save(boxName: _boxName, key: _accessTokenKey, value: token);
await _localStorage.save(boxName: _tokenBoxName, key: _accessTokenKey, value: token);
accessToken.value = token;
accessToken.refresh();
}
Future<void> saveRefreshToken(String token) async {
await _localStorage.save(boxName: _boxName, key: _refreshTokenKey, value: token);
await _localStorage.save(boxName: _tokenBoxName, key: _refreshTokenKey, value: token);
refreshToken.value = token;
refreshToken.refresh();
}
Future<void> saveModule(Module input) async {
await _localStorage.save(boxName: _boxName, key: _moduleKey, value: input);
await _localStorage.save(boxName: _tokenBoxName, key: _moduleKey, value: input);
appModule.value = input;
appModule.refresh();
}
Future<void> deleteTokens() async {
await _localStorage.clear(_boxName);
await _localStorage.clear(_tokenBoxName);
accessToken.value = null;
refreshToken.value = null;
}
Future<void> saveBaseUrl(String url) async {
await _localStorage.save(boxName: _appBoxName, key: _baseUrlKey, value: url);
baseurl.value = url;
baseurl.refresh();
}
void getBaseUrl() {
var url = _localStorage.read(boxName: _appBoxName, key: _baseUrlKey);
baseurl.value = url;
baseurl.refresh();
}
Future<void> saveApiKey(String key) async {
await _localStorage.save(boxName: _tokenBoxName, key: _apiKey, value: key);
}
}

View File

@@ -1,12 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:rasadyar_auth/auth.dart';
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
import 'package:rasadyar_core/core.dart';
import '../models/response/auth/auth_response_model.dart';
import '../services/token_storage_service.dart';
Future<void> safeCall<T>({
Future<T?> safeCall<T>({
required AppAsyncCallback<T> call,
Function(T result)? onSuccess,
ErrorCallback? onError,
@@ -18,13 +12,8 @@ Future<void> safeCall<T>({
bool showSnackBar = false,
Function()? onShowLoading,
Function()? onHideLoading,
Function()? onShowSuccessMessage,
Function()? onShowErrorMessage,
}) {
final authRepository = diAuth.get<AuthRepositoryImpl>();
TokenStorageService tokenStorageService = Get.find<TokenStorageService>();
return gSafeCall(
return gSafeCall<T>(
call: call,
onSuccess: onSuccess,
onError: onError,
@@ -32,30 +21,7 @@ Future<void> safeCall<T>({
showLoading: showLoading,
showError: showError,
showSuccess: showSuccess,
showToast: showToast,
showSnackBar: showSnackBar,
onShowLoading: onShowLoading,
onHideLoading: onHideLoading,
onShowSuccessMessage: onShowSuccessMessage,
onShowErrorMessage: onShowErrorMessage,
retryOnAuthError: true,
onTokenRefresh: () {
var token = tokenStorageService.refreshToken.value;
authRepository
.loginWithRefreshToken(authRequest: {"refresh_token": token})
.then((value) async {
if (value is AuthResponseModel) {
await tokenStorageService.saveAccessToken(value.access!);
} else {
throw Exception("Failed to refresh token");
}
})
.catchError((error) {
if (kDebugMode) {
print('Error during token refresh: $error');
}
throw error;
});
},
);
}
}

View File

@@ -7,9 +7,8 @@ import 'package:rasadyar_auth/data/models/local/user_local/user_local_model.dart
extension HiveRegistrar on HiveInterface {
void registerAdapters() {
registerAdapter(UserLocalModelAdapter());
registerAdapter(ModuleAdapter());
registerAdapter(UserLocalModelAdapter());
}
}

View File

@@ -4,7 +4,8 @@ import 'package:flutter/material.dart';
import 'package:rasadyar_auth/auth.dart';
import 'package:rasadyar_auth/data/common/dio_error_handler.dart';
import 'package:rasadyar_auth/data/models/request/login_request/login_request_model.dart';
import 'package:rasadyar_auth/data/models/response/auth/auth_response_model.dart';
import 'package:rasadyar_auth/data/models/response/user_info/user_info_model.dart';
import 'package:rasadyar_auth/data/models/response/user_profile_model/user_profile_model.dart';
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
import 'package:rasadyar_auth/data/utils/safe_call.dart';
@@ -26,14 +27,14 @@ class AuthLogic extends GetxController {
Rx<GlobalKey<FormState>> formKeySentOtp = GlobalKey<FormState>().obs;
Rx<TextEditingController> usernameController = TextEditingController().obs;
Rx<TextEditingController> passwordController = TextEditingController().obs;
Rx<TextEditingController> phoneOtpNumberController =
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;
@@ -70,15 +71,11 @@ class AuthLogic extends GetxController {
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
@override
void onInit() {
super.onInit();
}
@override
void onReady() {
super.onReady();
iLog('module111 : ${_module.toString()}');
iLog('module selected : ${_module.toString()}');
}
@override
@@ -88,8 +85,7 @@ class AuthLogic extends GetxController {
}
bool _isFormValid() {
final isCaptchaValid =
captchaController.formKey.currentState?.validate() ?? false;
final isCaptchaValid = captchaController.formKey.currentState?.validate() ?? false;
final isFormValid = formKey.currentState?.validate() ?? false;
return isCaptchaValid && isFormValid;
}
@@ -108,7 +104,7 @@ class AuthLogic extends GetxController {
);
}
Future<void> submitLoginForm() async {
/*Future<void> submitLoginForm() async {
if (!_isFormValid()) return;
iLog('module222 : ${_module.toString()}');
final loginRequestModel = _buildLoginRequest();
@@ -128,5 +124,52 @@ class AuthLogic extends GetxController {
},
);
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": phoneNumberController.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

@@ -27,28 +27,31 @@ class AuthPage extends GetView<AuthLogic> {
}
}, controller.authType),
SizedBox(height: 50),
SizedBox(height: 20),
RichText(
text: TextSpan(
children: [
TextSpan(
text: 'مطالعه بیانیه ',
style: AppFonts.yekan14.copyWith(
color: AppColor.darkGreyDark,
),
style: AppFonts.yekan16.copyWith(color: AppColor.darkGreyDark),
),
TextSpan(
recognizer: TapGestureRecognizer()
..onTap = () {},
..onTap = () {
Get.bottomSheet(
privacyPolicyWidget(),
isScrollControlled: true,
enableDrag: true,
ignoreSafeArea: false,
);
},
text: 'حریم خصوصی',
style: AppFonts.yekan14.copyWith(
color: AppColor.blueNormal,
),
style: AppFonts.yekan16.copyWith(color: AppColor.blueNormal),
),
],
),
),
SizedBox(height: 18),
/* SizedBox(height: 18),
ObxValue((types) {
return RichText(
@@ -80,7 +83,7 @@ class AuthPage extends GetView<AuthLogic> {
],
),
);
}, controller.authType),
}, controller.authType),*/
],
),
),
@@ -92,120 +95,201 @@ class AuthPage extends GetView<AuthLogic> {
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 50),
child: Form(
key: controller.formKey,
child: Column(
children: [
ObxValue(
(phoneController) =>
RTextField(
label: 'نام کاربری',
maxLength: 11,
maxLines: 1,
controller: phoneController.value,
keyboardType: TextInputType.text,
initText: phoneController.value.text,
onChanged: (value) {
phoneController.value.text = value;
phoneController.refresh();
},
prefixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 6, 8),
child: Assets.vec.callSvg.svg(
width: 12,
height: 12,
),
),
suffixIcon:
phoneController.value.text
.trim()
.isNotEmpty
? clearButton(() {
phoneController.value.clear();
phoneController.refresh();
})
: null,
validator: (value) {
if (value == null || value.isEmpty) {
return '⚠️ شماره موبایل را وارد کنید';
}
/*else if (value.length < 11) {
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,
),
),
controller.usernameController,
),
const SizedBox(height: 26),
ObxValue(
(passwordController) =>
RTextField(
label: 'رمز عبور',
filled: false,
controller: passwordController.value,
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),
ObxValue((data) {
return RElevated(
text: 'ورود',
isLoading: data.value,
onPressed: () async {
await controller.submitLoginForm();
child: AutofillGroup(
child: Column(
children: [
RTextField(
label: 'نام کاربری',
maxLength: 11,
maxLines: 1,
controller: controller.phoneNumberController.value,
keyboardType: TextInputType.number,
initText: controller.phoneNumberController.value.text,
autofillHints: [AutofillHints.username],
onChanged: (value) async {
controller.phoneNumberController.value.text = value;
controller.phoneNumberController.refresh();
if (value.length == 11) {
await controller.getUserInfo(value);
}
},
width: Get.width,
height: 48,
);
}, controller.isLoading),
],
prefixIcon: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 6, 8),
child: Assets.vec.callSvg.svg(width: 12, height: 12),
),
suffixIcon: controller.phoneNumberController.value.text.trim().isNotEmpty
? clearButton(() {
controller.phoneNumberController.value.clear();
controller.phoneNumberController.refresh();
})
: null,
validator: (value) {
if (value == null || value.isEmpty) {
return '⚠️ شماره موبایل را وارد کنید';
} else if (value.length < 11) {
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,
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.submitLoginForm2();
},
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(

View File

@@ -7,6 +7,7 @@ class ModulesLogic extends GetxController {
List<ModuleModel> moduleList=[
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.chicken),
];

View File

@@ -1,15 +1,17 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:rasadyar_auth/data/di/auth_di.dart';
import 'package:rasadyar_auth/data/models/response/captcha/captcha_response_model.dart';
import 'package:rasadyar_auth/data/repositories/auth_repository_imp.dart';
import 'package:rasadyar_auth/data/utils/safe_call.dart';
import 'package:rasadyar_core/core.dart';
class CaptchaWidgetLogic extends GetxController with StateMixin<CaptchaResponseModel> {
Rx<TextEditingController> textController = TextEditingController().obs;
TextEditingController textController = TextEditingController();
RxnString captchaKey = RxnString();
GlobalKey<FormState> formKey = GlobalKey<FormState>();
AuthRepositoryImpl authRepository = diAuth.get<AuthRepositoryImpl>();
final Random random = Random();
@override
void onInit() {
@@ -20,22 +22,17 @@ class CaptchaWidgetLogic extends GetxController with StateMixin<CaptchaResponseM
@override
void onClose() {
textController.value.dispose();
textController.clear();
textController.dispose();
super.onClose();
}
Future<void> getCaptcha() async {
change(null, status: RxStatus.loading());
textController.value.clear();
safeCall(
call: () async => await authRepository.captcha(),
onSuccess: (value) {
captchaKey.value = value?.captchaKey;
change(value, status: RxStatus.success());
},
onError: (error, stackTrace) {
change(null, status: RxStatus.error(error.toString()));
},
);
textController.clear();
await Future.delayed(Duration(milliseconds: 800));
captchaKey.value = (random.nextInt(900000)+100000).toString();
change(value, status: RxStatus.success());
}
}

View File

@@ -1,7 +1,8 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:rasadyar_auth/presentation/pages/auth/logic.dart';
import 'package:rasadyar_auth/presentation/widget/clear_button.dart';
import 'package:rasadyar_core/core.dart';
@@ -16,73 +17,93 @@ class CaptchaWidget extends GetView<CaptchaWidgetLogic> {
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),
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) => Center(
child: Text(
controller.captchaKey.value ?? 'دوباره سعی کنید',
style: AppFonts.yekan24Bold,
),
onError: (error) {
return const Center(
child: Text(
'خطا در بارگذاری کد امنیتی',
style: AppFonts.yekan13,
),
);
},
),
)),
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: ObxValue((data) {
return RTextField(
label: 'کد امنیتی',
controller: data.value,
keyboardType: TextInputType.numberWithOptions(
decimal: false,
signed: false,
),
maxLines: 1,
maxLength: 6,
suffixIcon:
(data.value.text
.trim()
.isNotEmpty ?? false)
? clearButton(
() => controller.textController.value.clear(),
)
: null,
child: RTextField(
label: 'کد امنیتی',
controller: controller.textController,
keyboardType: TextInputType.numberWithOptions(decimal: false, signed: false),
maxLines: 1,
maxLength: 6,
suffixIcon: (controller.textController.text.trim().isNotEmpty ?? false)
? clearButton(() => controller.textController.clear())
: null,
onSubmitted: (data) {},
validator: (value) {
if (value == null || value.isEmpty) {
return 'کد امنیتی را وارد کنید';
validator: (value) {
if (value == null || value.isEmpty) {
return 'کد امنیتی را وارد کنید';
} else if (controller.captchaKey.value != null &&
controller.captchaKey.value != value) {
return 'کد امنیتی اشتباه است';
}
return null;
},
onChanged: (pass) {
if (pass.length == 6) {
if (controller.formKey.currentState?.validate() ?? false) {
Get.find<AuthLogic>().isDisabled.value = false;
}
return null;
},
style: AppFonts.yekan13,
);
}, controller.textController),
}
},
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_auth
description: "A new Flutter project."
version: 1.0.2
version: 1.0.3
publish_to: 'none'
@@ -15,17 +15,17 @@ dependencies:
rasadyar_core:
path: ../core
##code generation
freezed_annotation: ^3.0.0
freezed_annotation: ^3.1.0
json_annotation: ^4.9.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter_lints: ^6.0.0
##code generation
build_runner: ^2.4.15
hive_ce_generator: ^1.9.1
freezed: ^3.0.6
json_serializable: ^6.9.4
build_runner: ^2.6.0
hive_ce_generator: ^1.9.3
freezed: ^3.2.0
json_serializable: ^6.10.0
##test
mocktail: ^1.0.4