import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart'; import 'package:taxglide/consts/local_store.dart'; import 'package:taxglide/controller/api_consts.dart'; import 'package:taxglide/model/chat_model.dart'; import 'package:taxglide/model/chat_profile_model.dart'; import 'package:taxglide/model/city_model.dart'; import 'package:taxglide/model/count_model.dart'; import 'package:taxglide/model/country_model.dart'; import 'package:taxglide/model/dash_board_model.dart'; import 'package:taxglide/model/detail_model.dart'; import 'package:taxglide/model/employeeprofile_model.dart'; import 'package:taxglide/model/login_model.dart'; import 'package:taxglide/model/notification_model.dart'; import 'package:taxglide/model/notificationcount_model.dart'; import 'package:taxglide/model/profile_get_model.dart'; import 'package:taxglide/model/serivce_list_model.dart'; import 'package:taxglide/model/service_list_history_model.dart'; import 'package:taxglide/model/signup_model.dart'; import 'package:taxglide/model/staff_model.dart'; import 'package:taxglide/model/terms_model.dart'; import 'package:taxglide/router/consts_routers.dart'; class ApiRepository { final LocalStore _localStore = LocalStore(); final FirebaseMessaging _messaging = FirebaseMessaging.instance; // 🔹 Common headers without token (for login/signup) Map get _baseHeaders => { 'Content-Type': 'application/json', 'Accept': 'application/json', }; // 🔹 Authorized headers with token Future> _authorizedHeaders() async { final token = await _localStore.getToken(); return { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'Accept': 'application/json', }; } /// 🔑 Get fresh FCM token on-demand (not from storage) Future _getFreshFcmToken() async { try { debugPrint('🔄 Generating fresh FCM token...'); final token = await _messaging.getToken(); debugPrint('✅ FCM Token generated: $token'); return token; } catch (e) { debugPrint('❌ Error getting FCM token: $e'); return null; } } Future> _regenerateFcmToken() async { try { // 📌 Print old token BEFORE deleting final oldToken = await _messaging.getToken(); debugPrint('📋 OLD FCM Token (before delete): $oldToken'); debugPrint('🔄 Deleting old FCM token...'); // Delete the current token await _messaging.deleteToken(); debugPrint('🗑️ Old FCM token deleted'); // Wait for Firebase to process the deletion await Future.delayed(const Duration(milliseconds: 800)); // Request permission again (important for iOS) await _messaging.requestPermission(); // Generate new tokens debugPrint('🔄 Requesting new tokens...'); final newToken = await _messaging.getToken(); final apnsToken = await _messaging.getAPNSToken(); debugPrint('✅ NEW FCM Token: $newToken'); debugPrint('✅ NEW APNS Token: $apnsToken'); // Compare tokens if (oldToken == newToken) { debugPrint('⚠️ WARNING: Old and New tokens are SAME!'); } else { debugPrint('✅ SUCCESS: New token is DIFFERENT from old token'); } return {'fcm_token': newToken, 'apns_token': apnsToken}; } catch (e) { debugPrint('❌ Error regenerating tokens: $e'); return {'fcm_token': null, 'apns_token': null}; } } // 🔐 Centralized token expiry handler Future _handleUnauthorized() async { debugPrint('🔐 Token expired - Auto logout'); await _localStore.clearAll(); Get.offAllNamed(ConstRouters.login); Get.snackbar( 'Session Expired', 'Please login again', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, duration: const Duration(seconds: 3), ); } // 🔹 Wrapper to handle token expiry for all authenticated requests Future _makeAuthenticatedRequest( Future Function() request, ) async { try { final response = await request(); if (response.statusCode == 401 || response.statusCode == 403) { await _handleUnauthorized(); throw Exception('Unauthorized - Token expired'); } return response; } catch (e) { rethrow; } } // 🔹 LOGIN API - sends OTP Future> loginUser(LoginModel model) async { try { final params = model.toJsonMobile(); final uri = Uri.parse(ConstsApi.login).replace( queryParameters: params.map( (key, value) => MapEntry(key, value.toString()), ), ); final response = await http.post(uri, headers: _baseHeaders); final data = jsonDecode(response.body); if (response.statusCode == 200 || response.statusCode == 201) { return {'success': true, 'data': data}; } else { return {'success': false, 'error': _extractErrorMessage(data)}; } } catch (e) { return { 'success': false, 'error': 'Connection error. Please check your internet.', }; } } // 🔹 VERIFY OTP (LOGIN) - ✅ Regenerates FCM token Future> verifyOtp(LoginModel model) async { try { final params = model.toJsonOtp(); // ✅ Regenerate FCM and APNS tokens on every login final tokens = await _regenerateFcmToken(); final fcmToken = tokens['fcm_token']; final apnsToken = tokens['apns_token']; if (fcmToken != null && fcmToken.isNotEmpty) { params['fcm_token'] = fcmToken; } if (apnsToken != null && apnsToken.isNotEmpty) { params['apns_token'] = apnsToken; } if (params['fcm_token'] == null) { debugPrint('⚠️ Failed to generate FCM token, trying fallback...'); final fallbackToken = await _getFreshFcmToken(); if (fallbackToken != null && fallbackToken.isNotEmpty) { params['fcm_token'] = fallbackToken; } } final uri = Uri.parse(ConstsApi.verifyOtp).replace( queryParameters: params.map( (key, value) => MapEntry(key, value.toString()), ), ); final response = await http.post(uri, headers: _baseHeaders); final data = jsonDecode(response.body); if (response.statusCode == 200 || response.statusCode == 201) { await _localStore.saveLoginData(data); return {'success': true, 'data': data}; } else { return {'success': false, 'error': _extractErrorMessage(data)}; } } catch (e) { return { 'success': false, 'error': 'Connection error. Please check your internet.', }; } } // 🔹 EMPLOYEE LOGIN Future> employeeLogin(String mobile) async { try { final uri = Uri.parse( ConstsApi.employeelogin, ).replace(queryParameters: {'mobile': mobile}); final response = await http.post(uri, headers: _baseHeaders); final data = jsonDecode(response.body); if (response.statusCode == 200 || response.statusCode == 201) { return {'success': true, 'data': data}; } else { return {'success': false, 'error': _extractErrorMessage(data)}; } } catch (e) { return { 'success': false, 'error': 'Connection error. Please check your internet.', }; } } // 🔹 SIGNUP API Future> signupUser(SignupModel model) async { try { final params = model.toJson(); final uri = Uri.parse(ConstsApi.signup).replace( queryParameters: params.map( (key, value) => MapEntry(key, value.toString()), ), ); final response = await http.post(uri, headers: _baseHeaders); final data = jsonDecode(response.body); if (response.statusCode == 200 || response.statusCode == 201) { return {'success': true, 'data': data}; } else { return {'success': false, 'error': _extractErrorMessage(data)}; } } catch (e) { return { 'success': false, 'error': 'Connection error. Please check your internet.', }; } } // 🔹 VERIFY OTP (SIGNUP) - ✅ Regenerates FCM token Future> verifySignupOtp( String mobile, String otp, ) async { try { // ✅ Regenerate FCM and APNS tokens on signup final tokens = await _regenerateFcmToken(); final fcmToken = tokens['fcm_token']; final apnsToken = tokens['apns_token']; final params = {'mobile': mobile, 'otp': otp}; if (fcmToken != null && fcmToken.isNotEmpty) { params['fcm_token'] = fcmToken; } if (apnsToken != null && apnsToken.isNotEmpty) { params['apns_token'] = apnsToken; } if (params['fcm_token'] == null) { debugPrint('⚠️ Failed to generate FCM token, trying fallback...'); final fallbackToken = await _getFreshFcmToken(); if (fallbackToken != null && fallbackToken.isNotEmpty) { params['fcm_token'] = fallbackToken; } } final uri = Uri.parse( ConstsApi.verifyOtp, ).replace(queryParameters: params); final response = await http.post(uri, headers: _baseHeaders); final data = jsonDecode(response.body); if (response.statusCode == 200 || response.statusCode == 201) { await _localStore.saveLoginData(data); return {'success': true, 'data': data}; } else { return {'success': false, 'error': _extractErrorMessage(data)}; } } catch (e) { return { 'success': false, 'error': 'Connection error. Please check your internet.', }; } } // 🔹 FETCH TERMS & CONDITIONS Future fetchTermsAndConditions() async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(ConstsApi.terms), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return TermsModel.fromJson(data); } else { throw Exception('Failed to fetch terms and conditions'); } } // 🔹 FETCH DASHBOARD Future fetchDashboard() async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(ConstsApi.dashboard), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return DashBoardModel.fromJson(data); } else { throw Exception('Failed to fetch dashboard'); } } // 🔹 FETCH POLICY Future fetchpolicy() async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(ConstsApi.policy), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return TermsModel.fromJson(data); } else { throw Exception('Failed to fetch Policy'); } } // 🔹 FETCH PROFILE Future fetchProfile() async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(ConstsApi.getprofile), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return ProfileGetModel.fromJson(data); } else { throw Exception('Failed to fetch Profile'); } } // 🔹 FETCH EMPLOYEE PROFILE Future fetchEmployeeProfile() async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(ConstsApi.getprofile), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return EmployeeProfileModel.fromJson(data); } else { throw Exception('Failed to fetch Employee Profile'); } } // 🔹 FETCH SERVICE HISTORY Future fetchServiceHistory( String type, { String? fromDate, String? toDate, }) async { final uri = Uri.parse(ConstsApi.serivcehistory).replace( queryParameters: { 'type': type, if (fromDate != null) 'from_date': fromDate, if (toDate != null) 'to_date': toDate, }, ); final response = await _makeAuthenticatedRequest(() async { return await http.get(uri, headers: await _authorizedHeaders()); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return ServiceListHistoryModel.fromJson(data); } else { throw Exception('Failed to fetch Service History'); } } // 🔹 FETCH COUNT Future fetchCount(String type) async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse("${ConstsApi.count}?chat_id=$type"), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return CountModel.fromJson(data); } else { throw Exception('Failed to fetch count'); } } // 🔹 FETCH NOTIFICATION COUNT Future fetchNotificationCount() async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(ConstsApi.notificationcount), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return NotificationCountModel.fromJson(data); } else { throw Exception('Failed to fetch notification count'); } } // 🔹 FETCH SERVICE DETAIL Future fetchServiceDetail(int id) async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse("${ConstsApi.serivcedetails}?service_request_id=$id"), headers: await _authorizedHeaders(), ); }); debugPrint("response: ${response.body}"); if (response.statusCode == 200) { final data = jsonDecode(response.body); return DetailModel.fromJson(data); } else { throw Exception('Failed to fetch Service Detail'); } } // 🔹 FETCH CHAT DOCUMENTS Future fetchChatDocuments(int id) async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse("${ConstsApi.chatdocument}?chat_id=$id"), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return ChatProfileModel.fromJson(data); } else { throw Exception('Failed to fetch Chat Documents'); } } // 🔹 FETCH SERVICE LIST Future> fetchServiceList() async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(ConstsApi.serivcelist), headers: await _authorizedHeaders(), ); }); debugPrint("📡 Status Code: ${response.statusCode}"); debugPrint("📡 Response: ${response.body}"); if (response.statusCode == 200) { final data = jsonDecode(response.body); final List list = data['data'] ?? []; return list.map((e) => ServiceListModel.fromJson(e)).toList(); } throw Exception('Failed to fetch service list'); } // 🔹 FETCH STAFF LIST Future> fetchStaffList() async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(ConstsApi.stafflist), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); final List list = data['data'] ?? []; return list.map((e) => StaffModel.fromJson(e)).toList(); } else { throw Exception('Failed to fetch staff list'); } } // 🔹 FETCH NOTIFICATION LIST Future> fetchNotificationList({ required int page, }) async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse("${ConstsApi.notificationList}?page=$page"), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); final List list = data['data'] ?? []; return list.map((e) => NotificationModel.fromJson(e)).toList(); } else { throw Exception('Failed to fetch notifications'); } } // 🔹 FETCH CHAT MESSAGES Future> fetchChatMessages({ required int chatId, required int page, }) async { final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse("${ConstsApi.chatList}/$chatId/messages?page=$page"), headers: await _authorizedHeaders(), ); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); final messages = data["messages"]["data"] ?? []; return List.from( messages.map((e) => MessageModel.fromJson(e)), ); } else { throw Exception("Failed to fetch chat messages → ${response.statusCode}"); } } // 🔹 FETCH COUNTRY AND STATES Future fetchCountryAndStates(String url) async { try { debugPrint('🌐 Fetching Country & States from: $url'); final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(url), headers: await _authorizedHeaders(), ); }); debugPrint('📡 API Status: ${response.statusCode}'); debugPrint('📡 API Body: ${response.body}'); if (response.statusCode == 200) { final jsonBody = jsonDecode(response.body); final model = CountryModel.fromJson(jsonBody); debugPrint( '✅ Countries: ${model.countriesData?.length ?? 0}, States: ${model.statesData?.length ?? 0}', ); return model; } else { final error = jsonDecode(response.body); throw Exception(error['message'] ?? 'Failed to fetch data'); } } catch (e) { debugPrint('❌ fetchCountryAndStates Error: $e'); throw Exception('Error fetching country & state data: $e'); } } // 🔹 FETCH CITY BY STATE Future fetchCityByState(String url, int stateId) async { try { final fullUrl = "$url?state_id=$stateId"; debugPrint('🌐 Fetching Cities (Districts) from: $fullUrl'); final response = await _makeAuthenticatedRequest(() async { return await http.get( Uri.parse(fullUrl), headers: await _authorizedHeaders(), ); }); debugPrint('📡 City API Status: ${response.statusCode}'); debugPrint('📡 City API Body: ${response.body}'); if (response.statusCode == 200) { final jsonBody = jsonDecode(response.body); final model = CityModel.fromJson(jsonBody); debugPrint('✅ Districts fetched: ${model.districtsData?.length ?? 0}'); return model; } else { final error = jsonDecode(response.body); throw Exception(error['message'] ?? 'Failed to fetch city data'); } } catch (e) { debugPrint('❌ fetchCityByState Error: $e'); throw Exception('Error fetching city data: $e'); } } // 🔹 KYC FORM UPDATE Future kycFormUpdate({ File? logo, File? panFile, File? gstFile, File? incorporationFile, int? countryId, int? stateId, int? cityId, String? companyPincode, String? companyAddress, String? panNumber, String? gstNumber, String? tanNumber, String? cinNumber, String? yearOfIncorporation, String? address, }) async { try { final token = await _localStore.getToken(); final uri = Uri.parse(ConstsApi.kycupdate); var request = http.MultipartRequest('POST', uri) ..headers.addAll({ 'Authorization': 'Bearer $token', 'Accept': 'application/json', 'Connection': 'keep-alive', }) ..fields.addAll({ if (countryId != null) 'country_id': countryId.toString(), if (stateId != null) 'state_id': stateId.toString(), if (cityId != null) 'district_id': cityId.toString(), if (companyPincode != null) 'company_pincode': companyPincode, if (companyAddress != null) 'company_address': companyAddress, if (panNumber != null) 'pan_number': panNumber, if (gstNumber != null) 'gst_number': gstNumber, if (tanNumber != null) 'tan_number': tanNumber, if (cinNumber != null) 'cin': cinNumber, if (yearOfIncorporation != null) 'year_of_incorporation': yearOfIncorporation, if (address != null) 'address': address, }); await Future.wait([ _addFileIfExists(request, logo, 'logo'), _addFileIfExists(request, panFile, 'pan_file'), _addFileIfExists(request, gstFile, 'gst_file'), _addFileIfExists(request, incorporationFile, 'incorporation_file'), ]); final streamedResponse = await request.send().timeout( const Duration(seconds: 60), onTimeout: () { throw TimeoutException('Request timed out after 60 seconds'); }, ); final response = await http.Response.fromStream(streamedResponse); debugPrint('📦 KYC Response Status: ${response.statusCode}'); debugPrint('📦 KYC Response Body: ${response.body}'); // Handle token expiry if (response.statusCode == 401 || response.statusCode == 403) { await _handleUnauthorized(); throw Exception('Unauthorized - Token expired'); } if (response.statusCode == 200) { debugPrint('✅ KYC update success'); return; } final decoded = _safeJsonDecode(response.body); if (response.statusCode == 422) { throw _formatValidationErrors(decoded); } else { final message = decoded?['message'] ?? decoded?['error'] ?? 'Something went wrong'; throw {'error': message.toString()}; } } on SocketException { throw {'error': 'No internet connection. Please check your network.'}; } on TimeoutException { throw {'error': 'Request timed out. Please try again.'}; } on FormatException { throw {'error': 'Invalid server response. Please try again.'}; } catch (e) { if (e is Map) rethrow; debugPrint('❌ KYC update error: $e'); throw {'error': 'Failed to update KYC: ${e.toString()}'}; } } // 🔹 SEND CHAT MESSAGE Future sendChatMessage({ required int chatId, required String messages, int? tagId, List? files, }) async { try { final token = await _localStore.getToken(); final uri = Uri.parse(ConstsApi.sendChatMessage); var request = http.MultipartRequest('POST', uri) ..headers.addAll({ 'Authorization': 'Bearer $token', 'Accept': 'application/json', }) ..fields.addAll({'chat_id': chatId.toString(), 'message': messages}); if (tagId != null && tagId > 0) { request.fields['tag_id'] = tagId.toString(); } if (files != null && files.isNotEmpty) { for (int i = 0; i < files.length; i++) { request.files.add( await http.MultipartFile.fromPath( 'file[$i]', files[i].path, filename: files[i].path.split('/').last, ), ); } } final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); // Handle token expiry if (response.statusCode == 401 || response.statusCode == 403) { await _handleUnauthorized(); throw Exception('Unauthorized - Token expired'); } final decoded = _safeJsonDecode(response.body); if (response.statusCode == 200) { debugPrint("✅ Chat message sent successfully"); return; } if (response.statusCode == 422) { throw _formatValidationErrors(decoded); } else { final message = decoded?['message'] ?? decoded?['error'] ?? 'Something went wrong'; throw {'error': message.toString()}; } } catch (e) { debugPrint('❌ Chat send error: $e'); if (e is Map) rethrow; throw {'error': 'Failed to send message: ${e.toString()}'}; } } // 🔹 UPDATE SERVICE REQUEST Future updateServiceRequest({ required int serviceId, required String message, List? documents, }) async { try { final token = await _localStore.getToken(); final uri = Uri.parse(ConstsApi.serivcerequest); var request = http.MultipartRequest('POST', uri) ..headers.addAll({ 'Authorization': 'Bearer $token', 'Accept': 'application/json', 'Connection': 'keep-alive', }) ..fields.addAll({ 'service_id': serviceId.toString(), 'message': message, }); if (documents != null && documents.isNotEmpty) { for (int i = 0; i < documents.length; i++) { final file = documents[i]; final fileName = file.path.split('/').last; request.files.add( await http.MultipartFile.fromPath( 'documents[$i]', file.path, filename: fileName, ), ); } } final streamedResponse = await request.send().timeout( const Duration(seconds: 60), onTimeout: () { throw TimeoutException('Request timed out after 60 seconds'); }, ); final response = await http.Response.fromStream(streamedResponse); // Handle token expiry if (response.statusCode == 401 || response.statusCode == 403) { await _handleUnauthorized(); throw Exception('Unauthorized - Token expired'); } if (response.statusCode == 200) { debugPrint('✅ Service request updated successfully'); return; } final decoded = _safeJsonDecode(response.body); if (response.statusCode == 422) { throw _formatValidationErrors(decoded); } else { final message = decoded?['message'] ?? decoded?['error'] ?? 'Something went wrong'; throw {'error': message.toString()}; } } on SocketException { throw {'error': 'No internet connection. Please check your network.'}; } on TimeoutException { throw {'error': 'Request timed out. Please try again.'}; } on FormatException { throw {'error': 'Invalid server response. Please try again.'}; } catch (e) { if (e is Map) rethrow; debugPrint('❌ Service request update error: $e'); throw {'error': 'Failed to update service request: ${e.toString()}'}; } } Future proformaaccept({required int proformaId}) async { try { final token = await _localStore.getToken(); final uri = Uri.parse(ConstsApi.proformaaccept); var request = http.MultipartRequest('POST', uri) ..headers.addAll({ 'Authorization': 'Bearer $token', 'Accept': 'application/json', 'Connection': 'keep-alive', }) ..fields.addAll({ 'id': proformaId.toString(), // ✅ FIXED }); final streamedResponse = await request.send().timeout( const Duration(seconds: 60), onTimeout: () { throw TimeoutException('Request timed out after 60 seconds'); }, ); final response = await http.Response.fromStream(streamedResponse); if (response.statusCode == 401 || response.statusCode == 403) { await _handleUnauthorized(); throw Exception('Unauthorized - Token expired'); } if (response.statusCode == 200) { debugPrint('✅ Proforma accepted successfully'); return; } final decoded = _safeJsonDecode(response.body); if (response.statusCode == 422) { throw _formatValidationErrors(decoded); } else { final message = decoded?['message'] ?? decoded?['error'] ?? 'Something went wrong'; throw {'error': message.toString()}; } } on SocketException { throw {'error': 'No internet connection. Please check your network.'}; } on TimeoutException { throw {'error': 'Request timed out. Please try again.'}; } on FormatException { throw {'error': 'Invalid server response. Please try again.'}; } catch (e) { if (e is Map) rethrow; debugPrint('❌ Proforma accept error: $e'); throw {'error': 'Failed to accept proforma: ${e.toString()}'}; } } // 🔹 LOGOUT - ✅ Deletes FCM token Future logout() async { try { final token = await _localStore.getToken(); if (token == null || token.isEmpty) { debugPrint('⚠️ No token found — clearing data and deleting FCM token'); await _messaging.deleteToken(); await _localStore.clearAll(keepFcm: false); return; } final uri = Uri.parse(ConstsApi.logout); debugPrint('📡 Logging out from: $uri'); final request = http.Request('POST', uri) ..headers.addAll({ 'Authorization': 'Bearer $token', 'Accept': 'application/json', 'Content-Type': 'application/json', 'Connection': 'keep-alive', }); final streamedResponse = await request.send().timeout( const Duration(seconds: 30), onTimeout: () => throw TimeoutException('Logout request timed out after 30 seconds'), ); final response = await http.Response.fromStream(streamedResponse); debugPrint('📡 Logout Status Code: ${response.statusCode}'); debugPrint('📦 Logout Body: ${response.body}'); final decoded = _safeJsonDecode(response.body); if (response.statusCode == 200) { final message = decoded?['message'] ?? 'Logout successful'; debugPrint('✅ $message'); } else if (response.statusCode == 401) { debugPrint('⚠️ Token expired or unauthorized. Clearing storage.'); } else { final message = decoded?['message'] ?? 'Logout failed'; debugPrint('⚠️ $message'); } } on SocketException { debugPrint('❌ No internet connection'); } on TimeoutException { debugPrint('❌ Request timed out'); } catch (e, stack) { debugPrint('❌ Logout error: $e'); debugPrint('🧩 Stack trace: $stack'); } finally { // ✅ ALWAYS delete FCM token and clear data on logout try { debugPrint('🗑️ Deleting FCM token on logout...'); await _messaging.deleteToken(); debugPrint('✅ FCM token deleted successfully'); } catch (e) { debugPrint('⚠️ Error deleting FCM token: $e'); } await _localStore.clearAll(keepFcm: false); debugPrint('✅ Local storage cleared'); } } // Helper methods remain the same... Map? _safeJsonDecode(String body) { try { return jsonDecode(body) as Map?; } catch (e) { debugPrint('⚠️ JSON decode error: $e'); return null; } } String _extractErrorMessage( dynamic data, [ String defaultMsg = 'Operation failed', ]) { if (data is! Map) return defaultMsg; if (data['status'] == 'error') { if (data['errors'] is Map) { final errors = data['errors'] as Map; final firstErrorKey = errors.keys.first; final errorList = errors[firstErrorKey]; if (errorList is List && errorList.isNotEmpty) { return errorList.first.toString(); } else if (errorList is String) { return errorList; } } else if (data['errors'] is List && (data['errors'] as List).isNotEmpty) { final firstError = (data['errors'] as List).first; if (firstError is String) { return firstError; } else if (firstError is Map && firstError['message'] != null) { return firstError['message']; } } } return data['message'] ?? data['error'] ?? data['msg'] ?? data['detail'] ?? defaultMsg; } // Additional helper methods (fetchProfile, fetchServiceHistory, etc.) remain unchanged // ... (include all other methods from your original code) } // 🔹 HELPER: Add file if exists Future _addFileIfExists( http.MultipartRequest request, File? file, String fieldName, ) async { if (file == null) return; try { if (!file.existsSync()) { debugPrint('⚠️ File not found: ${file.path}'); return; } request.files.add( await http.MultipartFile.fromPath( fieldName, file.path, filename: basename(file.path), ), ); } catch (e) { debugPrint('⚠️ Error adding file $fieldName: $e'); } } // // 🔹 HELPER: Safe JSON decode // Map? _safeJsonDecode(String body) { // try { // return jsonDecode(body) as Map?; // } catch (e) { // debugPrint('⚠️ JSON decode error: $e'); // return null; // } // } // // 🔹 HELPER: Extract error message from API response // String _extractErrorMessage( // dynamic data, [ // String defaultMsg = 'Operation failed', // ]) { // if (data is! Map) return defaultMsg; // // Check if status is error // if (data['status'] == 'error') { // // Handle errors object with field-specific errors // if (data['errors'] is Map) { // final errors = data['errors'] as Map; // final firstErrorKey = errors.keys.first; // final errorList = errors[firstErrorKey]; // if (errorList is List && errorList.isNotEmpty) { // return errorList.first.toString(); // } else if (errorList is String) { // return errorList; // } // } // // Handle errors as List // else if (data['errors'] is List && (data['errors'] as List).isNotEmpty) { // final firstError = (data['errors'] as List).first; // if (firstError is String) { // return firstError; // } else if (firstError is Map && firstError['message'] != null) { // return firstError['message']; // } // } // } // // Fallback to other common error formats // return data['message'] ?? // data['error'] ?? // data['msg'] ?? // data['detail'] ?? // defaultMsg; // } // 🔹 HELPER: Format validation errors Map _formatValidationErrors(Map? decoded) { if (decoded == null) return {'error': 'Validation failed'}; final rawErrors = (decoded['errors'] ?? decoded['error'] ?? {}) as Map; final Map formattedErrors = {}; rawErrors.forEach((key, value) { if (value is List && value.isNotEmpty) { formattedErrors[key] = value.first.toString(); } else if (value is String) { formattedErrors[key] = value; } else { formattedErrors[key] = 'Invalid input'; } }); return formattedErrors.isNotEmpty ? formattedErrors : {'error': 'Validation failed'}; } // ⚡ Optimized HTTP Client for better performance class OptimizedHttpClient { static final http.Client _client = http.Client(); static http.Client get client => _client; static void dispose() { _client.close(); } }