diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
new file mode 100644
index 0000000..371f2e2
--- /dev/null
+++ b/.idea/appInsightsSettings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ No newline at end of file
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..1d72750 100644
--- a/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt
+++ b/app/src/main/java/id/ac/ubharajaya/sistemakademik/MainActivity.kt
@@ -14,10 +14,17 @@ 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.background
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.unit.dp
import androidx.core.content.ContextCompat
@@ -41,12 +48,12 @@ fun kirimKeN8n(
context: ComponentActivity,
latitude: Double,
longitude: Double,
- foto: Bitmap
+ foto: Bitmap,
+ selectedMatkul: 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,8 +61,9 @@ fun kirimKeN8n(
conn.doOutput = true
val json = JSONObject().apply {
- put("npm", "12345")
- put("nama","Arif R D")
+ put("npm", "202310715002")
+ put("nama", "Rafi Fattan Fitriardi")
+ put("matkul", selectedMatkul) // <-- hanya mata kuliah yang dipilih
put("latitude", latitude)
put("longitude", longitude)
put("timestamp", System.currentTimeMillis())
@@ -80,7 +88,6 @@ fun kirimKeN8n(
}
conn.disconnect()
-
} catch (_: Exception) {
context.runOnUiThread {
Toast.makeText(
@@ -93,22 +100,23 @@ fun kirimKeN8n(
}
}
+/* ================= MODEL ================= */
+
+data class Absensi(
+ val matkul: String,
+ val waktu: String
+)
+
/* ================= ACTIVITY ================= */
class MainActivity : ComponentActivity() {
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
SistemAkademikTheme {
- Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
- AbsensiScreen(
- modifier = Modifier.padding(innerPadding),
- activity = this
- )
- }
+ AbsensiScreen(this)
}
}
}
@@ -116,159 +124,178 @@ class MainActivity : ComponentActivity() {
/* ================= UI ================= */
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun AbsensiScreen(
- modifier: Modifier = Modifier,
- activity: ComponentActivity
-) {
- val context = LocalContext.current
+fun AbsensiScreen(activity: ComponentActivity) {
- 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(null) }
var longitude by remember { mutableStateOf(null) }
var foto by remember { mutableStateOf(null) }
+ val absensiList = remember { mutableStateListOf() }
- val fusedLocationClient =
- LocationServices.getFusedLocationProviderClient(context)
-
- /* ===== Permission Lokasi ===== */
-
+ // ===== PERMISSION LOKASI =====
val locationPermissionLauncher =
- rememberLauncherForActivityResult(
- ActivityResultContracts.RequestPermission()
- ) { granted ->
+ rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
-
- if (
- ContextCompat.checkSelfPermission(
- context,
- Manifest.permission.ACCESS_FINE_LOCATION
- ) == PackageManager.PERMISSION_GRANTED
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED
) {
-
- fusedLocationClient.lastLocation
- .addOnSuccessListener { location ->
- 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"
- }
+ fusedLocationClient.lastLocation.addOnSuccessListener { location ->
+ latitude = location?.latitude
+ longitude = location?.longitude
+ }
}
-
- } else {
- Toast.makeText(
- context,
- "Izin lokasi ditolak",
- Toast.LENGTH_SHORT
- ).show()
}
}
- /* ===== Kamera ===== */
-
+ // ===== KAMERA =====
val cameraLauncher =
- rememberLauncherForActivityResult(
- ActivityResultContracts.StartActivityForResult()
- ) { result ->
+ rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
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()
- }
+ foto = result.data?.extras?.getParcelable("data", Bitmap::class.java)
}
}
val cameraPermissionLauncher =
- rememberLauncherForActivityResult(
- ActivityResultContracts.RequestPermission()
- ) { granted ->
+ rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
- val intent =
- Intent(MediaStore.ACTION_IMAGE_CAPTURE)
- cameraLauncher.launch(intent)
- } else {
- Toast.makeText(
- context,
- "Izin kamera ditolak",
- Toast.LENGTH_SHORT
- ).show()
+ cameraLauncher.launch(Intent(MediaStore.ACTION_IMAGE_CAPTURE))
}
}
- /* ===== Request Awal ===== */
-
LaunchedEffect(Unit) {
- locationPermissionLauncher.launch(
- Manifest.permission.ACCESS_FINE_LOCATION
- )
+ locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
- /* ===== UI ===== */
-
- Column(
- modifier = modifier
+ // ===== UI =====
+ Box(
+ modifier = Modifier
.fillMaxSize()
- .padding(24.dp),
- verticalArrangement = Arrangement.Center
- ) {
-
- Text(
- text = "Absensi Akademik",
- style = MaterialTheme.typography.titleLarge
- )
-
- Spacer(modifier = Modifier.height(16.dp))
-
- Text(text = lokasi)
-
- Spacer(modifier = Modifier.height(16.dp))
-
- Button(
- onClick = {
- cameraPermissionLauncher.launch(
- Manifest.permission.CAMERA
- )
- },
- modifier = Modifier.fillMaxWidth()
- ) {
- Text("Ambil Foto")
- }
-
- Spacer(modifier = Modifier.height(12.dp))
-
- Button(
- onClick = {
- if (latitude != null && longitude != null && foto != null) {
- kirimKeN8n(
- activity,
- latitude!!,
- longitude!!,
- foto!!
+ .background(
+ Brush.verticalGradient(
+ listOf(
+ MaterialTheme.colorScheme.primary.copy(alpha = 0.15f),
+ MaterialTheme.colorScheme.background
)
- } else {
- Toast.makeText(
- context,
- "Lokasi atau foto belum lengkap",
- Toast.LENGTH_SHORT
- ).show()
+ )
+ )
+ ) {
+ Scaffold(
+ containerColor = Color.Transparent,
+ topBar = {
+ TopAppBar(title = { Text("Absensi Akademik") })
+ }
+ ) { padding ->
+ Column(
+ modifier = Modifier
+ .padding(padding)
+ .padding(16.dp)
+ .fillMaxSize()
+ ) {
+
+ // ===== CARD UTAMA =====
+ Card(
+ 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()
+ )
+
+ ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
+ matkulList.forEach {
+ DropdownMenuItem(
+ text = { Text(it) },
+ onClick = {
+ selectedMatkul = it
+ 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}")
+ }
+ }
+ }
}
- },
- modifier = Modifier.fillMaxWidth()
- ) {
- Text("Kirim Absensi")
+
+ // ===== 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()
+ ) {
+ Text("Ambil Foto")
+ }
+
+ Spacer(Modifier.height(12.dp))
+
+ Button(
+ onClick = {
+ if (latitude != null && longitude != null && foto != null) {
+ kirimKeN8n(activity, latitude!!, longitude!!, foto!!, selectedMatkul)
+ absensiList.add(Absensi(selectedMatkul, System.currentTimeMillis().toString()))
+ } else {
+ Toast.makeText(context, "Lokasi atau foto belum lengkap", Toast.LENGTH_SHORT).show()
+ }
+ },
+ colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF2E7D32)),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text("Kirim Absensi")
+ }
+ }
}
}
}