feat : change app inspector and exception handling

This commit is contained in:
2025-07-12 17:06:29 +03:30
parent e52674de37
commit 0fc16569a6
24 changed files with 472 additions and 322 deletions

View File

@@ -5,9 +5,17 @@ import 'package:rasadyar_core/core.dart';
final di = GetIt.instance;
Future<void> setupInjection() async{
await setupAuthDI();
Future<void> setupPreInjection() async{
await setupAllCoreProvider();
await setupAuthDI();
}
Future<void> setupInjection() async{
await setupChickenDI();
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_app/infrastructure/di/di.dart';
import 'package:rasadyar_chicken/chicken.dart';
import 'package:rasadyar_core/core.dart';
class CustomNavigationObserver extends NavigatorObserver {
bool _isWorkDone = false;
void setInjectionDone() {
_isWorkDone = true;
}
@override
void didPush(Route route, Route? previousRoute) async {
super.didPush(route, previousRoute);
final routeName = route.settings.name;
if (!_isWorkDone && routeName == ChickenRoutes.init) {
_isWorkDone = true;
await setupInjection();
}
}
@override
void didReplace({Route? newRoute, Route? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
}
@override
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
}
@override
void didRemove(Route route, Route? previousRoute) {
super.didRemove(route, previousRoute);
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_app/infrastructure/service/app_navigation_observer.dart';
import 'package:rasadyar_app/presentation/routes/app_pages.dart';
import 'package:rasadyar_auth/auth.dart';
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
@@ -9,14 +10,15 @@ import 'infrastructure/service/auth_service.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await setupInjection();
await setupPreInjection();
Get.put(TokenStorageService());
var tokenService = Get.find<TokenStorageService>();
await tokenService.init();
Get.put(AuthMiddleware());
Get.put(AuthService());
runApp(MyApp());
// runApp(DevicePreview(builder: (context) => ForDevicePreview(),));
}
@@ -59,6 +61,7 @@ class MyApp extends StatelessWidget {
getPages: AppPages.pages,
locale: const Locale("fa", "IR"),
supportedLocales: const [Locale("fa", "IR")],
navigatorObservers: [CustomNavigationObserver()],
localizationsDelegates: [
PersianMaterialLocalizations.delegate,
PersianCupertinoLocalizations.delegate,

View File

@@ -34,7 +34,6 @@ sealed class AppPages {
}
String getTargetPage(Module? value) {
eLog('getTargetPage: $value');
switch (value) {
case Module.inspection:
return InspectionRoutes.inspection;

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(baseUrl: 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

@@ -9,7 +9,7 @@ import '../common/dio_manager.dart';
GetIt diAuth = GetIt.instance;
Future<void> setupAuthDI() async {
diAuth.registerLazySingleton(() => DioRemoteManager());
diAuth.registerLazySingleton<AppInterceptor>(
() => AppInterceptor(
refreshTokenCallback: () async {
@@ -19,7 +19,9 @@ Future<void> setupAuthDI() async {
final refreshToken = tokenService.refreshToken.value;
if (refreshToken == null) return null;
final result = await authRepo.loginWithRefreshToken(authRequest: {"refresh_token": refreshToken});
final result = await authRepo.loginWithRefreshToken(
authRequest: {"refresh_token": refreshToken},
);
if (result is AuthResponseModel) {
await tokenService.saveAccessToken(result.access!);
@@ -27,10 +29,18 @@ Future<void> setupAuthDI() async {
}
return null;
},
saveTokenCallback: (String newToken) async {
//
},
clearTokenCallback: () async {
//await tokenService.clearTokens(); // حذف همه توکن‌ها
},
),
);
diAuth.registerLazySingleton<DioRemote>(() => DioRemote(interceptors: [diAuth.get<AppInterceptor>()]));
diAuth.registerLazySingleton<DioRemote>(
() => DioRemote(interceptors: diAuth.get<AppInterceptor>()),
);
final dioRemote = diAuth.get<DioRemote>();
await dioRemote.init();
@@ -40,10 +50,13 @@ Future<void> setupAuthDI() async {
Future<void> newSetupAuthDI(String newUrl) async {
diAuth.registerLazySingleton<DioRemote>(
() => DioRemote(baseUrl: newUrl, interceptors: [diAuth.get<AppInterceptor>()]),
() => 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');
diAuth.registerSingleton<AuthRepositoryImpl>(
AuthRepositoryImpl(dioRemote),
instanceName: 'newUrl',
);
}

View File

@@ -5,7 +5,8 @@ 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';
@@ -17,7 +18,7 @@ class TokenStorageService extends GetxService {
RxnString accessToken = RxnString();
RxnString refreshToken = RxnString();
RxnString baseurl= RxnString();
RxnString baseurl = RxnString();
Rxn<Module> appModule = Rxn(null);
Future<void> init() async {
@@ -25,54 +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);
baseurl.value = _localStorage.read<String?>(boxName: _boxName, key: _baseUrlKey);
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: _boxName, key: _baseUrlKey, value: url);
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: _boxName, key: _apiKey, value: key);
await _localStorage.save(boxName: _tokenBoxName, key: _apiKey, value: key);
}
}

View File

@@ -1,6 +1,6 @@
import 'package:rasadyar_core/core.dart';
Future<void> safeCall<T>({
Future<T?> safeCall<T>({
required AppAsyncCallback<T> call,
Function(T result)? onSuccess,
ErrorCallback? onError,
@@ -12,10 +12,8 @@ Future<void> safeCall<T>({
bool showSnackBar = false,
Function()? onShowLoading,
Function()? onHideLoading,
Function()? onShowSuccessMessage,
Function()? onShowErrorMessage,
}) {
return gSafeCall(
return gSafeCall<T>(
call: call,
onSuccess: onSuccess,
onError: onError,
@@ -23,12 +21,7 @@ Future<void> safeCall<T>({
showLoading: showLoading,
showError: showError,
showSuccess: showSuccess,
showToast: showToast,
showSnackBar: showSnackBar,
onShowLoading: onShowLoading,
onHideLoading: onHideLoading,
onShowSuccessMessage: onShowSuccessMessage,
onShowErrorMessage: onShowErrorMessage,
);
}
}

View File

@@ -4,7 +4,6 @@ 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';
@@ -28,8 +27,7 @@ class AuthLogic extends GetxController {
Rx<GlobalKey<FormState>> formKeySentOtp = GlobalKey<FormState>().obs;
Rx<TextEditingController> phoneNumberController = 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>();
@@ -73,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
@@ -91,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;
}
@@ -179,22 +172,4 @@ class AuthLogic extends GetxController {
);
isLoading.value = false;
}
GetSnackBar _errorSnackBar(String message) {
return GetSnackBar(
titleText: Text(
'خطا',
style: AppFonts.yekan14.copyWith(color: Colors.white),
),
messageText: Text(
message,
style: AppFonts.yekan12.copyWith(color: Colors.white),
),
backgroundColor: AppColor.error,
margin: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
borderRadius: 12,
duration: Duration(milliseconds: 3500),
snackPosition: SnackPosition.TOP,
);
}
}

View File

@@ -4,7 +4,7 @@ import 'package:rasadyar_core/core.dart';
import '../di/chicken_di.dart';
import 'constant.dart';
class DioRemoteManager {
/*class DioRemoteManager {
DioRemote? _currentClient;
ApiEnvironment? _currentEnv;
@@ -28,6 +28,6 @@ class DioRemoteManager {
}
ApiEnvironment? get currentEnv => _currentEnv;
}
}*/

View File

@@ -1,4 +1,3 @@
import 'package:rasadyar_auth/data/di/auth_di.dart';
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
import 'package:rasadyar_chicken/data/repositories/chicken_repository_imp.dart';
import 'package:rasadyar_core/core.dart';
@@ -8,10 +7,30 @@ GetIt diChicken = GetIt.instance;
Future<void> setupChickenDI() async {
var tokenService = Get.find<TokenStorageService>();
diAuth.registerLazySingleton<DioRemote>(() => DioRemote(baseUrl: tokenService.baseurl.value));
final dioRemote = diAuth.get<DioRemote>();
diChicken.registerLazySingleton<AppInterceptor>(
() => AppInterceptor(
refreshTokenCallback: () async {},
saveTokenCallback: (String newToken) async {
await tokenService.saveAccessToken(newToken); // ذخیره توکن جدید
},
clearTokenCallback: () async {
await tokenService.deleteTokens();
},
),
instanceName: 'chickenInterceptor',
);
tokenService.getBaseUrl();
diChicken.registerLazySingleton<DioRemote>(() {
return DioRemote(
baseUrl: tokenService.baseurl.value,
interceptors: diChicken.get<AppInterceptor>(instanceName: 'chickenInterceptor'),
);
}, instanceName: 'chickenDioRemote');
final dioRemote = diChicken.get<DioRemote>(instanceName: 'chickenDioRemote');
await dioRemote.init();
diAuth.registerLazySingleton<ChickenRepositoryImpl>(() => ChickenRepositoryImpl(dioRemote));
diChicken.registerLazySingleton<ChickenRepositoryImpl>(() => ChickenRepositoryImpl(dioRemote));
diChicken.registerSingleton(ImagePicker());
}

View File

@@ -24,7 +24,7 @@ import 'package:rasadyar_core/core.dart';
import '../models/request/create_steward_free_bar/create_steward_free_bar.dart';
abstract class ChickenRepository {
Future<List<InventoryModel>?> getInventory({required String token});
Future<List<InventoryModel>?> getInventory({required String token, CancelToken? cancelToken});
Future<KillHouseDistributionInfo?> getKillHouseDistributionInfo({required String token});
@@ -117,7 +117,7 @@ abstract class ChickenRepository {
required OutProvinceCarcassesBuyer body,
});
Future<List<IranProvinceCityModel>?> getProvince();
Future<List<IranProvinceCityModel>?> getProvince({CancelToken? cancelToken});
Future<List<IranProvinceCityModel>?> getCity({required String provinceName});

View File

@@ -30,10 +30,13 @@ class ChickenRepositoryImpl implements ChickenRepository {
ChickenRepositoryImpl(this._httpClient);
@override
Future<List<InventoryModel>?> getInventory({required String token}) async {
Future<List<InventoryModel>?> getInventory({required String token, CancelToken? cancelToken}) async {
eLog(_httpClient.baseUrl);
var res = await _httpClient.get(
'/roles-products/?role=Steward',
headers: {'Authorization': 'Bearer $token'},
fromJsonList: (json) =>
(json).map((item) => InventoryModel.fromJson(item as Map<String, dynamic>)).toList(),
);
@@ -290,7 +293,7 @@ class ChickenRepositoryImpl implements ChickenRepository {
}
@override
Future<List<IranProvinceCityModel>?> getProvince() async {
Future<List<IranProvinceCityModel>?> getProvince({CancelToken? cancelToken}) async {
var res = await _httpClient.get(
'/iran_province/',
fromJsonList: (json) =>

View File

@@ -9,20 +9,19 @@ import 'package:rasadyar_core/core.dart';
class HomeLogic extends GetxController {
RootLogic rootLogic = Get.find<RootLogic>();
RxnInt totalWeightTodayBars = RxnInt();
Rxn<InventoryModel> inventoryModel = Rxn<InventoryModel>();
Rxn<KillHouseDistributionInfo> killHouseDistributionInfo = Rxn<KillHouseDistributionInfo>();
RxBool isExpanded = false.obs;
@override
void onInit() {
super.onInit();
void onReady() {
super.onReady();
getTodayBars();
getInventory();
getDistributionInformation();
}
Future<void> getTodayBars() async {
await safeCall<BarInformation?>(
call: () async => await rootLogic.chickenRepository.getGeneralBarInformation(
@@ -30,51 +29,22 @@ class HomeLogic extends GetxController {
queryParameters: buildQueryParams(fromDate: DateTime.now(), toDate: DateTime.now()),
),
onSuccess: (result) {
iLog(result);
if (result != null) {
totalWeightTodayBars.value = result.totalBarsWeight?.toInt();
}
},
onError: (error, stackTrace) {
switch (error.response?.statusCode) {
case 401:
errorHandler(error);
break;
case 403:
errorHandler(error);
break;
default:
errorHandler(error);
}
},
);
}
Future<void> getInventory() async {
await safeCall<List<InventoryModel>?>(
call: () async => await rootLogic.chickenRepository.getInventory(token: rootLogic.tokenService.accessToken.value!),
onSuccess: (result) {
if (result != null) {
inventoryModel.value = result.first;
}
},
onError: (error, stackTrace) {
switch (error.response?.statusCode) {
case 401:
errorHandler(error);
break;
case 403:
errorHandler(error);
break;
default:
errorHandler(error);
}
},
);
}
Future<void> getDistributionInformation() async {
await safeCall<KillHouseDistributionInfo?>(
call: () async => await rootLogic.chickenRepository.getKillHouseDistributionInfo(token: rootLogic.tokenService.accessToken.value!),
call: () async => await rootLogic.chickenRepository.getKillHouseDistributionInfo(
token: rootLogic.tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
killHouseDistributionInfo.value = result;
@@ -85,19 +55,5 @@ class HomeLogic extends GetxController {
}
void errorHandler(DioException error) {
handleGeneric(error, () {
rootLogic.tokenService.deleteTokens();
});
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {
super.onClose();
}
}

View File

@@ -309,7 +309,7 @@ class HomePage extends GetView<HomeLogic> {
),
],
);
}, controller.inventoryModel),
}, controller.rootLogic.inventoryModel),
);
}
@@ -345,7 +345,7 @@ class HomePage extends GetView<HomeLogic> {
],
),
);
}, controller.inventoryModel);
}, controller.rootLogic.inventoryModel);
}
Widget _todayShipmentWidget() {

View File

@@ -1,7 +1,10 @@
import 'dart:async';
import 'package:flutter/material.dart' show Colors;
import 'package:flutter/widgets.dart';
import 'package:rasadyar_auth/data/services/token_storage_service.dart';
import 'package:rasadyar_auth/data/utils/safe_call.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/data/models/response/inventory/inventory_model.dart';
import 'package:rasadyar_chicken/data/models/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/data/repositories/chicken_repository.dart';
@@ -39,19 +42,39 @@ class RootLogic extends GetxController {
Rxn<InventoryModel> inventoryModel = Rxn<InventoryModel>();
RxList<IranProvinceCityModel> provinces = <IranProvinceCityModel>[].obs;
// Cancel tokens for API calls
CancelToken? _inventoryCancelToken;
CancelToken? _provincesCancelToken;
@override
void onInit() {
super.onInit();
dioRemote = DioRemote(baseUrl: tokenService.baseurl.value);
dioRemote.init();
chickenRepository = ChickenRepositoryImpl(dioRemote);
getProvinces();
chickenRepository = diChicken.get<ChickenRepositoryImpl>();
getInventory();
//getKillHouseDistributionInfo();
}
@override
void onReady() {
super.onReady();
// Only call these methods if they haven't been called before
if (provinces.isEmpty) {
getProvinces();
}
if (inventoryModel.value == null) {
getInventory();
}
}
@override
void onClose() {
// Cancel any ongoing requests when controller is disposed
_inventoryCancelToken?.cancel();
_provincesCancelToken?.cancel();
super.onClose();
}
void toggleExpanded(int index) {
if (inventoryExpandedList.keys.contains(index)) {
inventoryExpandedList.remove(index);
@@ -61,24 +84,24 @@ class RootLogic extends GetxController {
}
Future<void> getInventory() async {
// Cancel previous request if still running
_inventoryCancelToken?.cancel();
_inventoryCancelToken = CancelToken();
await safeCall<List<InventoryModel>?>(
call: () async =>
await chickenRepository.getInventory(token: tokenService.accessToken.value!),
call: () async => await chickenRepository.getInventory(
token: tokenService.accessToken.value!,
cancelToken: _inventoryCancelToken,
),
onSuccess: (result) {
if (result != null) {
inventoryModel.value = result.first;
}
},
onError: (error, stackTrace) {
switch (error.response?.statusCode) {
case 401:
errorHandler(error);
break;
case 403:
errorHandler(error);
break;
default:
errorHandler(error);
if (error is DioException && error.type == DioExceptionType.cancel) {
// Request was cancelled, ignore the error
return;
}
},
);
@@ -95,20 +118,22 @@ class RootLogic extends GetxController {
}
Future<void> getProvinces() async {
// Cancel previous request if still running
_provincesCancelToken?.cancel();
_provincesCancelToken = CancelToken();
try {
final res = await chickenRepository.getProvince();
final res = await chickenRepository.getProvince(cancelToken: _provincesCancelToken);
if (res != null) {
provinces.clear();
provinces.value = res;
}
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) {
// Request was cancelled, ignore the error
return;
}
provinces.clear();
}
}
void errorHandler(DioException error) {
handleGeneric(error, () {
tokenService.deleteTokens();
});
}
}

View File

@@ -8,14 +8,12 @@ import 'package:rasadyar_chicken/presentation/pages/root/logic.dart';
import 'package:rasadyar_core/core.dart';
class SaleLogic extends GetxController {
Rxn<List<AllocatedMadeModel>?> allocatedMadeModel =
Rxn<List<AllocatedMadeModel>?>();
Rxn<List<AllocatedMadeModel>?> allocatedMadeModel = Rxn<List<AllocatedMadeModel>?>();
RxList<ProductModel> rolesProductsModel = RxList<ProductModel>();
RxList<GuildModel> guildsModel = <GuildModel>[].obs;
Rxn<StewardFreeBarDashboard> stewardFreeDashboard =
Rxn<StewardFreeBarDashboard>();
Rxn<StewardFreeBarDashboard> stewardFreeDashboard = Rxn<StewardFreeBarDashboard>();
RootLogic rootLogic = Get.find<RootLogic>();
@@ -25,6 +23,11 @@ class SaleLogic extends GetxController {
void onInit() {
super.onInit();
routesName = [...rootLogic.routesName, 'فروش'].toList();
}
@override
void onReady() {
super.onReady();
getStewardDashBord();
getRolesProducts();
}
@@ -33,12 +36,7 @@ class SaleLogic extends GetxController {
safeCall(
call: () async => await rootLogic.chickenRepository.getAllocatedMade(
token: rootLogic.tokenService.accessToken.value!,
queryParameters: buildQueryParams(
page: 1,
pageSize: 20,
search: 'filter',
role: 'Steward',
),
queryParameters: buildQueryParams(page: 1, pageSize: 20, search: 'filter', role: 'Steward'),
),
onSuccess: (result) {
if (result != null) {
@@ -81,8 +79,7 @@ class SaleLogic extends GetxController {
safeCall(
call: () async => await rootLogic.chickenRepository.confirmAllAllocation(
token: rootLogic.tokenService.accessToken.value!,
allocationTokens:
allocatedMadeModel.value?.map((e) => e.key!).toList() ?? [],
allocationTokens: allocatedMadeModel.value?.map((e) => e.key!).toList() ?? [],
),
onSuccess: (result) {
getAllocatedMade();

View File

@@ -1,11 +1,7 @@
import 'package:rasadyar_auth/auth.dart';
import 'package:rasadyar_chicken/presentation/pages/buy/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/buy/view.dart';
import 'package:rasadyar_chicken/presentation/pages/buy_in_province/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/buy_in_province/view.dart';
import 'package:rasadyar_chicken/presentation/pages/buy_in_province_all/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/buy_in_province_waiting/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/buy_out_of_province/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/buy_out_of_province/view.dart';
import 'package:rasadyar_chicken/presentation/pages/home/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/home/view.dart';
@@ -14,18 +10,13 @@ import 'package:rasadyar_chicken/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/root/view.dart';
import 'package:rasadyar_chicken/presentation/pages/sale/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/sale/view.dart';
import 'package:rasadyar_chicken/presentation/pages/sales_in_province/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/sales_in_province/view.dart';
import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province/logic.dart';
import 'package:rasadyar_chicken/presentation/pages/sales_out_of_province/view.dart';
import 'package:rasadyar_chicken/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/search/logic.dart';
import 'package:rasadyar_core/core.dart';
import '../pages/sales_out_of_province_buyers/logic.dart';
import '../pages/sales_out_of_province_sales_list/logic.dart';
sealed class ChickenPages {
ChickenPages._();
@@ -35,12 +26,12 @@ sealed class ChickenPages {
page: () => RootPage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.put(RootLogic());
Get.lazyPut(() => RootLogic());
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => HomeLogic());
Get.lazyPut(() => BuyLogic());
Get.lazyPut(() => SaleLogic());
Get.lazyPut(() => ProfileLogic());
Get.lazyPut(() => BaseLogic());
}),
),
@@ -50,8 +41,7 @@ sealed class ChickenPages {
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.put(HomeLogic());
Get.put(RootLogic());
Get.lazyPut(() => BaseLogic());
//Get.lazyPut(() => BaseLogic());
}),
),
@@ -61,7 +51,7 @@ sealed class ChickenPages {
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => EnteringTheWarehouseLogic());
Get.lazyPut(() => RootLogic());
// RootLogic already registered in root page binding
}),
),*/
@@ -72,9 +62,9 @@ sealed class ChickenPages {
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => SaleLogic());
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => SalesOutOfProvinceLogic());
Get.lazyPut(() => RootLogic());
//Get.lazyPut(() => BaseLogic());
//Get.lazyPut(() => SalesOutOfProvinceLogic());
//Get.lazyPut(() => RootLogic());
}),
),
GetPage(
@@ -82,13 +72,10 @@ sealed class ChickenPages {
page: () => SalesOutOfProvincePage(),
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => RootLogic());
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => SearchLogic());
Get.lazyPut(() => SalesOutOfProvinceLogic());
Get.lazyPut(() => SalesOutOfProvinceBuyersLogic());
Get.lazyPut(() => SalesOutOfProvinceSalesListLogic());
// Get.lazyPut(() => SalesOutOfProvinceLogic());
// Get.lazyPut(() => SalesOutOfProvinceBuyersLogic());
// Get.lazyPut(() => SalesOutOfProvinceSalesListLogic());
}),
),
GetPage(
@@ -97,8 +84,7 @@ sealed class ChickenPages {
middlewares: [AuthMiddleware()],
binding: BindingsBuilder(() {
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => SalesInProvinceLogic());
Get.lazyPut(() => RootLogic());
// Get.lazyPut(() => SalesInProvinceLogic());
Get.lazyPut(() => SearchLogic());
}),
),
@@ -111,7 +97,6 @@ sealed class ChickenPages {
binding: BindingsBuilder(() {
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => BuyLogic());
Get.lazyPut(() => RootLogic());
}),
),
@@ -122,8 +107,7 @@ sealed class ChickenPages {
binding: BindingsBuilder(() {
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => SearchLogic());
Get.lazyPut(() => BuyOutOfProvinceLogic());
Get.lazyPut(() => RootLogic());
// Get.lazyPut(() => BuyOutOfProvinceLogic());
}),
),
@@ -134,10 +118,9 @@ sealed class ChickenPages {
binding: BindingsBuilder(() {
Get.lazyPut(() => BaseLogic());
Get.lazyPut(() => SearchLogic());
Get.lazyPut(() => BuyInProvinceLogic());
Get.lazyPut(() => BuyInProvinceWaitingLogic());
Get.lazyPut(() => BuyInProvinceAllLogic());
Get.lazyPut(() => RootLogic());
// Get.lazyPut(() => BuyInProvinceLogic());
// Get.lazyPut(() => BuyInProvinceWaitingLogic());
// Get.lazyPut(() => BuyInProvinceAllLogic());
}),
),
];

View File

@@ -1,8 +1,8 @@
sealed class ChickenRoutes {
ChickenRoutes._();
static const _base = '/init';
static const init = '$_base/root';
static const _base = '/chicken';
static const init = '$_base/';
static const home = '$_base/home';
static const buy = '$_base/buy';
static const sale = '$_base/sale';

View File

@@ -1,62 +1,101 @@
import 'dart:async';
import '../../core.dart';
typedef RefreshTokenCallback = Future<String?> Function();
typedef SaveTokenCallback = Future<void> Function(String token);
typedef ClearTokenCallback = Future<void> Function();
class AppInterceptor extends Interceptor {
final RefreshTokenCallback refreshTokenCallback;
Completer<String?>? _refreshCompleter;
final RefreshTokenCallback? refreshTokenCallback;
final SaveTokenCallback saveTokenCallback;
final ClearTokenCallback clearTokenCallback;
late final Dio dio;
static Completer<String?>? _refreshCompleter;
static bool _isRefreshing = false;
AppInterceptor({required this.refreshTokenCallback});
AppInterceptor({
required this.saveTokenCallback,
required this.clearTokenCallback,
this.refreshTokenCallback,
});
@override
Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
if (_isRefreshing && _refreshCompleter != null) {
try {
final newToken = await _refreshCompleter!.future;
if (newToken != null && newToken.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $newToken';
}
} catch (_) {
handler.reject(DioException(requestOptions: options, type: DioExceptionType.cancel));
return;
}
}
handler.next(options);
}
@override
Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401 && !ApiHandler.isRefreshing) {
ApiHandler.cancelAllRequests("Token expired - refreshing");
if (_refreshCompleter == null) {
_refreshCompleter = Completer<String?>();
ApiHandler.isRefreshing = true;
try {
final newToken = await refreshTokenCallback();
if (newToken == null) throw Exception("Refresh failed");
ApiHandler.reset();
_refreshCompleter?.complete(newToken);
} catch (e) {
_refreshCompleter?.completeError(e);
if (!ApiHandler.isRedirecting) {
ApiHandler.isRedirecting = true;
ApiHandler.cancelAllRequests("Cancel All Requests - Unauthorized");
if (Get.currentRoute != '/Auth') {
Get.offAllNamed('/Auth');
}
}
} finally {
ApiHandler.isRefreshing = false;
_refreshCompleter = null;
}
if (err.response?.statusCode == 401) {
final retryResult = await _handleUnauthorizedError(err);
if (retryResult != null) {
handler.resolve(retryResult);
return;
}
}
handler.next(err);
}
Future<Response?> _handleUnauthorizedError(DioException err) async {
if (_isRefreshing && _refreshCompleter != null) {
try {
final newToken = await _refreshCompleter!.future;
if (newToken != null) {
final opts = err.requestOptions;
opts.headers['Authorization'] = 'Bearer $newToken';
final dio = Dio();
final cloneReq = await dio.fetch(opts);
handler.resolve(cloneReq);
return;
}
return newToken != null ? await _retryRequest(err.requestOptions, newToken) : null;
} catch (_) {
handler.reject(err);
return null;
}
} else if (err.type == DioExceptionType.cancel) {
handler.next(err);
} else {
handler.next(err);
}
_isRefreshing = true;
_refreshCompleter = Completer<String?>();
try {
final newToken = await refreshTokenCallback!();
if (!_refreshCompleter!.isCompleted) _refreshCompleter!.complete(newToken);
if (newToken != null) {
await saveTokenCallback(newToken); // ✅ ذخیره توکن جدید
return await _retryRequest(err.requestOptions, newToken);
} else {
await clearTokenCallback(); // ✅ پاک‌کردن توکن‌های قبلی
return null;
}
} catch (e) {
if (!_refreshCompleter!.isCompleted) _refreshCompleter!.completeError(e);
await clearTokenCallback(); // ✅ پاک‌کردن توکن در صورت خطا
_handleRefreshFailure();
return null;
} finally {
_isRefreshing = false;
_refreshCompleter = null;
}
}
Future<Response> _retryRequest(RequestOptions options, String token) async {
final newOptions = options.copyWith();
newOptions.headers['Authorization'] = 'Bearer $token';
return dio.fetch(newOptions);
}
void _handleRefreshFailure() {
ApiHandler.cancelAllRequests("Token refresh failed");
Future.delayed(const Duration(milliseconds: 100), () {
if (Get.currentRoute != '/Auth') {
Get.offAllNamed('/Auth');
}
});
}
}

View File

@@ -1,45 +1,45 @@
import 'package:flutter/foundation.dart';
import 'package:rasadyar_core/core.dart';
class DioRemote implements IHttpClient {
String? baseUrl;
late final Dio _dio;
final List<Interceptor> interceptors;
late Dio dio;
final AppInterceptor interceptors;
DioRemote({this.baseUrl, this.interceptors = const []});
DioRemote({this.baseUrl, required this.interceptors});
@override
Future<void> init() async {
final dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
dio.interceptors.addAll(interceptors);
dio = Dio(BaseOptions(baseUrl: baseUrl ?? ''));
dio.interceptors.add(interceptors);
if (kDebugMode) {
dio.interceptors.add(
PrettyDioLogger(
requestHeader: true,
responseHeader: true,
requestBody: true,
responseBody: true,
),
);
}
_dio = dio;
}
@override
Future<DioResponse<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
ProgressCallback? onReceiveProgress,
T Function(Map<String, dynamic> json)? fromJson,
T Function(List<dynamic> json)? fromJsonList,
}) async {
final response = await _dio.get(
String path, {
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
ProgressCallback? onReceiveProgress,
T Function(Map<String, dynamic> json)? fromJson,
T Function(List<dynamic> json)? fromJsonList,
}) async {
final response = await dio.get(
path,
queryParameters: queryParameters,
options: Options(headers: headers),
onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
if (fromJsonList != null && response.data is List) {
response.data = fromJsonList(response.data);
@@ -62,21 +62,19 @@ class DioRemote implements IHttpClient {
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) async {
final response = await _dio.post(
final response = await dio.post(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
if (fromJson != null) {
final rawData = response.data;
final parsedData = rawData is Map<String, dynamic>
? fromJson(rawData)
: null;
final parsedData = rawData is Map<String, dynamic> ? fromJson(rawData) : null;
response.data = parsedData;
return DioResponse<T>(response);
}
@@ -93,14 +91,14 @@ class DioRemote implements IHttpClient {
ProgressCallback? onSendProgress,
ProgressCallback? onReceiveProgress,
}) async {
final response = await _dio.put(
final response = await dio.put(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
return DioResponse<T>(response);
}
@@ -112,12 +110,12 @@ class DioRemote implements IHttpClient {
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
}) async {
final response = await _dio.delete<T>(
final response = await dio.delete<T>(
path,
data: data,
queryParameters: queryParameters,
options: Options(headers: headers),
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
return DioResponse<T>(response);
}
@@ -127,11 +125,11 @@ class DioRemote implements IHttpClient {
String url, {
ProgressCallback? onReceiveProgress,
}) async {
final response = await _dio.get<Uint8List>(
final response = await dio.get<Uint8List>(
url,
options: Options(responseType: ResponseType.bytes),
onReceiveProgress: onReceiveProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
return DioResponse(response);
}
@@ -143,12 +141,12 @@ class DioRemote implements IHttpClient {
Map<String, String>? headers,
ProgressCallback? onSendProgress,
}) async {
final response = await _dio.post(
final response = await dio.post(
path,
data: (formData as DioFormData).raw,
options: Options(headers: headers, contentType: 'multipart/form-data'),
onSendProgress: onSendProgress,
cancelToken: ApiHandler.globalCancelToken
cancelToken: ApiHandler.globalCancelToken,
);
return DioResponse<T>(response);
}

View File

@@ -17,6 +17,7 @@ Future<void> _setUpLogger() async{
Future<void> _setupLocalStorage() async {
diCore.registerSingleton<HiveLocalStorage>(HiveLocalStorage());
print('====> HiveLocalStorage registered');
}

View File

@@ -1,84 +1,169 @@
import 'package:flutter/material.dart';
import '../../core.dart';
class ApiHandler {
static bool _isRefreshing = false;
static bool _isRedirecting = false;
static CancelToken globalCancelToken = CancelToken();
static CancelToken _globalCancelToken = CancelToken();
static CancelToken get globalCancelToken => _globalCancelToken;
static Future<void> reset() async {
_isRefreshing = false;
_isRedirecting = false;
globalCancelToken = CancelToken();
_globalCancelToken = CancelToken();
}
static void cancelAllRequests(String reason) {
if (!globalCancelToken.isCancelled) {
globalCancelToken.cancel(reason);
if (!_globalCancelToken.isCancelled) {
_globalCancelToken.cancel(reason);
}
globalCancelToken = CancelToken();
reset();
}
static bool get isRefreshing => _isRefreshing;
static set isRefreshing(bool val) => _isRefreshing = val;
static bool get isRedirecting => _isRedirecting;
static set isRedirecting(bool val) => _isRedirecting = val;
}
typedef AppAsyncCallback<T> = Future<T> Function();
typedef ErrorCallback = Function(dynamic error, StackTrace? stackTrace);
typedef VoidCallback = void Function();
/// this is global safe call function
/// A utility function to safely cal l an asynchronous function with error
/// handling and optional loading, success, and error messages.
Future<void> gSafeCall<T>({
Future<T?> gSafeCall<T>({
required AppAsyncCallback<T> call,
Function(T result)? onSuccess,
ErrorCallback? onError,
VoidCallback? onComplete,
bool showLoading = false,
bool showError = false,
bool showError = true,
bool showSuccess = false,
bool showToast = false,
bool showSnackBar = false,
Function()? onShowLoading,
Function()? onHideLoading,
Function()? onShowSuccessMessage,
Function()? onShowErrorMessage,
Function(String message)? onShowErrorMessage,
Function(String message)? onShowSuccessMessage,
int maxRetries = 0,
Duration retryDelay = const Duration(seconds: 1),
}) async {
try {
if (showLoading) (onShowLoading ?? _defaultShowLoading)();
final result = await call();
if (showSuccess) (onShowSuccessMessage ?? _defaultShowSuccessMessage)();
onSuccess?.call(result);
} catch (error, stackTrace) {
if (showError) (onShowErrorMessage ?? _defaultShowErrorMessage)();
onError?.call(error, stackTrace);
} finally {
if (showLoading) (onHideLoading ?? _defaultHideLoading)();
onComplete?.call();
int retryCount = 0;
while (retryCount <= maxRetries) {
try {
if (showLoading && retryCount == 0) {
(onShowLoading ?? _defaultShowLoading)();
}
final result = await call();
if (showSuccess) {
(onShowSuccessMessage ?? _defaultShowSuccessMessage)('عملیات با موفقیت انجام شد');
}
onSuccess?.call(result);
return result;
} catch (error, stackTrace) {
retryCount++;
if (error is DioException && error.response?.statusCode == 401) {
if (showError) {
(onShowErrorMessage ?? _defaultShowErrorMessage)('خطا در احراز هویت');
}
onError?.call(error, stackTrace);
return null;
}
if (retryCount > maxRetries || !_isRetryableError(error)) {
if (showError) {
final message = _getErrorMessage(error);
(onShowErrorMessage ?? _defaultShowErrorMessage)(message);
}
onError?.call(error, stackTrace);
return null;
}
// صبر قبل از retry
if (retryCount <= maxRetries) {
await Future.delayed(retryDelay);
}
} finally {
if (showLoading && retryCount > maxRetries) {
(onHideLoading ?? _defaultHideLoading)();
}
if (retryCount > maxRetries) {
onComplete?.call();
}
}
}
return null;
}
bool _isRetryableError(dynamic error) {
if (error is DioException) {
// خطاهای قابل retry
return error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.sendTimeout ||
(error.response?.statusCode != null &&
error.response!.statusCode! >= 500);
}
return false;
}
String _getErrorMessage(dynamic error) {
if (error is DioException) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
return 'خطا در اتصال - زمان اتصال تمام شد';
case DioExceptionType.receiveTimeout:
return 'خطا در دریافت پاسخ';
case DioExceptionType.sendTimeout:
return 'خطا در ارسال درخواست';
case DioExceptionType.badCertificate:
return 'خطا در گواهی امنیتی';
case DioExceptionType.connectionError:
return 'خطا در اتصال به سرور';
case DioExceptionType.unknown:
return 'خطای نامشخص';
default:
if (error.response?.statusCode != null) {
return 'خطا: ${error.response!.statusCode}';
}
return 'خطای نامشخص';
}
}
return error.toString();
}
void _defaultShowLoading() {
// پیاده‌سازی پیش‌فرض
// نمایش loading
Get.dialog(
Center(child: CircularProgressIndicator()),
barrierDismissible: false,
);
}
void _defaultHideLoading() {
// پیاده‌سازی پیش‌فرض
// مخفی کردن loading
if (Get.isDialogOpen == true) {
Get.back();
}
}
void _defaultShowSuccessMessage() {
// پیاده‌سازی پیش‌فرض
void _defaultShowSuccessMessage(String message) {
Get.snackbar(
'موفقیت',
message,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green,
colorText: Colors.white,
);
}
void _defaultShowErrorMessage() {
// پیاده‌سازی پیش‌فرض
}
bool isTokenExpiredError(dynamic error) {
return error is DioException && error.response?.statusCode == 401;
void _defaultShowErrorMessage(String message) {
Get.snackbar(
'خطا',
message,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}

View File

@@ -1162,7 +1162,7 @@ packages:
path: "packages/chicken"
relative: true
source: path
version: "1.0.0"
version: "1.0.1"
rasadyar_core:
dependency: "direct main"
description: