import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import 'package:image_picker/image_picker.dart'; import 'package:http_parser/http_parser.dart'; class ApiService { static const String baseUrl = 'http://localhost:5000/api/auth'; static const String gejalaUrl = 'http://localhost:5000/api/gejala'; static const String hamaUrl = 'http://localhost:5000/api/hama'; static const String penyakitUrl = 'http://localhost:5000/api/penyakit'; static const String rulesPenyakitUrl ='http://localhost:5000/api/rules_penyakit'; static const String rulesHamaUrl = 'http://localhost:5000/api/rules_hama'; static const Duration timeout = Duration(seconds: 15); // Fungsi Login (dengan perbaikan) static Future> loginUser( String email, String password, ) async { try { final response = await http.post( Uri.parse("$baseUrl/login"), headers: {"Content-Type": "application/json"}, body: jsonEncode({'email': email, 'password': password}), ); print("Response Status: ${response.statusCode}"); print("Response Body: ${response.body}"); if (response.statusCode == 200) { return jsonDecode(response.body); } else { throw Exception("Login gagal: ${response.body}"); } } catch (e) { print("Error: $e"); throw Exception("Terjadi kesalahan saat login"); } } // Fungsi Logout static Future logoutUser() async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.remove('token'); await prefs.remove('role'); } // Fungsi Cek Login static Future checkLoginStatus() async { SharedPreferences prefs = await SharedPreferences.getInstance(); return prefs.getString('role'); // Return role jika login } // Ambil semua gejala Future>> getGejala() async { try { final response = await http.get(Uri.parse(gejalaUrl)); if (response.statusCode == 200) { return List>.from(jsonDecode(response.body)); } else { throw Exception('Gagal mengambil data gejala'); } } catch (e) { print('Error getGejala: $e'); throw Exception('Gagal mengambil data gejala'); } } // Tambah gejala baru (kode otomatis) Future> createGejala(String nama) async { try { final response = await http.post( Uri.parse(gejalaUrl), headers: {"Content-Type": "application/json"}, body: jsonEncode({"nama": nama}), ); if (response.statusCode == 201) { return jsonDecode(response.body); } else { throw Exception('Gagal menambahkan gejala'); } } catch (e) { print('Error createGejala: $e'); throw Exception('Gagal menambahkan gejala'); } } // Update gejala berdasarkan ID Future> updateGejala(int id, String nama) async { try { final response = await http.put( Uri.parse('$gejalaUrl/$id'), headers: {"Content-Type": "application/json"}, body: jsonEncode({"nama": nama}), ); if (response.statusCode == 200) { return jsonDecode(response.body); } else { throw Exception('Gagal mengupdate gejala'); } } catch (e) { print('Error updateGejala: $e'); throw Exception('Gagal mengupdate gejala'); } } // Hapus gejala berdasarkan ID Future deleteGejala(int id) async { try { final response = await http.delete(Uri.parse('$gejalaUrl/$id')); if (response.statusCode != 200) { throw Exception('Gagal menghapus gejala'); } } catch (e) { print('Error deleteGejala: $e'); throw Exception('Gagal menghapus gejala'); } } // Ambil semua hama // Get all hama Future>> getHama() async { try { final response = await http.get(Uri.parse(hamaUrl)).timeout(timeout); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); // Pastikan "data" ada dan berupa List if (responseData is Map && responseData.containsKey("data")) { final List data = responseData["data"]; return List>.from( data.map((item) => Map.from(item)), ); } else { throw Exception("Format respons API tidak sesuai"); } } else { print('Error response: ${response.statusCode} - ${response.body}'); throw Exception("Gagal mengambil data hama (Status: ${response.statusCode})"); } } catch (e) { print("Error getHama: $e"); throw Exception("Gagal mengambil data hama: $e"); } } Future> getHamaById(int id) async { try { final response = await http.get(Uri.parse('$hamaUrl/$id')); print('Fetching hama with ID $id from $hamaUrl/$id'); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); print('Response data: $responseData'); // Periksa format respons if (responseData is Map && responseData.containsKey("data")) { final data = responseData["data"]; return Map.from(data); } else if (responseData is Map) { // Jika langsung mengembalikan objek tanpa wrapper "data" return responseData; } else { throw Exception("Format respons API tidak sesuai"); } } else { print('Error response: ${response.statusCode} - ${response.body}'); throw Exception( "Gagal mengambil data hama dengan ID $id (Status: ${response.statusCode})", ); } } catch (e) { print("Error getHamaById: $e"); throw Exception("Gagal mengambil data hama dengan ID $id: $e"); } } // Fungsi untuk mendapatkan URL gambar hama String getHamaImageUrl(int id) { return '$hamaUrl/$id/image'; } // Fungsi untuk mengecek apakah gambar tersedia Future isHamaImageAvailable(int id) async { try { final url = Uri.parse(getHamaImageUrl(id)); print('Checking image availability: $url'); final response = await http.head(url); print('Image availability status: ${response.statusCode}'); return response.statusCode == 200; } catch (e) { print("Error checking image availability: $e"); return false; } } // Fungsi untuk mengambil gambar hama sebagai bytes Future getHamaImageBytes(int id) async { try { final url = Uri.parse(getHamaImageUrl(id)); print('Fetching image bytes from: $url'); final response = await http.get(url); if (response.statusCode == 200) { return response.bodyBytes; } else { print('Failed to get image bytes: ${response.statusCode}'); print( 'Response body: ${response.body}', ); // Tambahkan ini untuk melihat pesan error return null; } } catch (e) { print('Error getting image bytes: $e'); return null; } } Future getHamaImageBytesByFilename(String filename) async { try { final url = Uri.parse('http://localhost:5000/image_hama/$filename'); print('Fetching image from: $url'); final response = await http.get(url); if (response.statusCode == 200) { return response.bodyBytes; } else { print('Failed to fetch image. Status: ${response.statusCode}'); print('Response body: ${response.body}'); return null; } } catch (e) { print('Error fetching image by filename: $e'); return null; } } // Tambah hama baru (kode otomatis) Future> createHama( String nama, String deskripsi, String penanganan, XFile? pickedFile, ) async { try { var uri = Uri.parse(hamaUrl); var request = http.MultipartRequest('POST', uri); request.fields['nama'] = nama; request.fields['deskripsi'] = deskripsi; request.fields['penanganan'] = penanganan; print('Mengirim request ke: $uri'); print('Dengan fields: ${request.fields}'); if (pickedFile != null) { String mimeType = 'image/jpeg'; String fileName = pickedFile.name; if (fileName.isEmpty) { fileName = pickedFile.path.split('/').last; } if (fileName.toLowerCase().endsWith('.png')) { mimeType = 'image/png'; } else if (fileName.toLowerCase().endsWith('.jpg') || fileName.toLowerCase().endsWith('.jpeg')) { mimeType = 'image/jpeg'; } final bytes = await pickedFile.readAsBytes(); request.files.add( http.MultipartFile.fromBytes( 'foto', // Sesuaikan dengan field name yang diterima backend bytes, filename: fileName, contentType: MediaType.parse(mimeType), ), ); print('Menambahkan file: $fileName (${bytes.length} bytes)'); } else { print('Tidak ada file yang dilampirkan'); } var streamedResponse = await request.send(); var response = await http.Response.fromStream(streamedResponse); print('Status response: ${response.statusCode}'); print('Body response: ${response.body}'); if (response.statusCode == 201) { return jsonDecode(response.body); } else { String errorMessage = 'Gagal menambahkan hama (kode: ${response.statusCode})'; try { var errorBody = jsonDecode(response.body); if (errorBody is Map && errorBody.containsKey('message')) { errorMessage = errorBody['message']; } } catch (e) { if (response.body.isNotEmpty) { errorMessage = response.body; } } throw Exception(errorMessage); } } catch (e) { print('Error dalam createHama: $e'); throw Exception('Gagal menambahkan hama: $e'); } } // Update hama berdasarkan ID Future> updateHama( int id, String nama, String deskripsi, String penanganan, XFile? pickedFile, ) async { try { var uri = Uri.parse('$hamaUrl/$id'); var request = http.MultipartRequest('PUT', uri); // Tambahkan fields untuk data teks request.fields['nama'] = nama; request.fields['deskripsi'] = deskripsi; request.fields['penanganan'] = penanganan; // Log untuk debugging print('Mengirim request ke: $uri'); print('Dengan fields: ${request.fields}'); if (pickedFile != null) { // Dapatkan tipe MIME berdasarkan ekstensi file String mimeType = 'image/jpeg'; // Default String fileName = pickedFile.name; if (fileName.isEmpty) { fileName = pickedFile.path.split('/').last; } if (fileName.toLowerCase().endsWith('.png')) { mimeType = 'image/png'; } else if (fileName.toLowerCase().endsWith('.jpg') || fileName.toLowerCase().endsWith('.jpeg')) { mimeType = 'image/jpeg'; } // Baca file sebagai bytes final bytes = await pickedFile.readAsBytes(); // Tambahkan file ke request dengan tipe yang tepat request.files.add( http.MultipartFile.fromBytes( 'foto', // Nama field ini harus sama dengan yang diharapkan backend bytes, filename: fileName, contentType: MediaType.parse(mimeType), ), ); print('Menambahkan file: $fileName (${bytes.length} bytes)'); } else { print('Tidak ada file yang dilampirkan'); } // Kirim request var streamedResponse = await request.send(); var response = await http.Response.fromStream(streamedResponse); // Debug response print('Status response: ${response.statusCode}'); print('Body response: ${response.body}'); if (response.statusCode == 200) { return jsonDecode(response.body); } else { // Coba ambil pesan error dari response body String errorMessage = 'Gagal mengupdate hama (kode: ${response.statusCode})'; try { var errorBody = jsonDecode(response.body); if (errorBody is Map && errorBody.containsKey('message')) { errorMessage = errorBody['message']; } } catch (e) { // Jika gagal parse JSON, gunakan response body langsung if (response.body.isNotEmpty) { errorMessage = response.body; } } throw Exception(errorMessage); } } catch (e) { print('Error dalam updateHama: $e'); throw Exception('Gagal mengupdate hama: $e'); } } // Hapus hama berdasarkan ID Future deleteHama(int id) async { try { final response = await http.delete(Uri.parse('$hamaUrl/$id')); if (response.statusCode != 200) { throw Exception('Gagal menghapus hama'); } } catch (e) { print('Error deleteHama: $e'); throw Exception('Gagal menghapus hama'); } } // Ambil semua penyakit Future>> getPenyakit() async { try { final response = await http.get(Uri.parse(ApiService.penyakitUrl)); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); // Pastikan "data" ada dan berupa List if (responseData is Map && responseData.containsKey("data")) { final List data = responseData["data"]; return List>.from( data.map((item) => Map.from(item)), ); } else { throw Exception("Format respons API tidak sesuai"); } } else { throw Exception("Gagal mengambil data penyakit"); } } catch (e) { print("Error getHama: $e"); throw Exception("Gagal mengambil data penyakit"); } } Future> getPenyakitById(int id) async { try { final response = await http.get(Uri.parse('$penyakitUrl/$id')); print('Fetching penyakit with ID $id from $penyakitUrl/$id'); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); print('Response data: $responseData'); // Periksa format respons if (responseData is Map && responseData.containsKey("data")) { final data = responseData["data"]; return Map.from(data); } else if (responseData is Map) { // Jika langsung mengembalikan objek tanpa wrapper "data" return responseData; } else { throw Exception("Format respons API tidak sesuai"); } } else { print('Error response: ${response.statusCode} - ${response.body}'); throw Exception( "Gagal mengambil data penyakit dengan ID $id (Status: ${response.statusCode})", ); } } catch (e) { print("Error getPenyakitById: $e"); throw Exception("Gagal mengambil data penyakit dengan ID $id: $e"); } } // Fungsi untuk mendapatkan URL gambar penyakit String getPenyakitImageUrl(int id) { return '$penyakitUrl/$id/image'; } // Fungsi untuk mengecek apakah gambar tersedia Future isPenyakitImageAvailable(int id) async { try { final url = Uri.parse(getHamaImageUrl(id)); print('Checking image availability: $url'); final response = await http.head(url); print('Image availability status: ${response.statusCode}'); return response.statusCode == 200; } catch (e) { print("Error checking image availability: $e"); return false; } } Future getPenyakitImageBytes(int id) async { try { final url = Uri.parse(getPenyakitImageUrl(id)); print('Fetching image bytes from: $url'); final response = await http.get(url); if (response.statusCode == 200) { return response.bodyBytes; } else { print('Failed to get image bytes: ${response.statusCode}'); print( 'Response body: ${response.body}', ); // Tambahkan ini untuk melihat pesan error return null; } } catch (e) { print('Error getting image bytes: $e'); return null; } } // Tambah penyakit baru (kode otomatis) Future> createPenyakit( String nama, String deskripsi, String penanganan, XFile? pickedFile, ) async { try { var uri = Uri.parse(penyakitUrl); var request = http.MultipartRequest('POST', uri); request.fields['nama'] = nama; request.fields['deskripsi'] = deskripsi; request.fields['penanganan'] = penanganan; print('Mengirim request ke: $uri'); print('Dengan fields: ${request.fields}'); if (pickedFile != null) { String mimeType = 'image/jpeg'; String fileName = pickedFile.name; if (fileName.isEmpty) { fileName = pickedFile.path.split('/').last; } if (fileName.toLowerCase().endsWith('.png')) { mimeType = 'image/png'; } else if (fileName.toLowerCase().endsWith('.jpg') || fileName.toLowerCase().endsWith('.jpeg')) { mimeType = 'image/jpeg'; } final bytes = await pickedFile.readAsBytes(); request.files.add( http.MultipartFile.fromBytes( 'foto', // Sesuaikan dengan field name yang diterima backend bytes, filename: fileName, contentType: MediaType.parse(mimeType), ), ); print('Menambahkan file: $fileName (${bytes.length} bytes)'); } else { print('Tidak ada file yang dilampirkan'); } var streamedResponse = await request.send(); var response = await http.Response.fromStream(streamedResponse); print('Status response: ${response.statusCode}'); print('Body response: ${response.body}'); if (response.statusCode == 201) { return jsonDecode(response.body); } else { String errorMessage = 'Gagal menambahkan penyakit (kode: ${response.statusCode})'; try { var errorBody = jsonDecode(response.body); if (errorBody is Map && errorBody.containsKey('message')) { errorMessage = errorBody['message']; } } catch (e) { if (response.body.isNotEmpty) { errorMessage = response.body; } } throw Exception(errorMessage); } } catch (e) { print('Error dalam createPenyakit: $e'); throw Exception('Gagal menambahkan penyakit: $e'); } } // Update penyakit berdasarkan ID Future> updatePenyakit( int id, String nama, String deskripsi, String penanganan, XFile? pickedFile, ) async { try { var uri = Uri.parse('$penyakitUrl/$id'); var request = http.MultipartRequest('PUT', uri); // Tambahkan fields untuk data teks request.fields['nama'] = nama; request.fields['deskripsi'] = deskripsi; request.fields['penanganan'] = penanganan; // Log untuk debugging print('Mengirim request ke: $uri'); print('Dengan fields: ${request.fields}'); if (pickedFile != null) { // Dapatkan tipe MIME berdasarkan ekstensi file String mimeType = 'image/jpeg'; // Default String fileName = pickedFile.name; if (fileName.isEmpty) { fileName = pickedFile.path.split('/').last; } if (fileName.toLowerCase().endsWith('.png')) { mimeType = 'image/png'; } else if (fileName.toLowerCase().endsWith('.jpg') || fileName.toLowerCase().endsWith('.jpeg')) { mimeType = 'image/jpeg'; } // Baca file sebagai bytes final bytes = await pickedFile.readAsBytes(); // Tambahkan file ke request dengan tipe yang tepat request.files.add( http.MultipartFile.fromBytes( 'foto', // Nama field ini harus sama dengan yang diharapkan backend bytes, filename: fileName, contentType: MediaType.parse(mimeType), ), ); print('Menambahkan file: $fileName (${bytes.length} bytes)'); } else { print('Tidak ada file yang dilampirkan'); } // Kirim request var streamedResponse = await request.send(); var response = await http.Response.fromStream(streamedResponse); // Debug response print('Status response: ${response.statusCode}'); print('Body response: ${response.body}'); if (response.statusCode == 200) { return jsonDecode(response.body); } else { // Coba ambil pesan error dari response body String errorMessage = 'Gagal mengupdate hama (kode: ${response.statusCode})'; try { var errorBody = jsonDecode(response.body); if (errorBody is Map && errorBody.containsKey('message')) { errorMessage = errorBody['message']; } } catch (e) { // Jika gagal parse JSON, gunakan response body langsung if (response.body.isNotEmpty) { errorMessage = response.body; } } throw Exception(errorMessage); } } catch (e) { print('Error dalam updateHama: $e'); throw Exception('Gagal mengupdate hama: $e'); } } // Hapus penyakit berdasarkan ID Future deletePenyakit(int id) async { try { final response = await http.delete(Uri.parse('$penyakitUrl/$id')); if (response.statusCode != 200) { throw Exception('Gagal menghapus penyakit'); } } catch (e) { print('Error deletePenyakit: $e'); throw Exception('Gagal menghapus penyakit'); } } //registrasi Future registerUser({ required String name, required String email, required String password, required String alamat, required String nomorTelepon, }) async { final response = await http.post( Uri.parse('$baseUrl/register'), // Endpoint register headers: {"Content-Type": "application/json"}, body: jsonEncode({ 'name': name, 'email': email, 'password': password, 'alamat': alamat, 'nomorTelepon': nomorTelepon, 'role': 'user', // role default }), ); if (response.statusCode != 201) { throw Exception( jsonDecode(response.body)['message'] ?? 'Gagal mendaftar', ); } } // Fungsi untuk lupa password Future forgotPassword({ required String email, required String newPassword, }) async { final response = await http.post( Uri.parse('$baseUrl/forgot-password'), headers: {"Content-Type": "application/json"}, body: jsonEncode({ 'email': email, 'password': newPassword, // Kirim password baru }), ); if (response.statusCode != 200) { throw Exception( jsonDecode(response.body)['message'] ?? 'Gagal memperbarui password', ); } } // Create Rule penyakit static Future createRulePenyakit({ required int idGejala, int? idPenyakit, required double nilaiPakar, }) async { final response = await http.post( Uri.parse('$rulesPenyakitUrl'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'id_gejala': idGejala, 'id_penyakit': idPenyakit, 'nilai_pakar': nilaiPakar, }), ); return response; } //get all rules penyakit Future> getRulesPenyakit() async { final response = await http.get(Uri.parse(rulesPenyakitUrl)); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['data'] == null) { throw Exception('Data rules kosong'); } return data['data']; } else { throw Exception('Gagal mengambil data rules: ${response.statusCode}'); } } // Update Rule penyakit static Future updateRulePenyakit({ required int id, required int idGejala, int? idPenyakit, required double nilaiPakar, }) async { final response = await http.put( Uri.parse('$rulesPenyakitUrl/$id'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'id_gejala': idGejala, 'id_penyakit': idPenyakit, 'nilai_pakar': nilaiPakar, }), ); return response; } // Delete Rule penyakit static Future deleteRulePenyakit(int id) async { final response = await http.delete(Uri.parse('$rulesPenyakitUrl/$id')); return response; } // Create Rule Hama static Future createRuleHama({ required int idGejala, int? idHama, required double nilaiPakar, }) async { try { // Mencetak URL untuk debugging print("URL API: $rulesHamaUrl"); // Kirim request POST ke server final response = await http.post( Uri.parse('$rulesHamaUrl'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'id_gejala': idGejala, 'id_hama': idHama, 'nilai_pakar': nilaiPakar, }), ); // Pengecekan status response if (response.statusCode == 200 || response.statusCode == 201) { // Jika berhasil, kembalikan response return response; } else { // Jika gagal, cetak error dan lempar exception print("Gagal: ${response.statusCode} - ${response.body}"); throw Exception('Gagal menyimpan rule hama. ${response.body}'); } } catch (e) { print('Error: $e'); rethrow; // Rethrow exception agar bisa ditangani di tempat lain } } //get all rules hama Future> getRulesHama() async { final response = await http.get(Uri.parse(rulesHamaUrl)); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['data'] == null) { throw Exception('Data rules kosong'); } return data['data']; } else { throw Exception('Gagal mengambil data rules: ${response.statusCode}'); } } // Update Rule hama static Future updateRuleHama({ required int id, required int idGejala, int? idHama, required double nilaiPakar, }) async { final response = await http.put( Uri.parse('$rulesHamaUrl/$id'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'id_gejala': idGejala, 'id_hama': idHama, 'nilai_pakar': nilaiPakar, }), ); return response; } // Delete Rule hama static Future deleteRuleHama(int id) async { final response = await http.delete(Uri.parse('$rulesHamaUrl/$id')); return response; } }