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") + } + } } } }