import 'dart:convert'; import 'package:http/http.dart' as http; import '../config/app_config.dart'; import '../models/account.dart'; import '../models/category.dart'; import '../models/dashboard.dart'; import '../models/transaction_item.dart'; import 'offline_store.dart'; class ApiService { ApiService({OfflineStore? store}) : _store = store ?? OfflineStore.instance; final OfflineStore _store; static const String _baseUrlKey = 'api_base_url'; Future getBaseUrl() async { return await _store.getSetting(_baseUrlKey) ?? AppConfig.defaultApiBaseUrl; } Future setBaseUrl(String value) async { await _store.setSetting( _baseUrlKey, value.trim().replaceAll(RegExp(r'/$'), ''), ); } Future _uri(String path) async { final baseUrl = await getBaseUrl(); return Uri.parse('$baseUrl$path'); } Future _getJson(String path) async { final res = await http.get(await _uri(path)).timeout(const Duration(seconds: 5)); return _handleResponse(res); } Future _postJson(String path, Map body) async { final res = await http .post( await _uri(path), headers: {'Content-Type': 'application/json'}, body: jsonEncode(body), ) .timeout(const Duration(seconds: 8)); return _handleResponse(res); } dynamic _handleResponse(http.Response res) { if (res.statusCode >= 200 && res.statusCode < 300) { if (res.body.isEmpty) return null; return jsonDecode(utf8.decode(res.bodyBytes)); } String message = 'Erreur HTTP ${res.statusCode}'; try { final body = jsonDecode(utf8.decode(res.bodyBytes)); message = body['detail']?.toString() ?? body.toString(); } catch (_) {} throw Exception(message); } Future healthCheck() async { final data = await _getJson('/health'); return data is Map && data['status'] == 'ok'; } Future getDashboard() async { final data = await _getJson('/dashboard'); return DashboardData.fromJson(data as Map); } Future> getAccounts() async { final data = await _getJson('/accounts?active_only=true') as List; return data.map((e) => Account.fromJson(e as Map)).toList(); } Future> getCategories() async { final data = await _getJson('/categories') as List; return data.map((e) => Category.fromJson(e as Map)).toList(); } Future> getTransactions({int limit = AppConfig.defaultTransactionLimit}) async { final data = await _getJson('/transactions?limit=$limit') as List; return data.map((e) => TransactionItem.fromJson(e as Map)).toList(); } Future createTransactionFromPayload(Map payload) async { final cleanPayload = Map.from(payload); cleanPayload.removeWhere((_, value) => value == null); final data = await _postJson('/transactions', cleanPayload); return TransactionItem.fromJson(data as Map); } Future createTransaction({ required String label, required double amount, required String type, required int accountId, int? targetAccountId, int? categoryId, String? note, DateTime? date, }) async { return createTransactionFromPayload({ 'date': (date ?? DateTime.now()).toIso8601String(), 'label': label, 'amount': amount, 'type': type, 'account_id': accountId, 'target_account_id': targetAccountId, 'category_id': categoryId, 'created_from': 'android', 'note': note, }); } }