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

@@ -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,
);
}