import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:path/path.dart'; import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart'; import 'package:sefer_driver/controller/functions/network/net_guard.dart'; import 'package:secure_string_operations/secure_string_operations.dart'; import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/links.dart'; import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart'; import 'package:sefer_driver/main.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:sefer_driver/env/env.dart'; import 'package:sefer_driver/print.dart'; import '../../constant/api_key.dart'; import '../../constant/char_map.dart'; import '../../constant/info.dart'; import '../../views/widgets/error_snakbar.dart'; import 'gemeni.dart'; import 'upload_image.dart'; class CRUD { final NetGuard _netGuard = NetGuard(); final _client = http.Client(); /// Stores the signature of the last logged error to prevent duplicates. static String _lastErrorSignature = ''; /// Stores the timestamp of the last logged error. static DateTime _lastErrorTimestamp = DateTime(2000); // Initialize with an old date /// The minimum time that must pass before logging the same error again. static const Duration _errorLogDebounceDuration = Duration(minutes: 1); /// Asynchronously logs an error to the server with debouncing to prevent log flooding. /// /// [error]: A concise description of the error. /// [details]: Detailed information, such as a stack trace or the server response body. /// [where]: The location in the code where the error occurred (e.g., 'ClassName.methodName'). static Future addError( String error, String details, String where) async { try { final currentErrorSignature = '$where-$error'; final now = DateTime.now(); if (currentErrorSignature == _lastErrorSignature && now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) { return; } _lastErrorSignature = currentErrorSignature; _lastErrorTimestamp = now; final userId = box.read(BoxName.driverID) ?? box.read(BoxName.passengerID); final userType = box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger'; final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); // Fire-and-forget call to prevent infinite loops if the logger itself fails. CRUD().post( link: AppLink.addError, payload: { 'error': error.toString(), 'userId': userId.toString(), 'userType': userType, 'phone': phone.toString(), 'device': where, 'details': details, }, ); } catch (e) {} } /// Centralized private method to handle all API requests. /// Includes retry logic, network checking, and standardized error handling. // --- تعديل 1: دالة _makeRequest محسنة للإنترنت الضعيف --- Future _makeRequest({ required String link, Map? payload, required Map headers, }) async { // 🟢 زيادة الوقت للسماح بالشبكات البطيئة (سوريا) const connectTimeout = Duration(seconds: 20); // رفعنا الوقت من 6 لـ 20 const receiveTimeout = Duration(seconds: 40); // رفعنا الوقت من 10 لـ 40 Future doPost() { final url = Uri.parse(link); // نستخدم _client إذا كان معرفاً، أو ننشئ واحداً جديداً مع إغلاقه لاحقاً // لضمان عدم حدوث مشاكل، سنستخدم http.post المباشر كما في النسخة المستقرة لديك // ولكن مع timeout أطول return http .post(url, body: payload, headers: headers) .timeout(connectTimeout + receiveTimeout); } http.Response response = http.Response('', 500); // Default initialization // 🟢 محاولة إعادة الاتصال (Retry) حتى 3 مرات int attempts = 0; while (attempts < 3) { try { attempts++; response = await doPost(); // إذا نجح الاتصال، نخرج من الحلقة ونعالج الرد break; } on SocketException catch (_) { if (attempts >= 3) { _netGuard.notifyOnce((title, msg) => mySnackeBarError(msg)); return 'no_internet'; } // انتظار بسيط قبل المحاولة التالية (مهم جداً للشبكات المتقطعة) await Future.delayed(const Duration(seconds: 1)); } on TimeoutException catch (_) { if (attempts >= 3) return 'failure'; // لا ننتظر هنا، نعيد المحاولة فوراً } catch (e) { // إذا كان الخطأ هو errno = 9 (Bad file descriptor) نعيد المحاولة if (e.toString().contains('errno = 9') && attempts < 3) { await Future.delayed(const Duration(milliseconds: 500)); continue; } // أخطاء أخرى لا يمكن تجاوزها addError( 'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link'); return 'failure'; } } // --- معالجة الرد (كما هي في كودك) --- // ملاحظة: المتغير response هنا قد يكون غير معرف (null) إذا فشلت كل المحاولات // لكن بسبب الـ return داخل الـ catch، لن نصل هنا إلا بوجود response // الحل الآمن لضمان وجود response قبل استخدامه: try { // إعادة تعريف response لضمان عدم حدوث خطأ null safety في المحرر // (في المنطق الفعلي لن نصل هنا إلا ومعنا response) if (attempts > 3) return 'failure'; final sc = response.statusCode; // استخدمنا ! لأننا متأكدين final body = response.body; if (sc >= 200 && sc < 300) { try { final jsonData = jsonDecode(body); return jsonData; } catch (e, st) { addError('JSON Decode Error', 'Body: $body\n$st', 'CRUD._makeRequest $link'); return 'failure'; } } if (sc == 401) { return 'token_expired'; } if (sc >= 500) { addError( 'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link'); return 'failure'; } return 'failure'; } catch (e) { return 'failure'; } } // --- تعديل 2: دالة get (كما طلبت: بوست + إرجاع النص الخام) --- // أبقيتها كما هي في كودك الأصلي تماماً، فقط حسنت الـ Timeout Future get({ required String link, Map? payload, }) async { try { var url = Uri.parse(link); // 🟢 إضافة timeout هنا أيضاً var response = await http.post( url, body: payload, headers: { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer ${r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]}' }, ).timeout(const Duration(seconds: 40)); // وقت كافٍ للشبكات الضعيفة Log.print('response: ${response.body}'); Log.print('response: ${response.request}'); if (response.statusCode == 200) { // المنطق الخاص بك: إرجاع الـ body كاملاً كنص (String) // لأنك تريد عمل jsonDecode لاحقاً في المكان الذي استدعى الدالة // أو التحقق من status: success داخلياً // ملاحظة: في كودك الأصلي كنت تفحص jsonDecode هنا وتعود بـ response.body // سأبقيها كما هي: var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'success') { return response.body; // إرجاع النص الخام } return jsonData['status']; } else if (response.statusCode == 401) { var jsonData = jsonDecode(response.body); if (jsonData['error'] == 'Token expired') { await Get.put(LoginDriverController()).getJWT(); return 'token_expired'; } else { // addError('Unauthorized: ${jsonData['error']}', 'crud().get - 401', // url.toString()); return 'failure'; } } else { addError('Non-200: ${response.statusCode}', 'crud().get - Other', url.toString()); return 'failure'; } } on TimeoutException { // معالجة صامتة للتايم أوت في الـ GET return 'failure'; } on SocketException { // معالجة صامتة لانقطاع النت return 'no_internet'; } catch (e) { addError('GET Exception: $e', '', link); return 'failure'; } } /// Performs a standard authenticated POST request. /// Automatically handles token renewal. Future post({ required String link, Map? payload, }) async { String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; // if (JwtDecoder.isExpired(token)) { // await Get.put(LoginController()).getJWT(); // token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0]; // } final headers = { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer $token' }; return await _makeRequest( link: link, payload: payload, headers: headers, ); } /// Performs a standard authenticated GET request (using POST method as per original code). /// Automatically handles token renewal. /// Performs an authenticated POST request to wallet endpoints. Future postWallet({ required String link, Map? payload, }) async { var jwt = await LoginDriverController().getJwtWallet(); final hmac = box.read(BoxName.hmac); final headers = { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer $jwt', 'X-HMAC-Auth': hmac.toString(), }; return await _makeRequest( link: link, payload: payload, headers: headers, ); } /// Performs an authenticated GET request to wallet endpoints (using POST). Future getWallet({ required String link, Map? payload, }) async { var s = await LoginDriverController().getJwtWallet(); final hmac = box.read(BoxName.hmac); var url = Uri.parse( link, ); var response = await http.post( url, body: payload, headers: { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer $s', 'X-HMAC-Auth': hmac.toString(), }, ); if (response.statusCode == 200) { var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'success') { return response.body; } return jsonData['status']; } else if (response.statusCode == 401) { // Specifically handle 401 Unauthorized var jsonData = jsonDecode(response.body); if (jsonData['error'] == 'Token expired') { // Show snackbar prompting to re-login await Get.put(LoginDriverController()).getJwtWallet(); return 'token_expired'; // Return a specific value for token expiration } else { // Other 401 errors addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401', url.toString()); return 'failure'; } } else { addError('Non-200 response code: ${response.statusCode}', 'crud().post - Other', url.toString()); return 'failure'; } } Future postWalletMtn( {required String link, Map? payload}) async { final s = await LoginDriverController().getJwtWallet(); final hmac = box.read(BoxName.hmac); final url = Uri.parse(link); try { final response = await http.post( url, body: payload, // form-urlencoded مناسب لـ filterRequest headers: { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Bearer $s", "X-HMAC-Auth": hmac.toString(), }, ); Map wrap(String status, {Object? message, int? code}) { return { 'status': status, 'message': message, 'code': code ?? response.statusCode, }; } if (response.statusCode == 200) { try { final jsonData = jsonDecode(response.body); // نتوقع الآن شكل موحّد من السيرفر: {status, message, data?} return jsonData; } catch (e) { return wrap('failure', message: 'JSON decode error', code: response.statusCode); } } else if (response.statusCode == 401) { try { final jsonData = jsonDecode(response.body); if (jsonData is Map && jsonData['error'] == 'Token expired') { await Get.put(LoginDriverController()).getJWT(); return { 'status': 'failure', 'message': 'token_expired', 'code': 401 }; } return wrap('failure', message: jsonData); } catch (_) { return wrap('failure', message: response.body); } } else { // غير 200 – ارجع التفاصيل try { final jsonData = jsonDecode(response.body); return wrap('failure', message: jsonData); } catch (_) { return wrap('failure', message: response.body); } } } catch (e) { return { 'status': 'failure', 'message': 'HTTP request error: $e', 'code': -1 }; } } // Future postWallet( // {required String link, Map? payload}) async { // var s = await LoginDriverController().getJwtWallet(); // final hmac = box.read(BoxName.hmac); // var url = Uri.parse(link); // try { // // await LoginDriverController().getJWT(); // var response = await http.post( // url, // body: payload, // headers: { // "Content-Type": "application/x-www-form-urlencoded", // 'Authorization': 'Bearer $s', // 'X-HMAC-Auth': hmac.toString(), // }, // ); // if (response.statusCode == 200) { // try { // var jsonData = jsonDecode(response.body); // if (jsonData['status'] == 'success') { // return jsonData; // } else { // return jsonData['status']; // } // } catch (e) { // // addError(e.toString(), 'crud().post - JSON decoding'); // return 'failure'; // } // } else if (response.statusCode == 401) { // // Specifically handle 401 Unauthorized // var jsonData = jsonDecode(response.body); // if (jsonData['error'] == 'Token expired') { // return 'token_expired'; // Return a specific value for token expiration // } else { // // Other 401 errors // // addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401'); // return 'failure'; // } // } else { // // addError('Non-200 response code: ${response.statusCode}', // // 'crud().post - Other'); // return 'failure'; // } // } catch (e) { // // addError('HTTP request error: $e', 'crud().post - HTTP'); // return 'failure'; // } // } // Future post( // {required String link, Map? payload}) async { // var url = Uri.parse(link); // try { // bool isTokenExpired = JwtDecoder.isExpired(X // .r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs) // .toString() // .split(AppInformation.addd)[0]); // if (isTokenExpired) { // await LoginDriverController().getJWT(); // } // var response = await http.post( // url, // body: payload, // headers: { // "Content-Type": "application/x-www-form-urlencoded", // 'Authorization': // 'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}' // // 'Authorization': 'Bearer ${box.read(BoxName.jwt)}' // }, // ); // if (response.statusCode == 200) { // try { // var jsonData = jsonDecode(response.body); // if (jsonData['status'] == 'success') { // return jsonData; // } else { // return jsonData['status']; // } // } catch (e) { // // addError(e.toString(), url); // return 'failure'; // } // } else if (response.statusCode == 401) { // // Specifically handle 401 Unauthorized // var jsonData = jsonDecode(response.body); // if (jsonData['error'] == 'Token expired') { // // Show snackbar prompting to re-login // // await Get.put(LoginDriverController()).getJWT(); // // MyDialog().getDialog( // // 'Session expired. Please log in again.'.tr, // // '', // // () { // // Get.put(LoginController()).loginUsingCredentials( // // box.read(BoxName.passengerID), box.read(BoxName.email)); // // Get.back(); // // }, // // ); // return 'token_expired'; // Return a specific value for token expiration // } else { // // Other 401 errors // // addError('Unauthorized: ${jsonData['error']}', 'crud().post - 401'); // return 'failure'; // } // } else { // // addError('Non-200 response code: ${response.statusCode}', // // 'crud().post - Other'); // return 'failure'; // } // } catch (e) { // // addError('HTTP request error: $e', 'crud().post - HTTP'); // return 'failure'; // } // } Future getAgoraToken({ required String channelName, required String uid, }) async { var uid = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); var res = await http.get(Uri.parse( // 'https://repulsive-pig-rugby-shirt.cyclic.app/token?channelName=$channelName'), 'https://orca-app-b2i85.ondigitalocean.app/token?channelName=$channelName'), headers: {'Authorization': 'Bearer ${AK.agoraAppCertificate}'}); if (res.statusCode == 200) { var response = jsonDecode(res.body); return response['token']; } else {} } Future getLlama({ required String link, required String payload, required String prompt, }) async { var url = Uri.parse( link, ); var headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer LL-X5lJ0Px9CzKK0HTuVZ3u2u4v3tGWkImLTG7okGRk4t25zrsLqJ0qNoUzZ2x4ciPy' // 'Authorization': 'Bearer ${Env.llamaKey}' }; var data = json.encode({ "model": "Llama-3-70b-Inst-FW", // "model": "llama-13b-chat", "messages": [ { "role": "user", "content": "Extract the desired information from the following passage as json decoded like $prompt just in this:\n\n$payload" } ], "temperature": 0.9 }); var response = await http.post( url, body: data, headers: headers, ); if (response.statusCode == 200) { return response.body; } return response.statusCode; } Future allMethodForAI(String prompt, linkPHP, imagePath) async { await ImageController().choosImage(linkPHP, imagePath); Future.delayed(const Duration(seconds: 2)); var extractedString = await arabicTextExtractByVisionAndAI(imagePath: imagePath); var json = jsonDecode(extractedString); var textValues = extractTextFromLines(json); // await Get.put(AI()).geminiAiExtraction(prompt, textValues); await Get.put(AI()).anthropicAI(textValues, prompt, imagePath); } String extractTextFromLines(Map jsonData) { final readResult = jsonData['readResult']; final blocks = readResult['blocks']; final StringBuffer buffer = StringBuffer(); for (final block in blocks) { final lines = block['lines']; for (final line in lines) { final text = line['text']; buffer.write(text); buffer.write('\n'); } } return buffer.toString().trim(); } Future arabicTextExtractByVisionAndAI({ required String imagePath, }) async { var headers = { 'Content-Type': 'application/json', 'Ocp-Apim-Subscription-Key': AK.ocpApimSubscriptionKey }; String imagePathFull = '${AppLink.server}/card_image/$imagePath-${box.read(BoxName.driverID)}.jpg'; var request = http.Request('POST', Uri.parse( // 'https://ocrhamza.cognitiveservices.azure.com/vision/v2.1/ocr?language=ar')); // 'https://eastus.api.cognitive.microsoft.com/vision/v3.2/ocr' 'https://eastus.api.cognitive.microsoft.com/computervision/imageanalysis:analyze?features=caption,read&model-version=latest&language=en&api-version=2024-02-01')); request.body = json.encode({"url": imagePathFull}); // request.body = json.encode({"url": imagePathFull}); request.headers.addAll(headers); http.StreamedResponse response = await request.send(); if (response.statusCode == 200) { // 'response.stream.bytesToString(): ${await response.stream.bytesToString()}'); return await response.stream.bytesToString(); } else {} } Future getChatGPT({ required String link, required String payload, }) async { var url = Uri.parse( link, ); var headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ${Env.chatGPTkeySeferNew}' }; var data = json.encode({ "model": "gpt-3.5-turbo", "messages": [ { "role": "user", "content": "Extract the desired information from the following passage as json decoded like vin,make,made,year,expiration_date,color,owner,registration_date just in this:\n\n$payload" } ], "temperature": 0.9 }); var response = await http.post( url, body: data, headers: headers, ); if (response.statusCode == 200) { return response.body; } return response.statusCode; } Future postStripe({ required String link, Map? payload, }) async { // String? secretKey = await storage.read(key: BoxName.secretKey); var url = Uri.parse( link, ); var response = await http.post( url, body: payload, headers: { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Bearer ${AK.secretKeyStripe}', }, ); if (response.statusCode == 200) { return response.body; } else {} } Future postPayMob({ required String link, Map? payload, }) async { // String? basicAuthCredentials = // await storage.read(key: BoxName.basicAuthCredentials); var url = Uri.parse( link, ); var response = await http.post(url, body: payload, headers: {'Content-Type': 'application/json'}); var jsonData = jsonDecode(response.body); if (response.statusCode == 200) { if (jsonData['status'] == 'success') { return response.body; } else { return (jsonData['status']); } } else { return response.statusCode; } } Future sendEmail(String link, Map? payload) async { // التحقق من صلاحية التوكن String rawJwt = box.read(BoxName.jwt); String token = X .r(X.r(X.r(rawJwt, cn), cC), cs) .toString() .split(AppInformation.addd)[0]; bool isTokenExpired = JwtDecoder.isExpired(token); if (isTokenExpired) { await LoginDriverController().getJWT(); rawJwt = box.read(BoxName.jwt); // تحديث التوكن بعد التجديد token = X .r(X.r(X.r(rawJwt, cn), cC), cs) .toString() .split(AppInformation.addd)[0]; } // إعداد الهيدر final headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Bearer $token", }; // إعداد الطلب final request = http.Request('POST', Uri.parse(link)); request.bodyFields = payload ?? {}; request.headers.addAll(headers); // إرسال الطلب final response = await request.send(); // التحقق من النتيجة if (response.statusCode == 200) { } else { final responseBody = await response.stream.bytesToString(); } } Future postFromDialogue({ required String link, Map? payload, }) async { // String? basicAuthCredentials = // await storage.read(key: BoxName.basicAuthCredentials); var url = Uri.parse( link, ); var response = await http.post( url, body: payload, headers: { "Content-Type": "application/x-www-form-urlencoded", 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', }, ); if (response.body.isNotEmpty) { var jsonData = jsonDecode(response.body); if (response.statusCode == 200) { if (jsonData['status'] == 'success') { Get.back(); // Get.snackbar( // jsonData['status'], // jsonData['message'], // ); return response.body; } } return (jsonData['status']); } } Future sendVerificationRequest(String phoneNumber) async { final accountSid = AK.accountSIDTwillo; final authToken = AK.authTokenTwillo; final verifySid = AK.twilloRecoveryCode; final Uri verificationUri = Uri.parse( 'https://verify.twilio.com/v2/Services/$verifySid/Verifications'); // Send the verification request final response = await http.post( verificationUri, headers: { 'Authorization': 'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')), 'Content-Type': 'application/x-www-form-urlencoded', }, body: { 'To': phoneNumber, 'Channel': 'sms', }, ); if (response.statusCode == 201) { } else {} // Prompt the user to enter the OTP final otpCode = "123456"; // Replace with user input // Check the verification code final checkUri = Uri.parse( 'https://verify.twilio.com/v2/Services/$verifySid/VerificationCheck'); final checkResponse = await http.post( checkUri, headers: { 'Authorization': 'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')), 'Content-Type': 'application/x-www-form-urlencoded', }, body: { 'To': phoneNumber, 'Code': otpCode, }, ); if (checkResponse.statusCode == 201) { } else {} } Future getGoogleApi({ required String link, Map? payload, }) async { var url = Uri.parse( link, ); var response = await http.post( url, body: payload, ); var jsonData = jsonDecode(response.body); if (jsonData['status'] == 'OK') { return jsonData; } return (jsonData['status']); } Future update({ required String endpoint, required Map data, required String id, }) async { // String? basicAuthCredentials = // await storage.read(key: BoxName.basicAuthCredentials); var url = Uri.parse('$endpoint/$id'); var response = await http.put( url, body: json.encode(data), headers: { 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', }, ); return json.decode(response.body); } Future delete({ required String endpoint, required String id, }) async { // String? basicAuthCredentials = // await storage.read(key: BoxName.basicAuthCredentials); var url = Uri.parse('$endpoint/$id'); var response = await http.delete( url, headers: { 'Authorization': 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}', }, ); return json.decode(response.body); } } /// Custom exception for when there is no internet connection. class NoInternetException implements Exception { final String message; NoInternetException( [this.message = 'No internet connection. Please check your network and try again.']); @override String toString() => message; } /// Custom exception for when the network is too slow (request times out). class WeakNetworkException implements Exception { final String message; WeakNetworkException( [this.message = 'Your network connection is too slow. Please try again later.']); @override String toString() => message; } class ApiException implements Exception { final String message; final int? statusCode; ApiException(this.message, [this.statusCode]); @override String toString() => "ApiException: $message (Status Code: ${statusCode ?? 'N/A'})"; }