Compare commits
No commits in common. "bde4891e40a04b4d0b28566332e3db3bc8aa37e1" and "cbe7e50b9621330a16f900cded7f674a939644a5" have entirely different histories.
bde4891e40
...
cbe7e50b96
8
.idea/deploymentTargetSelector.xml
generated
8
.idea/deploymentTargetSelector.xml
generated
@ -4,14 +4,6 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-01-13T05:20:56.137492Z">
|
|
||||||
<Target type="DEFAULT_BOOT">
|
|
||||||
<handle>
|
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=RR8TA08RD8Z" />
|
|
||||||
</handle>
|
|
||||||
</Target>
|
|
||||||
</DropdownSelection>
|
|
||||||
<DialogSelection />
|
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
13
.idea/deviceManager.xml
generated
13
.idea/deviceManager.xml
generated
@ -1,13 +0,0 @@
|
|||||||
<?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>
|
|
||||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
|||||||
50
.idea/inspectionProfiles/Project_Default.xml
generated
50
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,50 +0,0 @@
|
|||||||
<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
8
.idea/markdown.xml
generated
@ -1,8 +0,0 @@
|
|||||||
<?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>
|
|
||||||
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
|||||||
6
.idea/studiobot.xml
generated
6
.idea/studiobot.xml
generated
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="StudioBotProjectSettings">
|
|
||||||
<option name="shareContext" value="OptedIn" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
BIN
Mockup.png
BIN
Mockup.png
Binary file not shown.
|
Before Width: | Height: | Size: 715 KiB |
17
README.md
17
README.md
@ -79,19 +79,4 @@ Aplikasi memerlukan izin berikut:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📂 Mockup
|
## 📂 Struktur Proyek (Contoh)
|
||||||

|
|
||||||
gambar mockup dibuat oleh AI
|
|
||||||
|
|
||||||
## Catatan:
|
|
||||||
- Starter project ini dibuat berbantukan AI
|
|
||||||
- Kembangkan project dari starter yang sudah disediakan, jangan membuat dari awal.
|
|
||||||
- Untuk koordinat bisa ditambah/kurangi angka tertentu agar tidak memunculkan koordinat rumah masing-masing, data awal tetap dari GPS.
|
|
||||||
|
|
||||||
## Pengecekan:
|
|
||||||
- https://ntfy.ubharajaya.ac.id/EAS
|
|
||||||
- https://docs.google.com/spreadsheets/d/1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs/edit?gid=0#gid=0
|
|
||||||
|
|
||||||
## Webhook:
|
|
||||||
- test: https://n8n.lab.ubharajaya.ac.id/webhook-test/23c6993d-1792-48fb-ad1c-ffc78a3e6254
|
|
||||||
- production: https://n8n.lab.ubharajaya.ac.id/webhook/23c6993d-1792-48fb-ad1c-ffc78a3e6254
|
|
||||||
|
|||||||
@ -45,15 +45,11 @@ dependencies {
|
|||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation("androidx.activity:activity-compose:1.9.0")
|
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.compose.ui)
|
implementation(libs.androidx.compose.ui)
|
||||||
implementation(libs.androidx.compose.ui.graphics)
|
implementation(libs.androidx.compose.ui.graphics)
|
||||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||||
implementation(libs.androidx.compose.material3)
|
implementation(libs.androidx.compose.material3)
|
||||||
// Location (GPS)
|
|
||||||
implementation("com.google.android.gms:play-services-location:21.0.1")
|
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
@ -2,14 +2,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.camera"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
|||||||
@ -1,112 +1,17 @@
|
|||||||
package id.ac.ubharajaya.sistemakademik
|
package id.ac.ubharajaya.sistemakademik
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.util.Base64
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
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.compose.foundation.layout.fillMaxSize
|
||||||
import android.annotation.SuppressLint
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.material3.*
|
|
||||||
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import com.google.android.gms.location.LocationServices
|
|
||||||
import id.ac.ubharajaya.sistemakademik.ui.theme.SistemAkademikTheme
|
import id.ac.ubharajaya.sistemakademik.ui.theme.SistemAkademikTheme
|
||||||
import org.json.JSONObject
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.net.URL
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
|
|
||||||
|
|
||||||
/* ================= UTIL (TIDAK DIUBAH) ================= */
|
|
||||||
|
|
||||||
fun bitmapToBase64(bitmap: Bitmap): String {
|
|
||||||
val out = ByteArrayOutputStream()
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out)
|
|
||||||
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(
|
|
||||||
activity: ComponentActivity,
|
|
||||||
npm: String,
|
|
||||||
nama: String,
|
|
||||||
matkul: String,
|
|
||||||
lat: Double,
|
|
||||||
lon: Double,
|
|
||||||
foto: Bitmap,
|
|
||||||
status: String
|
|
||||||
) {
|
|
||||||
thread {
|
|
||||||
try {
|
|
||||||
val url = URL("https://n8n.lab.ubharajaya.ac.id/webhook/23c6993d-1792-48fb-ad1c-ffc78a3e6254")
|
|
||||||
val conn = url.openConnection() as HttpURLConnection
|
|
||||||
conn.requestMethod = "POST"
|
|
||||||
conn.setRequestProperty("Content-Type", "application/json")
|
|
||||||
conn.doOutput = true
|
|
||||||
|
|
||||||
val json = JSONObject().apply {
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.outputStream.use { it.write(json.toString().toByteArray()) }
|
|
||||||
|
|
||||||
val responseCode = conn.responseCode
|
|
||||||
activity.runOnUiThread {
|
|
||||||
Toast.makeText(
|
|
||||||
activity,
|
|
||||||
if (responseCode == 200) "Absensi berhasil" else "Absensi ditolak",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
conn.disconnect()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
Toast.makeText(activity, "Gagal kirim absensi", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================= ACTIVITY ================= */
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -114,150 +19,29 @@ class MainActivity : ComponentActivity() {
|
|||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
SistemAkademikTheme {
|
SistemAkademikTheme {
|
||||||
AbsensiScreen(this)
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||||
|
Greeting(
|
||||||
|
name = "Android",
|
||||||
|
modifier = Modifier.padding(innerPadding)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= UI ================= */
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AbsensiScreen(activity: ComponentActivity) {
|
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||||
val context = LocalContext.current
|
Text(
|
||||||
|
text = "Hello $name!",
|
||||||
var npm by remember { mutableStateOf("") }
|
modifier = modifier
|
||||||
var nama by remember { mutableStateOf("") }
|
|
||||||
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) }
|
|
||||||
|
|
||||||
val LAT_ABSEN = -6.255
|
|
||||||
val LON_ABSEN = 106.618
|
|
||||||
val RADIUS = 30.0 // meter (REALISTIS)
|
|
||||||
|
|
||||||
val fusedClient = LocationServices.getFusedLocationProviderClient(context)
|
|
||||||
|
|
||||||
val locationPermissionLauncher =
|
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
|
|
||||||
if (it && ContextCompat.checkSelfPermission(
|
|
||||||
context,
|
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION
|
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
) {
|
|
||||||
ambilLokasi(fusedClient) { la, lo ->
|
|
||||||
lat = la
|
|
||||||
lon = lo
|
|
||||||
lokasi = "Lat: $la | Lon: $lo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val cameraLauncher =
|
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
|
||||||
if (it.resultCode == Activity.RESULT_OK) {
|
|
||||||
foto = it.data?.extras?.get("data") as? Bitmap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val cameraPermissionLauncher =
|
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
|
|
||||||
if (it) cameraLauncher.launch(Intent(MediaStore.ACTION_IMAGE_CAPTURE))
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(20.dp),
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
elevation = CardDefaults.cardElevation(8.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(20.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Text("Absensi Akademik", style = MaterialTheme.typography.titleLarge)
|
|
||||||
|
|
||||||
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(lokasi, style = MaterialTheme.typography.bodySmall)
|
|
||||||
|
|
||||||
Button(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = { cameraPermissionLauncher.launch(Manifest.permission.CAMERA) }
|
|
||||||
) {
|
|
||||||
Text("Ambil Foto")
|
|
||||||
}
|
|
||||||
|
|
||||||
foto?.let {
|
|
||||||
Image(
|
|
||||||
bitmap = it.asImageBitmap(),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(200.dp),
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = {
|
|
||||||
if (npm.isBlank() || nama.isBlank() || matkul.isBlank()
|
|
||||||
|| lat == null || lon == null || foto == null
|
|
||||||
) {
|
|
||||||
Toast.makeText(context, "Data belum lengkap", Toast.LENGTH_SHORT).show()
|
|
||||||
return@Button
|
|
||||||
}
|
|
||||||
|
|
||||||
val jarak = hitungJarak(lat!!, lon!!, LAT_ABSEN, LON_ABSEN)
|
|
||||||
|
|
||||||
// 🔴 BLOK TOTAL JIKA DI LUAR RADIUS
|
|
||||||
if (jarak > RADIUS) {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Anda berada di luar radius absensi",
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
return@Button
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ HANYA JIKA VALID BARU KIRIM
|
|
||||||
kirimKeN8n(
|
|
||||||
activity,
|
|
||||||
npm,
|
|
||||||
nama,
|
|
||||||
matkul,
|
|
||||||
lat!!,
|
|
||||||
lon!!,
|
|
||||||
foto!!,
|
|
||||||
"VALID"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("Kirim Absensi")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@Preview(showBackground = true)
|
||||||
fun ambilLokasi(
|
@Composable
|
||||||
client: com.google.android.gms.location.FusedLocationProviderClient,
|
fun GreetingPreview() {
|
||||||
onResult: (Double, Double) -> Unit
|
SistemAkademikTheme {
|
||||||
) {
|
Greeting("Android")
|
||||||
client.lastLocation.addOnSuccessListener {
|
|
||||||
if (it != null) onResult(it.latitude, it.longitude)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,225 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "EAS",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"method": "POST",
|
|
||||||
"url": "https://ntfy.ubharajaya.ac.id/EAS",
|
|
||||||
"sendBody": true,
|
|
||||||
"contentType": "raw",
|
|
||||||
"body": "=Absensi: {{ $json.body.nama }} NPM: {{ $json.body.npm }}",
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.3,
|
|
||||||
"position": [
|
|
||||||
-272,
|
|
||||||
-240
|
|
||||||
],
|
|
||||||
"id": "83504eec-6d20-46d7-9ea1-509ae4ee8660",
|
|
||||||
"name": "NTFY HTTP Request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"httpMethod": "POST",
|
|
||||||
"path": "23c6993d-1792-48fb-ad1c-ffc78a3e6254",
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"type": "n8n-nodes-base.webhook",
|
|
||||||
"typeVersion": 2.1,
|
|
||||||
"position": [
|
|
||||||
-864,
|
|
||||||
-112
|
|
||||||
],
|
|
||||||
"id": "9ed3d2db-2d50-40b5-8408-7404edd48442",
|
|
||||||
"name": "Webhook Absensi",
|
|
||||||
"webhookId": "23c6993d-1792-48fb-ad1c-ffc78a3e6254"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"operation": "append",
|
|
||||||
"documentId": {
|
|
||||||
"__rl": true,
|
|
||||||
"value": "1jH15MfnNgpPGuGeid0hYfY7fFUHCEFbCmg8afTyyLZs",
|
|
||||||
"mode": "id"
|
|
||||||
},
|
|
||||||
"sheetName": {
|
|
||||||
"__rl": true,
|
|
||||||
"value": "Absensi",
|
|
||||||
"mode": "name"
|
|
||||||
},
|
|
||||||
"columns": {
|
|
||||||
"mappingMode": "defineBelow",
|
|
||||||
"value": {
|
|
||||||
"latitude": "={{ $json.body.latitude }}",
|
|
||||||
"longitude": "={{ $json.body.longitude }}",
|
|
||||||
"timestamp": "={{ $json.body.timestamp }}",
|
|
||||||
"foto_base64": "={{ $json.body.foto_base64 }}",
|
|
||||||
"nama": "={{ $json.body.nama }}",
|
|
||||||
"npm": "={{ $json.body.npm }}"
|
|
||||||
},
|
|
||||||
"matchingColumns": [],
|
|
||||||
"schema": [
|
|
||||||
{
|
|
||||||
"id": "timestamp",
|
|
||||||
"displayName": "timestamp",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"type": "string",
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"removed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "npm",
|
|
||||||
"displayName": "npm",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"type": "string",
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"removed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "nama",
|
|
||||||
"displayName": "nama",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"type": "string",
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"removed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "latitude",
|
|
||||||
"displayName": "latitude",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"type": "string",
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"removed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "longitude",
|
|
||||||
"displayName": "longitude",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"type": "string",
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"removed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "photo",
|
|
||||||
"displayName": "photo",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"type": "string",
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"removed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "status",
|
|
||||||
"displayName": "status",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"type": "string",
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"removed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "foto_base64",
|
|
||||||
"displayName": "foto_base64",
|
|
||||||
"required": false,
|
|
||||||
"defaultMatch": false,
|
|
||||||
"display": true,
|
|
||||||
"type": "string",
|
|
||||||
"canBeUsedToMatch": true,
|
|
||||||
"removed": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attemptToConvertTypes": false,
|
|
||||||
"convertFieldsToString": false
|
|
||||||
},
|
|
||||||
"options": {}
|
|
||||||
},
|
|
||||||
"type": "n8n-nodes-base.googleSheets",
|
|
||||||
"typeVersion": 4.7,
|
|
||||||
"position": [
|
|
||||||
-272,
|
|
||||||
-32
|
|
||||||
],
|
|
||||||
"id": "cd83a9fa-ea00-4a20-aa31-846bfe044aeb",
|
|
||||||
"name": "Append row in sheet",
|
|
||||||
"credentials": {
|
|
||||||
"googleSheetsOAuth2Api": {
|
|
||||||
"id": "hNVNhkTQbqkJ3C56",
|
|
||||||
"name": "Google Sheets account"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nfor (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();"
|
|
||||||
},
|
|
||||||
"type": "n8n-nodes-base.code",
|
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
|
||||||
-528,
|
|
||||||
-240
|
|
||||||
],
|
|
||||||
"id": "4ed9edf6-4562-41b6-afd0-89c96991454a",
|
|
||||||
"name": "Code in JavaScript"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pinData": {},
|
|
||||||
"connections": {
|
|
||||||
"Webhook Absensi": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Append row in sheet",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node": "Code in JavaScript",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"NTFY HTTP Request": {
|
|
||||||
"main": [
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Code in JavaScript": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "NTFY HTTP Request",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"active": true,
|
|
||||||
"settings": {
|
|
||||||
"executionOrder": "v1",
|
|
||||||
"availableInMCP": false
|
|
||||||
},
|
|
||||||
"versionId": "49466b31-67ce-49b7-af37-33cd28d7092d",
|
|
||||||
"meta": {
|
|
||||||
"templateCredsSetupCompleted": true,
|
|
||||||
"instanceId": "b8ffac81bb85d267c3296e074b3e692ecef11caeef79fa72af892085548f350a"
|
|
||||||
},
|
|
||||||
"id": "E_gxZpNrN3G5ibejHcTFS",
|
|
||||||
"tags": []
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user