import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; const String _baseUrl = 'https://backend.prosapp.co/api/v1'; class ApiException implements Exception { final int statusCode; final String message; ApiException(this.statusCode, this.message); @override String toString() => 'ApiException($statusCode): $message'; } class ApiService { static ApiService? _instance; static ApiService get instance => _instance ??= ApiService._(); ApiService._(); String? _token; Future getToken() async { if (_token != null) return _token; final prefs = await SharedPreferences.getInstance(); _token = prefs.getString('token'); return _token; } Future saveToken(String token) async { _token = token; final prefs = await SharedPreferences.getInstance(); await prefs.setString('token', token); } Future clearToken() async { _token = null; final prefs = await SharedPreferences.getInstance(); await prefs.remove('token'); } Future> _headers({bool auth = true}) async { final headers = {'Content-Type': 'application/json'}; if (auth) { final token = await getToken(); if (token != null) headers['Authorization'] = 'Bearer $token'; } return headers; } dynamic _parse(http.Response res) { final body = jsonDecode(res.body); if (res.statusCode >= 200 && res.statusCode < 300) return body; final msg = body is Map ? (body['message'] ?? res.reasonPhrase) : res.reasonPhrase; throw ApiException(res.statusCode, msg.toString()); } Future get(String path, {bool auth = true, Map? query}) async { final uri = Uri.parse('$_baseUrl$path').replace(queryParameters: query); final res = await http.get(uri, headers: await _headers(auth: auth)); return _parse(res); } Future post(String path, Map body, {bool auth = false}) async { final res = await http.post( Uri.parse('$_baseUrl$path'), headers: await _headers(auth: auth), body: jsonEncode(body), ); return _parse(res); } Future patch(String path, Map body, {bool auth = true}) async { final res = await http.patch( Uri.parse('$_baseUrl$path'), headers: await _headers(auth: auth), body: jsonEncode(body), ); return _parse(res); } Future uploadFile(String filePath) async { final token = await getToken(); final req = http.MultipartRequest('POST', Uri.parse('$_baseUrl/storage/upload')); if (token != null) req.headers['Authorization'] = 'Bearer $token'; req.files.add(await http.MultipartFile.fromPath('file', filePath)); final streamed = await req.send(); final res = await http.Response.fromStream(streamed); final body = jsonDecode(res.body); if (res.statusCode >= 200 && res.statusCode < 300) return body['url'] as String; throw ApiException(res.statusCode, body['message']?.toString() ?? 'Upload failed'); } }