import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../config/app_config.dart'; class ApiClient { late final Dio _dio; ApiClient() { _dio = Dio( BaseOptions( baseUrl: AppConfig.apiBaseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(minutes: 2), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ), ); _setupInterceptors(); } void _setupInterceptors() { // Request interceptor to append JWT token _dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('auth_token'); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } return handler.next(options); }, )); // Logging interceptor for debugging _dio.interceptors.add(LogInterceptor( requestBody: true, responseBody: true, )); // Standardized Error Interceptor _dio.interceptors.add(InterceptorsWrapper( onError: (DioException e, handler) { String errorMessage = 'Terjadi kesalahan yang tidak terduga'; if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.receiveTimeout) { errorMessage = 'Koneksi timeout. Silakan coba lagi.'; } else if (e.response != null) { // Standardized HTTP error formatting final statusCode = e.response?.statusCode; String serverMsg = 'Tidak diketahui'; final responseData = e.response?.data; if (responseData is Map) { serverMsg = responseData['message'] ?? 'Tidak diketahui'; } else if (responseData is String) { // Truncate raw string if it's a long HTML page serverMsg = responseData.length > 100 ? '${responseData.substring(0, 100)}...' : responseData; } errorMessage = 'Kesalahan server ($statusCode): $serverMsg'; } else if (e.type == DioExceptionType.connectionError) { errorMessage = 'Tidak ada koneksi internet.'; } final customError = DioException( requestOptions: e.requestOptions, response: e.response, type: e.type, error: errorMessage, ); return handler.next(customError); }, )); // Dummy Response Interceptor for Development // TODO: Remove this interceptor before production release // _dio.interceptors.add(DummyResponseInterceptor()); } Dio get client => _dio; } // Single shared instance so every screen/notifier reuses the same Dio HTTP client // (connection keep-alive) instead of paying a fresh TCP/TLS handshake per call site. final apiClientProvider = Provider((ref) => ApiClient());