diff --git a/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt b/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt index c774502..fc0f566 100644 --- a/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt +++ b/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt @@ -5,6 +5,7 @@ import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap +import android.location.Location import android.os.Bundle import android.provider.MediaStore import android.util.Base64 @@ -14,10 +15,13 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat @@ -29,6 +33,21 @@ import java.net.HttpURLConnection import java.net.URL import kotlin.concurrent.thread +/* ================= KONFIGURASI LOKASI ================= */ + +const val KAMPUS_LAT = -6.222967558410948 +const val KAMPUS_LON = 107.00931291609834 +const val MAX_RADIUS = 1000 // meter + +fun hitungJarak( + lat1: Double, lon1: Double, + lat2: Double, lon2: Double +): Float { + val hasil = FloatArray(1) + Location.distanceBetween(lat1, lon1, lat2, lon2, hasil) + return hasil[0] +} + /* ================= UTIL ================= */ fun bitmapToBase64(bitmap: Bitmap): String { @@ -39,14 +58,17 @@ fun bitmapToBase64(bitmap: Bitmap): String { fun kirimKeN8n( context: ComponentActivity, + npm: String, + nama: String, + mataKuliah: String, latitude: Double, longitude: Double, - foto: Bitmap + foto: Bitmap, + status: String ) { thread { try { val url = URL("https://n8n.lab.ubharajaya.ac.id/webhook/23c6993d-1792-48fb-ad1c-ffc78a3e6254") -// test URL val url = URL("https://n8n.lab.ubharajaya.ac.id/webhook-test/23c6993d-1792-48fb-ad1c-ffc78a3e6254") val conn = url.openConnection() as HttpURLConnection conn.requestMethod = "POST" @@ -54,11 +76,13 @@ fun kirimKeN8n( conn.doOutput = true val json = JSONObject().apply { - put("npm", "12345") - put("nama","Arif R D") + put("npm", npm) + put("nama", nama) + put("mata_kuliah", mataKuliah) put("latitude", latitude) put("longitude", longitude) put("timestamp", System.currentTimeMillis()) + put("status", status) put("foto_base64", bitmapToBase64(foto)) } @@ -72,9 +96,9 @@ fun kirimKeN8n( Toast.makeText( context, if (responseCode == 200) - "Absensi diterima server" + "Absensi $status" else - "Absensi ditolak server", + "Server menolak absensi", Toast.LENGTH_SHORT ).show() } @@ -123,6 +147,10 @@ fun AbsensiScreen( ) { val context = LocalContext.current + var npm by remember { mutableStateOf("") } + var nama by remember { mutableStateOf("") } + var mataKuliah by remember { mutableStateOf("") } + var lokasi by remember { mutableStateOf("Koordinat: -") } var latitude by remember { mutableStateOf(null) } var longitude by remember { mutableStateOf(null) } @@ -138,14 +166,12 @@ fun AbsensiScreen( ActivityResultContracts.RequestPermission() ) { granted -> if (granted) { - if ( ContextCompat.checkSelfPermission( context, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED ) { - fusedLocationClient.lastLocation .addOnSuccessListener { location -> if (location != null) { @@ -157,11 +183,7 @@ fun AbsensiScreen( lokasi = "Lokasi tidak tersedia" } } - .addOnFailureListener { - lokasi = "Gagal mengambil lokasi" - } } - } else { Toast.makeText( context, @@ -178,8 +200,7 @@ fun AbsensiScreen( ActivityResultContracts.StartActivityForResult() ) { result -> if (result.resultCode == Activity.RESULT_OK) { - val bitmap = - result.data?.extras?.getParcelable("data", Bitmap::class.java) + val bitmap = result.data?.extras?.get("data") as? Bitmap if (bitmap != null) { foto = bitmap Toast.makeText( @@ -187,6 +208,12 @@ fun AbsensiScreen( "Foto berhasil diambil", Toast.LENGTH_SHORT ).show() + } else { + Toast.makeText( + context, + "Gagal mengambil foto", + Toast.LENGTH_SHORT + ).show() } } } @@ -196,8 +223,7 @@ fun AbsensiScreen( ActivityResultContracts.RequestPermission() ) { granted -> if (granted) { - val intent = - Intent(MediaStore.ACTION_IMAGE_CAPTURE) + val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) cameraLauncher.launch(intent) } else { Toast.makeText( @@ -208,8 +234,6 @@ fun AbsensiScreen( } } - /* ===== Request Awal ===== */ - LaunchedEffect(Unit) { locationPermissionLauncher.launch( Manifest.permission.ACCESS_FINE_LOCATION @@ -232,9 +256,32 @@ fun AbsensiScreen( Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = npm, + onValueChange = { npm = it }, + label = { Text("NPM") }, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = nama, + onValueChange = { nama = it }, + label = { Text("Nama") }, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = mataKuliah, + onValueChange = { mataKuliah = it }, + label = { Text("Mata Kuliah") }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(12.dp)) + Text(text = lokasi) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(12.dp)) Button( onClick = { @@ -247,21 +294,66 @@ fun AbsensiScreen( Text("Ambil Foto") } + /* ===== PREVIEW FOTO ===== */ + + if (foto != null) { + Spacer(modifier = Modifier.height(12.dp)) + Image( + bitmap = foto!!.asImageBitmap(), + contentDescription = "Preview Foto Absensi", + modifier = Modifier + .fillMaxWidth() + .height(220.dp), + contentScale = ContentScale.Crop + ) + } + Spacer(modifier = Modifier.height(12.dp)) Button( onClick = { - if (latitude != null && longitude != null && foto != null) { - kirimKeN8n( - activity, + if ( + npm.isNotEmpty() && + nama.isNotEmpty() && + mataKuliah.isNotEmpty() && + latitude != null && + longitude != null && + foto != null + ) { + + val jarak = hitungJarak( latitude!!, longitude!!, - foto!! + KAMPUS_LAT, + KAMPUS_LON ) + + val status = + if (jarak <= MAX_RADIUS) "HADIR" else "DITOLAK" + + if (status == "HADIR") { + kirimKeN8n( + activity, + npm, + nama, + mataKuliah, + latitude!!, + longitude!!, + foto!!, + status + ) + } else { + Toast.makeText( + context, + "Absensi ditolak (di luar area)", + Toast.LENGTH_LONG + ).show() + } + } else { Toast.makeText( context, - "Lokasi atau foto belum lengkap", + "Data absensi belum lengkap", Toast.LENGTH_SHORT ).show() } @@ -271,4 +363,4 @@ fun AbsensiScreen( Text("Kirim Absensi") } } -} +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 99b1b95..1cc9d66 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,7 +12,7 @@ pluginManagement { } } dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositoriesMode.set(org.gradle.api.initialization.resolve.RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() @@ -21,4 +21,3 @@ dependencyResolutionManagement { rootProject.name = "Sistem Akademik" include(":app") - \ No newline at end of file