feat : remember me for chicken module

This commit is contained in:
2025-08-31 10:16:18 +03:30
parent 04d44b2615
commit 9fab48aee1
11 changed files with 155 additions and 123 deletions

View File

@@ -15,7 +15,6 @@ import 'package:rasadyar_core/core.dart';
GetIt diChicken = GetIt.instance; GetIt diChicken = GetIt.instance;
Future<void> setupChickenDI() async { Future<void> setupChickenDI() async {
tLog("setup 1");
diChicken.registerSingleton(DioErrorHandler()); diChicken.registerSingleton(DioErrorHandler());
var tokenService = Get.find<TokenStorageService>(); var tokenService = Get.find<TokenStorageService>();
@@ -52,7 +51,6 @@ Future<void> setupChickenDI() async {
diChicken.registerLazySingleton<AuthRepository>( diChicken.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(diChicken.get<AuthRemoteDataSource>()), () => AuthRepositoryImpl(diChicken.get<AuthRemoteDataSource>()),
instanceName: 'oldRepo',
); );
diChicken.registerLazySingleton<ChickenRemoteDatasource>( diChicken.registerLazySingleton<ChickenRemoteDatasource>(
@@ -95,11 +93,10 @@ Future<void> newSetupAuthDI(String newUrl) async {
); );
} }
if (diChicken.isRegistered<AuthRepository>(instanceName: 'oldRepo')) { if (diChicken.isRegistered<AuthRepository>()) {
await diChicken.unregister<AuthRepository>(instanceName: 'oldRepo'); await diChicken.unregister<AuthRepository>();
diChicken.registerLazySingleton<AuthRepository>( diChicken.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(diChicken.get<AuthRemoteDataSource>()), () => AuthRepositoryImpl(diChicken.get<AuthRemoteDataSource>()),
instanceName: 'newRepo',
); );
} }

View File

@@ -23,6 +23,7 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin {
late AnimationController _textAnimationController; late AnimationController _textAnimationController;
late Animation<double> textAnimation; late Animation<double> textAnimation;
RxBool showCard = false.obs; RxBool showCard = false.obs;
RxBool rememberMe = false.obs;
Rx<GlobalKey<FormState>> formKeyOtp = GlobalKey<FormState>().obs; Rx<GlobalKey<FormState>> formKeyOtp = GlobalKey<FormState>().obs;
Rx<GlobalKey<FormState>> formKeySentOtp = GlobalKey<FormState>().obs; Rx<GlobalKey<FormState>> formKeySentOtp = GlobalKey<FormState>().obs;
@@ -45,7 +46,7 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin {
RxInt secondsRemaining = 120.obs; RxInt secondsRemaining = 120.obs;
Timer? _timer; Timer? _timer;
AuthRepository authRepository = diChicken.get<AuthRepository>(instanceName: 'oldRepo'); AuthRepository authRepository = diChicken.get<AuthRepository>();
final Module _module = Get.arguments; final Module _module = Get.arguments;
@@ -60,6 +61,8 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin {
}); });
textAnimation = CurvedAnimation(parent: _textAnimationController, curve: Curves.easeInOut); textAnimation = CurvedAnimation(parent: _textAnimationController, curve: Curves.easeInOut);
initUserPassData();
} }
@override @override
@@ -118,7 +121,7 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin {
Future<void> submitLoginForm() async { Future<void> submitLoginForm() async {
if (!_isFormValid()) return; if (!_isFormValid()) return;
AuthRepository authTmp = diChicken.get<AuthRepository>(instanceName: 'newRepo'); AuthRepository authTmp = diChicken.get<AuthRepository>();
isLoading.value = true; isLoading.value = true;
await safeCall<UserProfileModel?>( await safeCall<UserProfileModel?>(
call: () => authTmp.login( call: () => authTmp.login(
@@ -131,6 +134,16 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin {
await tokenStorageService.saveModule(_module); await tokenStorageService.saveModule(_module);
await tokenStorageService.saveAccessToken(result?.accessToken ?? ''); await tokenStorageService.saveAccessToken(result?.accessToken ?? '');
await tokenStorageService.saveRefreshToken(result?.accessToken ?? ''); await tokenStorageService.saveRefreshToken(result?.accessToken ?? '');
if (rememberMe.value) {
await tokenStorageService.saveUserPass(
UserLocalModel(
username: usernameController.value.text,
password: passwordController.value.text,
module: _module,
),
);
}
Get.offAndToNamed(ChickenRoutes.init); Get.offAndToNamed(ChickenRoutes.init);
}, },
onError: (error, stackTrace) { onError: (error, stackTrace) {
@@ -163,4 +176,13 @@ class AuthLogic extends GetxController with GetTickerProviderStateMixin {
); );
isLoading.value = false; isLoading.value = false;
} }
void initUserPassData() {
UserLocalModel? userPass = tokenStorageService.getUserPass(_module);
if (userPass != null) {
usernameController.value.text = userPass.username ?? '';
passwordController.value.text = userPass.password ?? '';
rememberMe.value = true;
}
}
} }

View File

@@ -197,7 +197,28 @@ class AuthPage extends GetView<AuthLogic> {
), ),
SizedBox(height: 26), SizedBox(height: 26),
CaptchaWidget(), CaptchaWidget(),
SizedBox(height: 23),
Row(
children: [
ObxValue((data) {
return Checkbox(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity(horizontal: -4, vertical: 4),
tristate: true,
value: data.value,
onChanged: (value) {
data.value = value ?? false;
},
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
activeColor: AppColor.blueNormal,
);
}, controller.rememberMe),
Text(
'مرا به خاطر بسپار',
style: AppFonts.yekan14.copyWith(color: AppColor.darkGreyDark),
),
],
),
Obx(() { Obx(() {
return RElevated( return RElevated(

View File

@@ -3,9 +3,9 @@ import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/chicken.dart'; import 'package:rasadyar_chicken/chicken.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart'; import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/models/response/user_profile/user_profile.dart'; import 'package:rasadyar_chicken/data/models/response/user_profile/user_profile.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
import 'logic.dart'; import 'logic.dart';
@@ -27,49 +27,41 @@ class ProfilePage extends GetView<ProfileLogic> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Row(), Row(),
ObxValue( ObxValue((data) {
(data) { final status = data.value.status;
final status = data.value.status;
if (status == ResourceStatus.loading) { if (status == ResourceStatus.loading) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: CupertinoActivityIndicator(color: AppColor
.greenNormal,)),
);
}
if (status == ResourceStatus.error) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: Text('خطا در دریافت اطلاعات')),
);
}
// Default UI
return Container( return Container(
width: 128.w, width: 128.w,
height: 128.h, height: 128.h,
decoration: BoxDecoration( child: Center(child: CupertinoActivityIndicator(color: AppColor.greenNormal)),
shape: BoxShape.circle,
color: AppColor.blueLightActive,
),
child: Center(
child: CircleAvatar(
radius: 64.w,
backgroundImage:
NetworkImage(data.value.data!.image!)
),
),
); );
}, }
controller.userProfile,
) if (status == ResourceStatus.error) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: Text('خطا در دریافت اطلاعات')),
);
}
// Default UI
return Container(
width: 128.w,
height: 128.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.blueLightActive,
),
child: Center(
child: CircleAvatar(
radius: 64.w,
backgroundImage: NetworkImage(data.value.data!.image!),
),
),
);
}, controller.userProfile),
], ],
), ),
), ),
@@ -126,17 +118,16 @@ class ProfilePage extends GetView<ProfileLogic> {
Container invoiceIssuanceInformation() => Container(); Container invoiceIssuanceInformation() => Container();
Widget bankInformationWidget() => Widget bankInformationWidget() => Column(
Column( spacing: 16,
spacing: 16, children: [
children: [ itemList(title: 'نام بانک', content: 'سامان'),
itemList(title: 'نام بانک', content: 'سامان'), itemList(title: 'نام صاحب حساب', content: 'رضا رضایی'),
itemList(title: 'نام صاحب حساب', content: 'رضا رضایی'), itemList(title: 'شماره کارت ', content: '54154545415'),
itemList(title: 'شماره کارت ', content: '54154545415'), itemList(title: 'شماره حساب', content: '62565263263652'),
itemList(title: 'شماره حساب', content: '62565263263652'), itemList(title: 'شماره شبا', content: '62565263263652'),
itemList(title: 'شماره شبا', content: '62565263263652'), ],
], );
);
Widget userProfileInformation() { Widget userProfileInformation() {
return ObxValue((data) { return ObxValue((data) {
@@ -152,7 +143,10 @@ class ProfilePage extends GetView<ProfileLogic> {
buildRowOnTapped( buildRowOnTapped(
onTap: () { onTap: () {
Get.bottomSheet( Get.bottomSheet(
userInformationBottomSheet(), isScrollControlled: true, ignoreSafeArea: false); userInformationBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
);
}, },
titleWidget: Column( titleWidget: Column(
spacing: 3, spacing: 3,
@@ -216,34 +210,33 @@ class ProfilePage extends GetView<ProfileLogic> {
required String content, required String content,
String? icon, String? icon,
bool hasColoredBox = false, bool hasColoredBox = false,
}) => }) => Container(
Container( padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 6.h),
padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 6.h), decoration: BoxDecoration(
decoration: BoxDecoration( color: hasColoredBox ? AppColor.greenLight : Colors.transparent,
color: hasColoredBox ? AppColor.greenLight : Colors.transparent, borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(8), border: hasColoredBox
border: hasColoredBox ? Border.all(width: 0.25, color: AppColor.bgDark)
? Border.all(width: 0.25, color: AppColor.bgDark) : Border.all(width: 0, color: Colors.transparent),
: Border.all(width: 0, color: Colors.transparent), ),
), child: Row(
child: Row( spacing: 4,
spacing: 4, children: [
children: [ if (icon != null)
if (icon != null) Padding(
Padding( padding: const EdgeInsets.only(left: 8.0),
padding: const EdgeInsets.only(left: 8.0), child: SvgGenImage.vec(icon).svg(
child: SvgGenImage.vec(icon).svg( width: 20.w,
width: 20.w, height: 20.h,
height: 20.h, colorFilter: ColorFilter.mode(AppColor.mediumGreyNormalActive, BlendMode.srcIn),
colorFilter: ColorFilter.mode(AppColor.mediumGreyNormalActive, BlendMode.srcIn), ),
), ),
), Text(title, style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyNormalActive)),
Text(title, style: AppFonts.yekan12.copyWith(color: AppColor.mediumGreyNormalActive)), Spacer(),
Spacer(), Text(content, style: AppFonts.yekan13.copyWith(color: AppColor.mediumGreyNormalHover)),
Text(content, style: AppFonts.yekan13.copyWith(color: AppColor.mediumGreyNormalHover)), ],
], ),
), );
);
Widget cardActionWidget({ Widget cardActionWidget({
required String title, required String title,
@@ -271,7 +264,7 @@ class ProfilePage extends GetView<ProfileLogic> {
width: 40, width: 40,
height: 40, height: 40,
colorFilter: colorFilter:
color ?? color ??
ColorFilter.mode( ColorFilter.mode(
selected ? AppColor.blueNormal : AppColor.whiteLight, selected ? AppColor.blueNormal : AppColor.whiteLight,
BlendMode.srcIn, BlendMode.srcIn,
@@ -355,8 +348,6 @@ class ProfilePage extends GetView<ProfileLogic> {
}, controller.birthDate), }, controller.birthDate),
SizedBox(), SizedBox(),
], ],
), ),
), ),
@@ -372,8 +363,10 @@ class ProfilePage extends GetView<ProfileLogic> {
child: Column( child: Column(
spacing: 8, spacing: 8,
children: [ children: [
Text('عکس پروفایل', Text(
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal)), 'عکس پروفایل',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
ObxValue((data) { ObxValue((data) {
return Container( return Container(
width: Get.width, width: Get.width,
@@ -386,9 +379,11 @@ class ProfilePage extends GetView<ProfileLogic> {
child: Center( child: Center(
child: data.value == null child: data.value == null
? Padding( ? Padding(
padding: const EdgeInsets.fromLTRB(30, 10, 10, 30), padding: const EdgeInsets.fromLTRB(30, 10, 10, 30),
child: Image.network(controller.userProfile.value.data?.image ?? '') child: Image.network(
) controller.userProfile.value.data?.image ?? '',
),
)
: Image.file(File(data.value!.path), fit: BoxFit.cover), : Image.file(File(data.value!.path), fit: BoxFit.cover),
), ),
); );
@@ -447,7 +442,7 @@ class ProfilePage extends GetView<ProfileLogic> {
Get.back(); Get.back();
}, },
); );
},controller.isOnLoading), }, controller.isOnLoading),
ROutlinedElevated( ROutlinedElevated(
height: 40.h, height: 40.h,
text: 'انصراف', text: 'انصراف',
@@ -618,7 +613,7 @@ class ProfilePage extends GetView<ProfileLogic> {
text: 'خروج', text: 'خروج',
backgroundColor: AppColor.error, backgroundColor: AppColor.error,
onPressed: () async { onPressed: () async {
await controller.rootLogic.tokenService.deleteTokens().then((value) { await controller.rootLogic.tokenService.deleteTokens().then((value){
Get.back(); Get.back();
Get.offAllNamed(ChickenRoutes.auth, arguments: Module.chicken); Get.offAllNamed(ChickenRoutes.auth, arguments: Module.chicken);
}); });

View File

@@ -2,7 +2,8 @@ library;
export 'package:android_intent_plus/android_intent.dart'; export 'package:android_intent_plus/android_intent.dart';
export 'package:android_intent_plus/flag.dart'; export 'package:android_intent_plus/flag.dart';
export 'package:cached_network_image/cached_network_image.dart' ; export 'package:cached_network_image/cached_network_image.dart';
export 'package:collection/collection.dart';
export 'package:connectivity_plus/connectivity_plus.dart'; export 'package:connectivity_plus/connectivity_plus.dart';
export 'package:device_info_plus/device_info_plus.dart'; export 'package:device_info_plus/device_info_plus.dart';
export 'package:dio/dio.dart'; export 'package:dio/dio.dart';

View File

@@ -1,5 +1,4 @@
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
import 'package:rasadyar_core/utils/local/local_utils.dart';
part 'user_local_model.g.dart'; part 'user_local_model.g.dart';
@@ -13,27 +12,18 @@ class UserLocalModel extends HiveObject {
String? token; String? token;
@HiveField(3) @HiveField(3)
String? refreshToken; String? refreshToken;
@HiveField(4)
String? name;
@HiveField(5) @HiveField(5)
Module? module; Module? module;
@HiveField(6) @HiveField(6)
String? backend; String? backend;
@HiveField(7)
String? apiKey;
UserLocalModel({ UserLocalModel({
this.username, this.username,
this.password, this.password,
this.token, this.token,
this.refreshToken, this.refreshToken,
this.name,
this.module, this.module,
this.backend, this.backend,
this.apiKey,
}); });
UserLocalModel copyWith({ UserLocalModel copyWith({
@@ -41,20 +31,16 @@ class UserLocalModel extends HiveObject {
String? password, String? password,
String? token, String? token,
String? refreshToken, String? refreshToken,
String? name,
Module? module, Module? module,
String? backend, String? backend,
String? apiKey,
}) { }) {
return UserLocalModel( return UserLocalModel(
username: username ?? this.username, username: username ?? this.username,
password: password ?? this.password, password: password ?? this.password,
token: token ?? this.token, token: token ?? this.token,
refreshToken: refreshToken ?? this.refreshToken, refreshToken: refreshToken ?? this.refreshToken,
name: name ?? this.name,
module: module ?? this.module, module: module ?? this.module,
backend: backend ?? this.backend, backend: backend ?? this.backend,
apiKey: apiKey ?? this.apiKey,
); );
} }
} }

View File

@@ -21,17 +21,15 @@ class UserLocalModelAdapter extends TypeAdapter<UserLocalModel> {
password: fields[1] as String?, password: fields[1] as String?,
token: fields[2] as String?, token: fields[2] as String?,
refreshToken: fields[3] as String?, refreshToken: fields[3] as String?,
name: fields[4] as String?,
module: fields[5] as Module?, module: fields[5] as Module?,
backend: fields[6] as String?, backend: fields[6] as String?,
apiKey: fields[7] as String?,
); );
} }
@override @override
void write(BinaryWriter writer, UserLocalModel obj) { void write(BinaryWriter writer, UserLocalModel obj) {
writer writer
..writeByte(8) ..writeByte(6)
..writeByte(0) ..writeByte(0)
..write(obj.username) ..write(obj.username)
..writeByte(1) ..writeByte(1)
@@ -40,14 +38,10 @@ class UserLocalModelAdapter extends TypeAdapter<UserLocalModel> {
..write(obj.token) ..write(obj.token)
..writeByte(3) ..writeByte(3)
..write(obj.refreshToken) ..write(obj.refreshToken)
..writeByte(4)
..write(obj.name)
..writeByte(5) ..writeByte(5)
..write(obj.module) ..write(obj.module)
..writeByte(6) ..writeByte(6)
..write(obj.backend) ..write(obj.backend);
..writeByte(7)
..write(obj.apiKey);
} }
@override @override

View File

@@ -5,6 +5,7 @@ import 'package:rasadyar_core/hive_registrar.g.dart';
class TokenStorageService extends GetxService { class TokenStorageService extends GetxService {
static const String _tokenBoxName = 'TokenBox'; static const String _tokenBoxName = 'TokenBox';
static const String _userPassBox = 'UserPassBox';
static const String _appBoxName = 'AppBox'; static const String _appBoxName = 'AppBox';
static const String _accessTokenKey = 'accessToken'; static const String _accessTokenKey = 'accessToken';
static const String _refreshTokenKey = 'refreshToken'; static const String _refreshTokenKey = 'refreshToken';
@@ -21,7 +22,6 @@ class TokenStorageService extends GetxService {
Rxn<Module> appModule = Rxn(null); Rxn<Module> appModule = Rxn(null);
Future<void> init() async { Future<void> init() async {
Hive.registerAdapters(); Hive.registerAdapters();
final String? encryptedKey = await _secureStorage.read(key: 'hive_enc_key'); final String? encryptedKey = await _secureStorage.read(key: 'hive_enc_key');
@@ -36,6 +36,7 @@ class TokenStorageService extends GetxService {
await _localStorage.init(); await _localStorage.init();
await _localStorage.openBox(_tokenBoxName, encryptionCipher: HiveAesCipher(encryptionKey)); await _localStorage.openBox(_tokenBoxName, encryptionCipher: HiveAesCipher(encryptionKey));
await _localStorage.openBox(_appBoxName); await _localStorage.openBox(_appBoxName);
await _localStorage.openBox<UserLocalModel>(_userPassBox);
accessToken.value = _localStorage.read<String?>(boxName: _tokenBoxName, key: _accessTokenKey); accessToken.value = _localStorage.read<String?>(boxName: _tokenBoxName, key: _accessTokenKey);
refreshToken.value = _localStorage.read<String?>(boxName: _tokenBoxName, key: _refreshTokenKey); refreshToken.value = _localStorage.read<String?>(boxName: _tokenBoxName, key: _refreshTokenKey);
@@ -88,4 +89,18 @@ class TokenStorageService extends GetxService {
Future<void> saveApiKey(String key) async { Future<void> saveApiKey(String key) async {
await _localStorage.save(boxName: _tokenBoxName, key: _apiKey, value: key); await _localStorage.save(boxName: _tokenBoxName, key: _apiKey, value: key);
} }
Future<void> saveUserPass(UserLocalModel model) async {
await _localStorage.save<UserLocalModel>(
boxName: _userPassBox,
key: model.module!.name,
value: model,
);
}
UserLocalModel? getUserPass(Module module) {
return _localStorage
.readBox<UserLocalModel>(boxName: _userPassBox)
?.firstWhereOrNull((element) => element.module == module);
}
} }

View File

@@ -1,4 +1,4 @@
extension ListExtensions<T> on List<T> { extension AppListExtensions<T> on List<T> {
void toggle(T item) { void toggle(T item) {
if (contains(item)) { if (contains(item)) {

View File

@@ -202,7 +202,7 @@ packages:
source: hosted source: hosted
version: "4.10.1" version: "4.10.1"
collection: collection:
dependency: transitive dependency: "direct main"
description: description:
name: collection name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"

View File

@@ -62,6 +62,7 @@ dependencies:
permission_handler: ^12.0.1 permission_handler: ^12.0.1
persian_datetime_picker: ^3.1.1 persian_datetime_picker: ^3.1.1
encrypt: ^5.0.3 encrypt: ^5.0.3
collection: ^1.19.1
#L10N tools #L10N tools
intl: ^0.20.2 intl: ^0.20.2