Aplikasi Absen

This commit is contained in:
RafiFattan23 2026-01-14 15:39:15 +07:00
parent ed435ffbc1
commit b1cb5cdc1a
2 changed files with 189 additions and 136 deletions

26
.idea/appInsightsSettings.xml generated Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="tabSettings">
<map>
<entry key="Firebase Crashlytics">
<value>
<InsightsFilterSettings>
<option name="connection">
<ConnectionSetting>
<option name="appId" value="PLACEHOLDER" />
<option name="mobileSdkAppId" value="" />
<option name="projectId" value="" />
<option name="projectNumber" value="" />
</ConnectionSetting>
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />
<option name="timeIntervalDays" value="THIRTY_DAYS" />
<option name="visibilityType" value="ALL" />
</InsightsFilterSettings>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@ -14,10 +14,17 @@ 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.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
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.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
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
@ -41,12 +48,12 @@ fun kirimKeN8n(
context: ComponentActivity, context: ComponentActivity,
latitude: Double, latitude: Double,
longitude: Double, longitude: Double,
foto: Bitmap foto: Bitmap,
selectedMatkul: 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,8 +61,9 @@ fun kirimKeN8n(
conn.doOutput = true conn.doOutput = true
val json = JSONObject().apply { val json = JSONObject().apply {
put("npm", "12345") put("npm", "202310715002")
put("nama","Arif R D") put("nama", "Rafi Fattan Fitriardi")
put("matkul", selectedMatkul) // <-- hanya mata kuliah yang dipilih
put("latitude", latitude) put("latitude", latitude)
put("longitude", longitude) put("longitude", longitude)
put("timestamp", System.currentTimeMillis()) put("timestamp", System.currentTimeMillis())
@ -80,7 +88,6 @@ fun kirimKeN8n(
} }
conn.disconnect() conn.disconnect()
} catch (_: Exception) { } catch (_: Exception) {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText( Toast.makeText(
@ -93,22 +100,23 @@ fun kirimKeN8n(
} }
} }
/* ================= MODEL ================= */
data class Absensi(
val matkul: String,
val waktu: String
)
/* ================= 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
)
}
} }
} }
} }
@ -116,159 +124,178 @@ class MainActivity : ComponentActivity() {
/* ================= UI ================= */ /* ================= UI ================= */
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AbsensiScreen( fun AbsensiScreen(activity: ComponentActivity) {
modifier: Modifier = Modifier,
activity: ComponentActivity
) {
val context = LocalContext.current
var lokasi by remember { mutableStateOf("Koordinat: -") } val context = LocalContext.current
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
// ===== DATA & STATE =====
val matkulList = listOf(
"Pemrograman Perangkat Bergerak",
"Pembelajaran Mesin",
"Keamanan Siber",
"Kecerdasan Buatan",
"Interaksi Manusia dan Komputer",
"Manajemen Proyek Perangkat Lunak"
)
var selectedMatkul by remember { mutableStateOf(matkulList[0]) }
var expanded by remember { mutableStateOf(false) }
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) }
var foto by remember { mutableStateOf<Bitmap?>(null) } var foto by remember { mutableStateOf<Bitmap?>(null) }
val absensiList = remember { mutableStateListOf<Absensi>() }
val fusedLocationClient = // ===== PERMISSION LOKASI =====
LocationServices.getFusedLocationProviderClient(context)
/* ===== Permission Lokasi ===== */
val locationPermissionLauncher = val locationPermissionLauncher =
rememberLauncherForActivityResult( rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) { if (granted) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
if ( == PackageManager.PERMISSION_GRANTED
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) { ) {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
fusedLocationClient.lastLocation latitude = location?.latitude
.addOnSuccessListener { location -> longitude = location?.longitude
if (location != null) {
latitude = location.latitude
longitude = location.longitude
lokasi =
"Lat: ${location.latitude}\nLon: ${location.longitude}"
} else {
lokasi = "Lokasi tidak tersedia"
} }
} }
.addOnFailureListener {
lokasi = "Gagal mengambil lokasi"
} }
} }
} else { // ===== KAMERA =====
Toast.makeText(
context,
"Izin lokasi ditolak",
Toast.LENGTH_SHORT
).show()
}
}
/* ===== Kamera ===== */
val cameraLauncher = val cameraLauncher =
rememberLauncherForActivityResult( rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
val bitmap = foto = result.data?.extras?.getParcelable("data", Bitmap::class.java)
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()) { granted ->
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) { if (granted) {
val intent = cameraLauncher.launch(Intent(MediaStore.ACTION_IMAGE_CAPTURE))
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 ===== */ // ===== UI =====
Box(
Column( modifier = Modifier
modifier = modifier .fillMaxSize()
.background(
Brush.verticalGradient(
listOf(
MaterialTheme.colorScheme.primary.copy(alpha = 0.15f),
MaterialTheme.colorScheme.background
)
)
)
) {
Scaffold(
containerColor = Color.Transparent,
topBar = {
TopAppBar(title = { Text("Absensi Akademik") })
}
) { padding ->
Column(
modifier = Modifier
.padding(padding)
.padding(16.dp)
.fillMaxSize() .fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center
) { ) {
Text( // ===== CARD UTAMA =====
text = "Absensi Akademik", Card(
style = MaterialTheme.typography.titleLarge modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(6.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Mata Kuliah", style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(8.dp))
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded }
) {
TextField(
value = selectedMatkul,
onValueChange = {},
readOnly = true,
modifier = Modifier.menuAnchor().fillMaxWidth()
) )
Spacer(modifier = Modifier.height(16.dp)) ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
matkulList.forEach {
Text(text = lokasi) DropdownMenuItem(
text = { Text(it) },
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { onClick = {
cameraPermissionLauncher.launch( selectedMatkul = it
Manifest.permission.CAMERA expanded = false
}
) )
}, }
}
}
Spacer(Modifier.height(16.dp))
Text("Riwayat Absensi", style = MaterialTheme.typography.titleMedium)
LazyColumn(modifier = Modifier.height(120.dp)) {
items(absensiList) {
Text("${it.matkul} - ${it.waktu}")
}
}
}
}
// ===== PREVIEW FOTO =====
if (foto != null) {
Spacer(Modifier.height(16.dp))
Card(
modifier = Modifier.fillMaxWidth().height(220.dp),
elevation = CardDefaults.cardElevation(6.dp)
) {
Image(
bitmap = foto!!.asImageBitmap(),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
}
Spacer(Modifier.height(24.dp))
// ===== TOMBOL =====
Button(
onClick = { cameraPermissionLauncher.launch(Manifest.permission.CAMERA) },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Ambil Foto") Text("Ambil Foto")
} }
Spacer(modifier = Modifier.height(12.dp)) Spacer(Modifier.height(12.dp))
Button( Button(
onClick = { onClick = {
if (latitude != null && longitude != null && foto != null) { if (latitude != null && longitude != null && foto != null) {
kirimKeN8n( kirimKeN8n(activity, latitude!!, longitude!!, foto!!, selectedMatkul)
activity, absensiList.add(Absensi(selectedMatkul, System.currentTimeMillis().toString()))
latitude!!,
longitude!!,
foto!!
)
} else { } else {
Toast.makeText( Toast.makeText(context, "Lokasi atau foto belum lengkap", Toast.LENGTH_SHORT).show()
context,
"Lokasi atau foto belum lengkap",
Toast.LENGTH_SHORT
).show()
} }
}, },
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF2E7D32)),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Kirim Absensi") Text("Kirim Absensi")
} }
} }
} }
}
}