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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import 'package:rasadyar_core/hive_registrar.g.dart';
class TokenStorageService extends GetxService {
static const String _tokenBoxName = 'TokenBox';
static const String _userPassBox = 'UserPassBox';
static const String _appBoxName = 'AppBox';
static const String _accessTokenKey = 'accessToken';
static const String _refreshTokenKey = 'refreshToken';
@@ -21,7 +22,6 @@ class TokenStorageService extends GetxService {
Rxn<Module> appModule = Rxn(null);
Future<void> init() async {
Hive.registerAdapters();
final String? encryptedKey = await _secureStorage.read(key: 'hive_enc_key');
@@ -36,6 +36,7 @@ class TokenStorageService extends GetxService {
await _localStorage.init();
await _localStorage.openBox(_tokenBoxName, encryptionCipher: HiveAesCipher(encryptionKey));
await _localStorage.openBox(_appBoxName);
await _localStorage.openBox<UserLocalModel>(_userPassBox);
accessToken.value = _localStorage.read<String?>(boxName: _tokenBoxName, key: _accessTokenKey);
refreshToken.value = _localStorage.read<String?>(boxName: _tokenBoxName, key: _refreshTokenKey);
@@ -88,4 +89,18 @@ class TokenStorageService extends GetxService {
Future<void> saveApiKey(String key) async {
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) {
if (contains(item)) {

View File

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

View File

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