EAS-Muhammad-Rafi

This commit is contained in:
202310715191 MUHAMMAD RAFI 2026-01-14 11:04:07 +07:00
parent ed435ffbc1
commit f4788298db
2 changed files with 129 additions and 24 deletions

13
.idea/deviceManager.xml generated Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

View File

@ -5,6 +5,7 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.location.Location
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Base64 import android.util.Base64
@ -14,10 +15,13 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier 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.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -29,6 +33,21 @@ import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import kotlin.concurrent.thread import kotlin.concurrent.thread
/* ================= KONFIGURASI LOKASI ================= */
const val KAMPUS_LAT = -6.222967558410948
const val KAMPUS_LON = 107.00931291609834
const val MAX_RADIUS = 50// 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 ================= */ /* ================= UTIL ================= */
fun bitmapToBase64(bitmap: Bitmap): String { fun bitmapToBase64(bitmap: Bitmap): String {
@ -39,14 +58,17 @@ fun bitmapToBase64(bitmap: Bitmap): String {
fun kirimKeN8n( fun kirimKeN8n(
context: ComponentActivity, context: ComponentActivity,
npm: String,
nama: String,
mataKuliah: String,
latitude: Double, latitude: Double,
longitude: Double, longitude: Double,
foto: Bitmap foto: Bitmap,
status: String
) { ) {
thread { thread {
try { try {
val url = URL("https://n8n.lab.ubharajaya.ac.id/webhook/23c6993d-1792-48fb-ad1c-ffc78a3e6254") 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 val conn = url.openConnection() as HttpURLConnection
conn.requestMethod = "POST" conn.requestMethod = "POST"
@ -54,11 +76,13 @@ fun kirimKeN8n(
conn.doOutput = true conn.doOutput = true
val json = JSONObject().apply { val json = JSONObject().apply {
put("npm", "12345") put("npm", npm)
put("nama","Arif R D") put("nama", nama)
put("mata_kuliah", mataKuliah)
put("latitude", latitude) put("latitude", latitude)
put("longitude", longitude) put("longitude", longitude)
put("timestamp", System.currentTimeMillis()) put("timestamp", System.currentTimeMillis())
put("status", status)
put("foto_base64", bitmapToBase64(foto)) put("foto_base64", bitmapToBase64(foto))
} }
@ -72,9 +96,9 @@ fun kirimKeN8n(
Toast.makeText( Toast.makeText(
context, context,
if (responseCode == 200) if (responseCode == 200)
"Absensi diterima server" "Absensi $status"
else else
"Absensi ditolak server", "Server menolak absensi",
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }
@ -123,6 +147,10 @@ fun AbsensiScreen(
) { ) {
val context = LocalContext.current 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 lokasi by remember { mutableStateOf("Koordinat: -") }
var latitude by remember { mutableStateOf<Double?>(null) } var latitude by remember { mutableStateOf<Double?>(null) }
var longitude by remember { mutableStateOf<Double?>(null) } var longitude by remember { mutableStateOf<Double?>(null) }
@ -138,14 +166,12 @@ fun AbsensiScreen(
ActivityResultContracts.RequestPermission() ActivityResultContracts.RequestPermission()
) { granted -> ) { granted ->
if (granted) { if (granted) {
if ( if (
ContextCompat.checkSelfPermission( ContextCompat.checkSelfPermission(
context, context,
Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED ) == PackageManager.PERMISSION_GRANTED
) { ) {
fusedLocationClient.lastLocation fusedLocationClient.lastLocation
.addOnSuccessListener { location -> .addOnSuccessListener { location ->
if (location != null) { if (location != null) {
@ -157,11 +183,7 @@ fun AbsensiScreen(
lokasi = "Lokasi tidak tersedia" lokasi = "Lokasi tidak tersedia"
} }
} }
.addOnFailureListener {
lokasi = "Gagal mengambil lokasi"
} }
}
} else { } else {
Toast.makeText( Toast.makeText(
context, context,
@ -178,8 +200,7 @@ fun AbsensiScreen(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
val bitmap = val bitmap = result.data?.extras?.get("data") as? Bitmap
result.data?.extras?.getParcelable("data", Bitmap::class.java)
if (bitmap != null) { if (bitmap != null) {
foto = bitmap foto = bitmap
Toast.makeText( Toast.makeText(
@ -187,6 +208,12 @@ fun AbsensiScreen(
"Foto berhasil diambil", "Foto berhasil diambil",
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} else {
Toast.makeText(
context,
"Gagal mengambil foto",
Toast.LENGTH_SHORT
).show()
} }
} }
} }
@ -196,8 +223,7 @@ fun AbsensiScreen(
ActivityResultContracts.RequestPermission() ActivityResultContracts.RequestPermission()
) { granted -> ) { granted ->
if (granted) { if (granted) {
val intent = val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
Intent(MediaStore.ACTION_IMAGE_CAPTURE)
cameraLauncher.launch(intent) cameraLauncher.launch(intent)
} else { } else {
Toast.makeText( Toast.makeText(
@ -208,8 +234,6 @@ fun AbsensiScreen(
} }
} }
/* ===== Request Awal ===== */
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
locationPermissionLauncher.launch( locationPermissionLauncher.launch(
Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
@ -232,9 +256,32 @@ fun AbsensiScreen(
Spacer(modifier = Modifier.height(16.dp)) 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) Text(text = lokasi)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(12.dp))
Button( Button(
onClick = { onClick = {
@ -247,21 +294,66 @@ fun AbsensiScreen(
Text("Ambil Foto") 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)) Spacer(modifier = Modifier.height(12.dp))
Button( Button(
onClick = { onClick = {
if (latitude != null && longitude != null && foto != null) { if (
kirimKeN8n( npm.isNotEmpty() &&
activity, nama.isNotEmpty() &&
mataKuliah.isNotEmpty() &&
latitude != null &&
longitude != null &&
foto != null
) {
val jarak = hitungJarak(
latitude!!, latitude!!,
longitude!!, 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 { } else {
Toast.makeText( Toast.makeText(
context, context,
"Lokasi atau foto belum lengkap", "Absensi ditolak (di luar area)",
Toast.LENGTH_LONG
).show()
}
} else {
Toast.makeText(
context,
"Data absensi belum lengkap",
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
} }