api intigreted of scan Qr and recent scan histoy ,scan history,scan history details

This commit is contained in:
Raj.Ghag
2026-04-17 15:55:54 +05:30
parent f301585948
commit 94cd74a135
60 changed files with 3678 additions and 1119 deletions

View File

@@ -8,11 +8,14 @@ class ApiService {
static const String _baseUrl = 'https://your-api-base-url.com/api';
static final ApiService _instance = ApiService._internal();
late Dio _dio;
late Dio _tokenDio; // ✅ Separate Dio for token refresh (no interceptors)
factory ApiService() => _instance;
ApiService._internal() {
// ================= MAIN DIO =================
_dio = Dio(
BaseOptions(
baseUrl: _baseUrl,
@@ -25,7 +28,21 @@ class ApiService {
),
);
// ================= RETRY INTERCEPTOR =================
// ================= TOKEN DIO (No interceptors — used only for refresh) =================
_tokenDio = Dio(
BaseOptions(
baseUrl: _baseUrl,
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
// ================= 1. RETRY INTERCEPTOR =================
// ✅ Added FIRST so it only retries network errors
_dio.interceptors.add(
InterceptorsWrapper(
onError: (err, handler) async {
@@ -42,7 +59,7 @@ class ApiService {
if (shouldRetry) {
if (kDebugMode) {
print(
'🔁 Retrying request (${currentRetry + 1}) => ${options.uri}',
'🔁 Retrying request (${currentRetry + 1}/$maxRetries) => ${options.uri}',
);
}
options.extra['retry'] = currentRetry + 1;
@@ -54,12 +71,13 @@ class ApiService {
}
}
return handler.reject(err);
return handler.next(err);
},
),
);
// ================= MAIN INTERCEPTOR (Queued for concurrency) =================
// ================= 2. MAIN INTERCEPTOR (Auth + Token Refresh) =================
// ✅ Added SECOND — handles auth and token refresh
_dio.interceptors.add(
QueuedInterceptorsWrapper(
onRequest: (options, handler) async {
@@ -70,6 +88,10 @@ class ApiService {
handler.next(options);
},
onResponse: (response, handler) {
handler.next(response);
},
onError: (error, handler) async {
if (error.response?.statusCode == 401) {
final requestOptions = error.requestOptions;
@@ -80,13 +102,18 @@ class ApiService {
if (refreshed) {
final newToken = await LocalPreference.getAccessToken();
requestOptions.headers['Authorization'] = 'Bearer $newToken';
// ✅ Retry original request with new token
final response = await _dio.fetch(requestOptions);
return handler.resolve(response);
} else {
await _forceLogout();
return handler.reject(error);
}
} catch (_) {
} catch (e) {
if (kDebugMode) {
print('❌ Error during token refresh flow: $e');
}
await _forceLogout();
return handler.reject(error);
}
@@ -97,7 +124,8 @@ class ApiService {
),
);
// ================= LOGGING INTERCEPTOR =================
// ================= 3. LOGGING INTERCEPTOR =================
// ✅ Added LAST so it captures the final state of all requests/responses
if (kDebugMode) {
_dio.interceptors.add(
LogInterceptor(
@@ -105,7 +133,9 @@ class ApiService {
requestHeader: true,
requestBody: true,
responseBody: true,
responseHeader: false,
error: true,
logPrint: (log) => print('📡 $log'),
),
);
}
@@ -198,26 +228,67 @@ class ApiService {
}
// ================= REFRESH TOKEN =================
// ✅ Uses _tokenDio (no interceptors) to avoid QueuedInterceptor deadlock
Future<bool> _refreshToken() async {
try {
final refreshToken = await LocalPreference.getRefreshToken();
if (refreshToken == null) return false;
final response = await _dio.post(
if (kDebugMode) print('🔍 Refresh token from storage: $refreshToken');
if (refreshToken == null || refreshToken.isEmpty) {
if (kDebugMode) print('❌ No refresh token found');
return false;
}
if (kDebugMode) print('🔄 Attempting token refresh...');
final response = await _tokenDio.post(
ApiUrls.refreshToken,
data: {"refreshToken": refreshToken},
options: Options(headers: {'Authorization': null}),
data: '', // ✅ Empty body — server reads token from Cookie header
options: Options(
headers: {
// ✅ Manually inject refresh token as cookie header
'Cookie': 'partner_refresh_token=$refreshToken',
},
validateStatus: (status) => status != null && status < 500,
),
);
await LocalPreference.setAccessToken(response.data['accessToken']);
if (kDebugMode) {
print("🔄 Refresh response status: ${response.statusCode}");
print("✅ REFRESH RESPONSE => ${response.data}");
}
if (response.statusCode != 200 && response.statusCode != 201) {
if (kDebugMode) {
print('❌ Refresh failed with status: ${response.statusCode}');
}
return false;
}
final newAccessToken = response.data['accessToken'];
if (newAccessToken == null || (newAccessToken as String).isEmpty) {
if (kDebugMode) print('❌ Access token missing in refresh response');
return false;
}
await LocalPreference.setAccessToken(newAccessToken);
if (kDebugMode) print('✅ Token refreshed successfully');
return true;
} catch (_) {
} on DioException catch (e) {
if (kDebugMode) print('❌ Refresh token DioException: ${e.message}');
return false;
} catch (e) {
if (kDebugMode) print('❌ Refresh token unexpected error: $e');
return false;
}
}
// ================= FORCE LOGOUT =================
Future<void> _forceLogout() async {
if (kDebugMode) print('🚪 Force logout triggered');
await LocalPreference.clearAll();
await LocalPreference.setLogin(false);
}
@@ -241,10 +312,8 @@ class ApiService {
responseData['error'] ??
"Invalid status code: ${error.response?.statusCode}";
}
if (responseData is String) {
return responseData.isNotEmpty
? responseData
: "Invalid status code: ${error.response?.statusCode}";
if (responseData is String && responseData.isNotEmpty) {
return responseData;
}
return "Invalid status code: ${error.response?.statusCode}";
} catch (_) {

View File

@@ -4,15 +4,16 @@ class ApiUrls {
static const baseUrl = "https://testingapi.citycards.betadelivery.com"; // Test API
// static const baseUrl = "https://uatapi.citycard.betadelivery.com"; // Production Lvl API
static const refreshToken = "$baseUrl/auth/refresh";
static const refreshToken = "$baseUrl/partner/auth/refresh";
// ================= GET APIs =================
static const authUserDetails = "$baseUrl/partner/auth";
static const supportDetails = "$baseUrl/mobile/partners/support";
static const scanHistory = "$baseUrl/mobile/partners/scan-history";
// ================= POST APIs =================
static const login = "$baseUrl/partner/auth/login";
static const forgotPassword = "$baseUrl/partner/auth/forgot-password";
static const verifyOtp = "$baseUrl/partner/auth/verify-otp";
static const resetPassword = "$baseUrl/partner/auth/set-password";
static const redeem = "$baseUrl/mobile/partners/redeem";
}