import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import '../../../core/config/environment.dart'; import 'interceptors/auth_interceptor.dart'; import 'interceptors/error_interceptor.dart'; import 'interceptors/logging_interceptor.dart'; /// API 클라이언트 클래스 class ApiClient { late final Dio _dio; static final ApiClient _instance = ApiClient._internal(); factory ApiClient() => _instance; ApiClient._internal() { _dio = Dio(_baseOptions); _setupInterceptors(); } /// Dio 인스턴스 getter Dio get dio => _dio; /// 기본 옵션 설정 BaseOptions get _baseOptions => BaseOptions( baseUrl: Environment.apiBaseUrl, connectTimeout: Duration(milliseconds: Environment.apiTimeout), receiveTimeout: Duration(milliseconds: Environment.apiTimeout), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, validateStatus: (status) { return status != null && status < 500; }, ); /// 인터셉터 설정 void _setupInterceptors() { _dio.interceptors.clear(); // 인증 인터셉터 _dio.interceptors.add(AuthInterceptor()); // 에러 처리 인터셉터 _dio.interceptors.add(ErrorInterceptor()); // 로깅 인터셉터 (개발 환경에서만) if (Environment.enableLogging && kDebugMode) { _dio.interceptors.add(LoggingInterceptor()); } } /// 토큰 업데이트 void updateAuthToken(String token) { _dio.options.headers['Authorization'] = 'Bearer $token'; } /// 토큰 제거 void removeAuthToken() { _dio.options.headers.remove('Authorization'); } /// GET 요청 Future> get( String path, { Map? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onReceiveProgress, }) { return _dio.get( path, queryParameters: queryParameters, options: options, cancelToken: cancelToken, onReceiveProgress: onReceiveProgress, ); } /// POST 요청 Future> post( String path, { dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, }) { return _dio.post( path, data: data, queryParameters: queryParameters, options: options, cancelToken: cancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, ); } /// PUT 요청 Future> put( String path, { dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, }) { return _dio.put( path, data: data, queryParameters: queryParameters, options: options, cancelToken: cancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, ); } /// PATCH 요청 Future> patch( String path, { dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, }) { return _dio.patch( path, data: data, queryParameters: queryParameters, options: options, cancelToken: cancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, ); } /// DELETE 요청 Future> delete( String path, { dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, }) { return _dio.delete( path, data: data, queryParameters: queryParameters, options: options, cancelToken: cancelToken, ); } /// 파일 업로드 Future> uploadFile( String path, { required String filePath, required String fileFieldName, Map? additionalData, ProgressCallback? onSendProgress, CancelToken? cancelToken, }) async { final fileName = filePath.split('/').last; final formData = FormData.fromMap({ fileFieldName: await MultipartFile.fromFile( filePath, filename: fileName, ), ...?additionalData, }); return _dio.post( path, data: formData, options: Options( headers: { 'Content-Type': 'multipart/form-data', }, ), onSendProgress: onSendProgress, cancelToken: cancelToken, ); } /// 파일 다운로드 Future downloadFile( String path, { required String savePath, ProgressCallback? onReceiveProgress, CancelToken? cancelToken, Map? queryParameters, }) { return _dio.download( path, savePath, queryParameters: queryParameters, onReceiveProgress: onReceiveProgress, cancelToken: cancelToken, options: Options( responseType: ResponseType.bytes, followRedirects: false, ), ); } }