import 'dart:convert'; import 'dart:io'; import 'dart:async'; import 'package:http_parser/http_parser.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; class AuthService { // ================= BASE URL ================= static const String baseUrl = 'https://ta.myhost.id/E31231033/backend-laravel/public/api'; /// ================= DEFAULT HEADERS ================= static const Map _headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', }; /// ================= LOGIN ================= static Future> login({ required String email, required String password, }) async { try { final response = await http.post( Uri.parse('$baseUrl/login'), headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: jsonEncode({'email': email, 'password': password}), ); final data = jsonDecode(response.body); if (response.statusCode == 200 && data['status'] == true) { final prefs = await SharedPreferences.getInstance(); final user = data['data']?['user']; final token = data['data']?['token']; if (user == null || token == null) { return {'success': false, 'message': 'Response server tidak valid'}; } await prefs.setString('token', token); await prefs.setString('role', user['role']); await prefs.setString('nama_user', user['name']); await prefs.setBool('isLogin', true); return { 'success': true, 'data': {'user': user, 'token': token} }; } return { 'success': false, 'message': data['message'] ?? 'Login gagal', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= REGISTER ================= static Future> register({ required String name, required String email, required String password, }) async { try { final response = await http.post( Uri.parse('$baseUrl/register'), headers: _headers, body: jsonEncode({'name': name, 'email': email, 'password': password}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 201 || response.statusCode == 200, 'message': data['message'] ?? 'Registrasi berhasil', 'new_user': data['new_user'] ?? true, }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= FORGOT PASSWORD ================= static Future> forgotPassword(String email) async { try { final response = await http.post( Uri.parse('$baseUrl/forgot-password'), headers: _headers, body: jsonEncode({'email': email}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && (data['status'] == true || data['success'] == true), 'message': data['message'] ?? 'Gagal kirim OTP', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= VERIFY RESET OTP ================= static Future> verifyResetOtp({ required String email, required String otp, }) async { try { final response = await http.post( Uri.parse('$baseUrl/verify-reset-otp'), headers: _headers, body: jsonEncode({'email': email, 'otp': otp}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && data['status'] == true, 'message': data['message'] ?? 'OTP tidak valid', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= RESET PASSWORD ================= static Future> resetPassword({ required String email, required String otp, required String password, }) async { try { final response = await http.post( Uri.parse('$baseUrl/reset-password'), headers: _headers, body: jsonEncode({'email': email, 'otp': otp, 'password': password}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && data['status'] == true, 'message': data['message'] ?? 'Password berhasil diubah', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= VERIFY OTP ================= static Future> verifyOtp({ required String email, required String otp, }) async { try { final response = await http.post( Uri.parse('$baseUrl/verify-otp'), headers: _headers, body: jsonEncode({'email': email, 'otp': otp}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && data['status'] == true, 'message': data['message'] ?? 'OTP tidak valid', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= RESEND OTP ================= static Future> resendOtp( {required String email}) async { try { final response = await http.post( Uri.parse('$baseUrl/resend-otp'), headers: _headers, body: jsonEncode({'email': email}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && data['status'] == true, 'message': data['message'] ?? 'Gagal mengirim ulang OTP', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= GET PROFILE ================= static Future> getProfile() async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); if (token == null) { return {'success': false, 'message': 'Token tidak ditemukan'}; } final response = await http.get( Uri.parse('$baseUrl/me'), headers: {..._headers, 'Authorization': 'Bearer $token'}, ); final data = jsonDecode(response.body); if (response.statusCode == 200 && data['status'] == true) { return {'success': true, 'data': data['data']}; } return {'success': false, 'message': data['message']}; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= KIRIM LAPORAN 1 FOTO ================= static Future> kirimLaporan({ required String jadwalId, required String lokasiId, required String keterangan, required double latitude, required double longitude, required File foto, }) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); if (token == null) { return {'success': false, 'message': 'Token tidak ditemukan'}; } var request = http.MultipartRequest( "POST", Uri.parse('$baseUrl/laporan'), ); request.headers['Accept'] = 'application/json'; request.headers['Authorization'] = 'Bearer $token'; request.headers['User-Agent'] = 'Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36'; request.fields['jadwal_id'] = jadwalId; request.fields['lokasi_id'] = lokasiId; request.fields['keterangan'] = keterangan; request.fields['latitude'] = latitude.toString(); request.fields['longitude'] = longitude.toString(); request.files.add(await http.MultipartFile.fromPath('foto', foto.path)); var streamedResponse = await request.send(); var response = await http.Response.fromStream(streamedResponse); final data = jsonDecode(response.body); return { 'success': response.statusCode == 201, 'message': data['message'] ?? 'Gagal kirim laporan', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= MULTI FOTO & VIDEO ================= static Future> kirimLaporanMultiMedia({ required String jadwalId, required String lokasiId, required String keterangan, required double latitude, required double longitude, required List images, required List videos, void Function(double progress, String status)? onProgress, }) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); if (token == null) { return {'success': false, 'message': 'Token tidak ditemukan'}; } onProgress?.call(0.0, 'Menyiapkan file...'); var request = http.MultipartRequest( "POST", Uri.parse('$baseUrl/laporan'), ); request.headers['Accept'] = 'application/json'; request.headers['Authorization'] = 'Bearer $token'; request.fields['jadwal_id'] = jadwalId; request.fields['lokasi_id'] = lokasiId; request.fields['keterangan'] = keterangan; request.fields['latitude'] = latitude.toString(); request.fields['longitude'] = longitude.toString(); for (int i = 0; i < images.length; i++) { final file = images[i]; final ext = file.path.split('.').last.toLowerCase(); request.files.add( http.MultipartFile( 'foto[]', file.openRead(), await file.length(), filename: 'foto_$i.$ext', contentType: MediaType('image', ext == 'png' ? 'png' : 'jpeg'), ), ); } for (int i = 0; i < videos.length; i++) { final file = videos[i]; final ext = file.path.split('.').last.toLowerCase(); request.files.add( http.MultipartFile( 'video[]', file.openRead(), await file.length(), filename: 'video_$i.$ext', contentType: MediaType('video', 'mp4'), ), ); } onProgress?.call(0.2, 'Mengupload...'); final streamedResponse = await request.send().timeout(const Duration(minutes: 5)); final response = await http.Response.fromStream(streamedResponse); onProgress?.call(1.0, 'Selesai'); final data = jsonDecode(response.body); if (response.statusCode == 200 || response.statusCode == 201) { return { 'success': true, 'message': data['message'] ?? 'Laporan berhasil dikirim', 'data': data, }; } else { String errorMsg = data['message'] ?? 'Gagal kirim laporan'; if (data['errors'] != null) { final errors = data['errors'] as Map; errorMsg = errors.values.map((e) => e[0]).join('\n'); } return { 'success': false, 'message': errorMsg, 'data': data, }; } } on TimeoutException { return { 'success': false, 'message': 'Timeout. Coba koneksi lebih stabil.' }; } on SocketException { return {'success': false, 'message': 'Tidak ada koneksi internet'}; } catch (e) { return {'success': false, 'message': 'Error: ${e.toString()}'}; } } /// ================= UPDATE PROFILE ================= static Future> updateProfile({ required String name, required String email, String? noHp, }) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); final response = await http.put( Uri.parse('$baseUrl/update-profile'), headers: { ..._headers, 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode({'name': name, 'email': email, 'no_hp': noHp ?? ""}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200, 'message': data['message'], }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= UPLOAD FOTO PROFIL ================= static Future> uploadPhoto(File image) async { try { String? token = await getToken(); if (token == null) { return {'status': false, 'message': 'Token tidak ditemukan'}; } var request = http.MultipartRequest( 'POST', Uri.parse("$baseUrl/upload-foto"), ); request.headers['Authorization'] = "Bearer $token"; request.headers['Accept'] = 'application/json'; var multipartFile = await http.MultipartFile.fromPath('foto', image.path); request.files.add(multipartFile); var streamedResponse = await request.send(); var res = await http.Response.fromStream(streamedResponse); final data = jsonDecode(res.body); if (data['status'] == true) { final prefs = await SharedPreferences.getInstance(); await prefs.setString('foto', data['data']['foto']); } return data; } catch (e) { return {'status': false, 'message': e.toString()}; } } /// ================= DELETE FOTO PROFIL ================= static Future> deletePhoto() async { try { final token = await getToken(); if (token == null) { return {'status': false, 'message': 'Token tidak ditemukan'}; } final response = await http.delete( Uri.parse('$baseUrl/delete-photo'), headers: { 'Accept': 'application/json', 'Authorization': 'Bearer $token', }, ); final data = jsonDecode(response.body); if (data['status'] == true) { final prefs = await SharedPreferences.getInstance(); await prefs.remove('foto'); } return data; } catch (e) { return {'status': false, 'message': e.toString()}; } } /// ================= GET TOKEN ================= static Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('token'); } /// ================= GET NAMA USER ================= static Future getNamaUser() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('nama_user'); } /// ================= GET ROLE ================= static Future getRole() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('role'); } /// ================= UPDATE EMAIL ================= static Future> updateEmail(String newEmail) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); if (token == null) { return {'success': false, 'message': 'Token tidak ditemukan'}; } final response = await http.put( Uri.parse('$baseUrl/update-email'), headers: { ..._headers, 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode({'email': newEmail}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && data['status'] == true, 'message': data['message'] ?? 'Gagal memperbarui email', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= UPDATE PASSWORD ================= static Future> updatePassword( String oldPassword, String newPassword, ) async { try { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); final response = await http.put( Uri.parse('$baseUrl/update-password'), headers: { ..._headers, 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode({ 'old_password': oldPassword, 'new_password': newPassword, 'confirm_password': newPassword, }), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && data['status'] == true, 'message': data['message'], }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= SEND EMAIL OTP (untuk ganti email) ================= /// Mengirim kode OTP 6 digit ke [newEmail] yang akan digunakan sebagai email baru. /// Backend: POST /email/send-otp { email: newEmail } /// Response yang diharapkan: { status: true, message: "..." } static Future> sendEmailOtp(String newEmail) async { try { final token = await getToken(); if (token == null) { return {'success': false, 'message': 'Token tidak ditemukan'}; } final response = await http.post( Uri.parse('$baseUrl/email/send-otp'), headers: { ..._headers, 'Authorization': 'Bearer $token', }, body: jsonEncode({'email': newEmail}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && data['status'] == true, 'message': data['message'] ?? 'Gagal mengirim OTP', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= VERIFY EMAIL OTP (untuk ganti email) ================= /// Memverifikasi [otp] yang dikirim ke [newEmail], sekaligus mengganti email user. /// Backend: POST /email/verify-otp { email: newEmail, otp: "123456" } /// Response yang diharapkan: { status: true, message: "..." } static Future> verifyEmailOtp( String newEmail, String otp, ) async { try { final token = await getToken(); if (token == null) { return {'success': false, 'message': 'Token tidak ditemukan'}; } final response = await http.post( Uri.parse('$baseUrl/email/verify-otp'), headers: { ..._headers, 'Authorization': 'Bearer $token', }, body: jsonEncode({'email': newEmail, 'otp': otp}), ); final data = jsonDecode(response.body); return { 'success': response.statusCode == 200 && data['status'] == true, 'message': data['message'] ?? 'OTP tidak valid atau sudah kadaluarsa', }; } catch (e) { return {'success': false, 'message': e.toString()}; } } /// ================= LOGOUT ================= static Future logout() async { final prefs = await SharedPreferences.getInstance(); await prefs.clear(); } }