EAS 2026
This commit is contained in:
parent
ed435ffbc1
commit
bde4891e40
13
.idea/deviceManager.xml
generated
Normal file
13
.idea/deviceManager.xml
generated
Normal 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>
|
||||||
50
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
50
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
8
.idea/markdown.xml
generated
Normal file
8
.idea/markdown.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MarkdownSettings">
|
||||||
|
<option name="previewPanelProviderInfo">
|
||||||
|
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@ -3,7 +3,6 @@ package id.ac.ubharajaya.sistemakademik
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
@ -14,10 +13,16 @@ 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 android.annotation.SuppressLint
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
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
|
||||||
@ -28,66 +33,74 @@ import java.io.ByteArrayOutputStream
|
|||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
|
||||||
/* ================= UTIL ================= */
|
|
||||||
|
/* ================= UTIL (TIDAK DIUBAH) ================= */
|
||||||
|
|
||||||
fun bitmapToBase64(bitmap: Bitmap): String {
|
fun bitmapToBase64(bitmap: Bitmap): String {
|
||||||
val outputStream = ByteArrayOutputStream()
|
val out = ByteArrayOutputStream()
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)
|
||||||
return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP)
|
return Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hitungJarak(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
|
||||||
|
val R = 6371000.0
|
||||||
|
val dLat = Math.toRadians(lat2 - lat1)
|
||||||
|
val dLon = Math.toRadians(lon2 - lon1)
|
||||||
|
val a =
|
||||||
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
|
Math.cos(Math.toRadians(lat1)) *
|
||||||
|
Math.cos(Math.toRadians(lat2)) *
|
||||||
|
Math.sin(dLon / 2) * Math.sin(dLon / 2)
|
||||||
|
val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||||||
|
return R * c
|
||||||
}
|
}
|
||||||
|
|
||||||
fun kirimKeN8n(
|
fun kirimKeN8n(
|
||||||
context: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
latitude: Double,
|
npm: String,
|
||||||
longitude: Double,
|
nama: String,
|
||||||
foto: Bitmap
|
matkul: String,
|
||||||
|
lat: Double,
|
||||||
|
lon: Double,
|
||||||
|
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"
|
||||||
conn.setRequestProperty("Content-Type", "application/json")
|
conn.setRequestProperty("Content-Type", "application/json")
|
||||||
conn.doOutput = true
|
conn.doOutput = true
|
||||||
|
|
||||||
val json = JSONObject().apply {
|
val json = JSONObject().apply {
|
||||||
put("npm", "12345")
|
|
||||||
put("nama","Arif R D")
|
|
||||||
put("latitude", latitude)
|
|
||||||
put("longitude", longitude)
|
|
||||||
put("timestamp", System.currentTimeMillis())
|
put("timestamp", System.currentTimeMillis())
|
||||||
|
put("ip_addr", "android")
|
||||||
|
put("npm", npm)
|
||||||
|
put("nama", nama)
|
||||||
|
put("latitude", lat)
|
||||||
|
put("longitude", lon)
|
||||||
|
put("mata_kuliah", matkul)
|
||||||
|
put("status", status)
|
||||||
put("foto_base64", bitmapToBase64(foto))
|
put("foto_base64", bitmapToBase64(foto))
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.outputStream.use {
|
conn.outputStream.use { it.write(json.toString().toByteArray()) }
|
||||||
it.write(json.toString().toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
val responseCode = conn.responseCode
|
val responseCode = conn.responseCode
|
||||||
|
activity.runOnUiThread {
|
||||||
context.runOnUiThread {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
activity,
|
||||||
if (responseCode == 200)
|
if (responseCode == 200) "Absensi berhasil" else "Absensi ditolak",
|
||||||
"Absensi diterima server"
|
|
||||||
else
|
|
||||||
"Absensi ditolak server",
|
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.disconnect()
|
conn.disconnect()
|
||||||
|
} catch (e: Exception) {
|
||||||
} catch (_: Exception) {
|
activity.runOnUiThread {
|
||||||
context.runOnUiThread {
|
Toast.makeText(activity, "Gagal kirim absensi", Toast.LENGTH_SHORT).show()
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Gagal kirim ke server",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,19 +109,12 @@ fun kirimKeN8n(
|
|||||||
/* ================= ACTIVITY ================= */
|
/* ================= ACTIVITY ================= */
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
SistemAkademikTheme {
|
SistemAkademikTheme {
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
AbsensiScreen(this)
|
||||||
AbsensiScreen(
|
|
||||||
modifier = Modifier.padding(innerPadding),
|
|
||||||
activity = this
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,158 +123,141 @@ class MainActivity : ComponentActivity() {
|
|||||||
/* ================= UI ================= */
|
/* ================= UI ================= */
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AbsensiScreen(
|
fun AbsensiScreen(activity: ComponentActivity) {
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
activity: ComponentActivity
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
var lokasi by remember { mutableStateOf("Koordinat: -") }
|
var npm by remember { mutableStateOf("") }
|
||||||
var latitude by remember { mutableStateOf<Double?>(null) }
|
var nama by remember { mutableStateOf("") }
|
||||||
var longitude by remember { mutableStateOf<Double?>(null) }
|
var matkul by remember { mutableStateOf("") }
|
||||||
|
var lat by remember { mutableStateOf<Double?>(null) }
|
||||||
|
var lon by remember { mutableStateOf<Double?>(null) }
|
||||||
|
var lokasi by remember { mutableStateOf("Lokasi belum didapat") }
|
||||||
var foto by remember { mutableStateOf<Bitmap?>(null) }
|
var foto by remember { mutableStateOf<Bitmap?>(null) }
|
||||||
|
|
||||||
val fusedLocationClient =
|
val LAT_ABSEN = -6.255
|
||||||
LocationServices.getFusedLocationProviderClient(context)
|
val LON_ABSEN = 106.618
|
||||||
|
val RADIUS = 30.0 // meter (REALISTIS)
|
||||||
|
|
||||||
/* ===== Permission Lokasi ===== */
|
val fusedClient = LocationServices.getFusedLocationProviderClient(context)
|
||||||
|
|
||||||
val locationPermissionLauncher =
|
val locationPermissionLauncher =
|
||||||
rememberLauncherForActivityResult(
|
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||||
ActivityResultContracts.RequestPermission()
|
if (it && ContextCompat.checkSelfPermission(
|
||||||
) { granted ->
|
|
||||||
if (granted) {
|
|
||||||
|
|
||||||
if (
|
|
||||||
ContextCompat.checkSelfPermission(
|
|
||||||
context,
|
context,
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
) {
|
) {
|
||||||
|
ambilLokasi(fusedClient) { la, lo ->
|
||||||
fusedLocationClient.lastLocation
|
lat = la
|
||||||
.addOnSuccessListener { location ->
|
lon = lo
|
||||||
if (location != null) {
|
lokasi = "Lat: $la | Lon: $lo"
|
||||||
latitude = location.latitude
|
|
||||||
longitude = location.longitude
|
|
||||||
lokasi =
|
|
||||||
"Lat: ${location.latitude}\nLon: ${location.longitude}"
|
|
||||||
} else {
|
|
||||||
lokasi = "Lokasi tidak tersedia"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.addOnFailureListener {
|
|
||||||
lokasi = "Gagal mengambil lokasi"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Izin lokasi ditolak",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== Kamera ===== */
|
|
||||||
|
|
||||||
val cameraLauncher =
|
val cameraLauncher =
|
||||||
rememberLauncherForActivityResult(
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
ActivityResultContracts.StartActivityForResult()
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
) { result ->
|
foto = it.data?.extras?.get("data") as? Bitmap
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
|
||||||
val bitmap =
|
|
||||||
result.data?.extras?.getParcelable("data", Bitmap::class.java)
|
|
||||||
if (bitmap != null) {
|
|
||||||
foto = bitmap
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Foto berhasil diambil",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cameraPermissionLauncher =
|
val cameraPermissionLauncher =
|
||||||
rememberLauncherForActivityResult(
|
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||||
ActivityResultContracts.RequestPermission()
|
if (it) cameraLauncher.launch(Intent(MediaStore.ACTION_IMAGE_CAPTURE))
|
||||||
) { granted ->
|
|
||||||
if (granted) {
|
|
||||||
val intent =
|
|
||||||
Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
|
||||||
cameraLauncher.launch(intent)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Izin kamera ditolak",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== Request Awal ===== */
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
locationPermissionLauncher.launch(
|
locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== UI ===== */
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(20.dp),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
elevation = CardDefaults.cardElevation(8.dp)
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = Modifier.padding(20.dp),
|
||||||
.fillMaxSize()
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
.padding(24.dp),
|
|
||||||
verticalArrangement = Arrangement.Center
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Text(
|
Text("Absensi Akademik", style = MaterialTheme.typography.titleLarge)
|
||||||
text = "Absensi Akademik",
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
OutlinedTextField(npm, { npm = it }, label = { Text("NPM") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
OutlinedTextField(nama, { nama = it }, label = { Text("Nama") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
OutlinedTextField(matkul, { matkul = it }, label = { Text("Mata Kuliah") }, modifier = Modifier.fillMaxWidth())
|
||||||
|
|
||||||
Text(text = lokasi)
|
Text(lokasi, style = MaterialTheme.typography.bodySmall)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
modifier = Modifier.fillMaxWidth(),
|
||||||
cameraPermissionLauncher.launch(
|
onClick = { cameraPermissionLauncher.launch(Manifest.permission.CAMERA) }
|
||||||
Manifest.permission.CAMERA
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
) {
|
||||||
Text("Ambil Foto")
|
Text("Ambil Foto")
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
foto?.let {
|
||||||
|
Image(
|
||||||
|
bitmap = it.asImageBitmap(),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(200.dp),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (latitude != null && longitude != null && foto != null) {
|
if (npm.isBlank() || nama.isBlank() || matkul.isBlank()
|
||||||
kirimKeN8n(
|
|| lat == null || lon == null || foto == null
|
||||||
activity,
|
) {
|
||||||
latitude!!,
|
Toast.makeText(context, "Data belum lengkap", Toast.LENGTH_SHORT).show()
|
||||||
longitude!!,
|
return@Button
|
||||||
foto!!
|
}
|
||||||
)
|
|
||||||
} else {
|
val jarak = hitungJarak(lat!!, lon!!, LAT_ABSEN, LON_ABSEN)
|
||||||
|
|
||||||
|
// 🔴 BLOK TOTAL JIKA DI LUAR RADIUS
|
||||||
|
if (jarak > RADIUS) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
"Lokasi atau foto belum lengkap",
|
"Anda berada di luar radius absensi",
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
|
return@Button
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ HANYA JIKA VALID BARU KIRIM
|
||||||
|
kirimKeN8n(
|
||||||
|
activity,
|
||||||
|
npm,
|
||||||
|
nama,
|
||||||
|
matkul,
|
||||||
|
lat!!,
|
||||||
|
lon!!,
|
||||||
|
foto!!,
|
||||||
|
"VALID"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
) {
|
||||||
Text("Kirim Absensi")
|
Text("Kirim Absensi")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
fun ambilLokasi(
|
||||||
|
client: com.google.android.gms.location.FusedLocationProviderClient,
|
||||||
|
onResult: (Double, Double) -> Unit
|
||||||
|
) {
|
||||||
|
client.lastLocation.addOnSuccessListener {
|
||||||
|
if (it != null) onResult(it.latitude, it.longitude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user