Compare commits

..

6 Commits

4 changed files with 499 additions and 105 deletions

13
.idea/deviceManager.xml generated Normal file
View 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>

View 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>

View File

@ -1,10 +1,14 @@
# 📱 Aplikasi Absensi Akademik Berbasis Koordinat dan Foto (Mobile) # 📱 Aplikasi Absensi Akademik Berbasis Koordinat dan Foto (Mobile)
Nama Pengembang : **Syahril Achmad Fahrezi** **NAMA :** **Syahril Achmad Fahrezi**
NPM : **202310715211**
**NPM :** **202310715211**
**MATA KULIAH** : **PERMROGRAMAN PERANGKAT BERGERAK**
**- INI DIKEMBANGKAN BUKAN BUAT DARI AWAL -**
## 📌 Deskripsi Proyek
Proyek ini merupakan **Tugas Project Akhir Mata Kuliah Pemrograman Mobile** yang bertujuan untuk membangun **aplikasi akademik berbasis mobile** dengan fokus pada **fitur absensi menggunakan data koordinat (GPS) dan pengambilan foto mahasiswa**. Proyek ini merupakan **Tugas Project Akhir Mata Kuliah Pemrograman Mobile** yang bertujuan untuk membangun **aplikasi akademik berbasis mobile** dengan fokus pada **fitur absensi menggunakan data koordinat (GPS) dan pengambilan foto mahasiswa**.
Aplikasi ini dirancang untuk meningkatkan **validitas kehadiran mahasiswa**, dengan memastikan bahwa absensi hanya dapat dilakukan apabila mahasiswa: Aplikasi ini dirancang untuk meningkatkan **validitas kehadiran mahasiswa**, dengan memastikan bahwa absensi hanya dapat dilakukan apabila mahasiswa:

View File

@ -5,6 +5,7 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.location.Location
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Base64 import android.util.Base64
@ -14,11 +15,26 @@ 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.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.draw.scale
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.text.font.FontWeight
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationServices
@ -31,6 +47,22 @@ import kotlin.concurrent.thread
/* ================= UTIL ================= */ /* ================= UTIL ================= */
// Koordinat Kampus Universitas Bhayangkara Jakarta Raya
const val KAMPUS_LATITUDE = -6.2642
const val KAMPUS_LONGITUDE = 107.0008
const val RADIUS_METER = 100.0
fun hitungJarak(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Float {
val results = FloatArray(1)
Location.distanceBetween(lat1, lon1, lat2, lon2, results)
return results[0]
}
fun cekDalamRadius(latitude: Double, longitude: Double): Boolean {
val jarak = hitungJarak(latitude, longitude, KAMPUS_LATITUDE, KAMPUS_LONGITUDE)
return jarak <= RADIUS_METER
}
fun bitmapToBase64(bitmap: Bitmap): String { fun bitmapToBase64(bitmap: Bitmap): String {
val outputStream = ByteArrayOutputStream() val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream) bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
@ -41,12 +73,13 @@ fun kirimKeN8n(
context: ComponentActivity, context: ComponentActivity,
latitude: Double, latitude: Double,
longitude: Double, longitude: Double,
foto: Bitmap foto: Bitmap,
onSuccess: () -> Unit,
onError: () -> Unit
) { ) {
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"
@ -55,7 +88,9 @@ fun kirimKeN8n(
val json = JSONObject().apply { val json = JSONObject().apply {
put("npm", "202310715211") put("npm", "202310715211")
put("nama","Syahril Achmad F")
put("nama", "SyahrilAchmadFAHREZI")
put("latitude", latitude) put("latitude", latitude)
put("longitude", longitude) put("longitude", longitude)
put("timestamp", System.currentTimeMillis()) put("timestamp", System.currentTimeMillis())
@ -69,6 +104,13 @@ fun kirimKeN8n(
val responseCode = conn.responseCode val responseCode = conn.responseCode
context.runOnUiThread { context.runOnUiThread {
<<<<<<< HEAD
if (responseCode == 200) {
onSuccess()
} else {
onError()
}
=======
Toast.makeText( Toast.makeText(
context, context,
if (responseCode == 200) if (responseCode == 200)
@ -83,11 +125,7 @@ fun kirimKeN8n(
} catch (_: Exception) { } catch (_: Exception) {
context.runOnUiThread { context.runOnUiThread {
Toast.makeText( onError()
context,
"Gagal kirim ke server",
Toast.LENGTH_SHORT
).show()
} }
} }
} }
@ -123,152 +161,441 @@ fun AbsensiScreen(
) { ) {
val context = LocalContext.current val context = LocalContext.current
var lokasi by remember { mutableStateOf("Koordinat: -") }
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) }
var dalamRadius by remember { mutableStateOf(false) }
var jarak by remember { mutableStateOf<Float?>(null) }
var isLoading by remember { mutableStateOf(false) }
var showSuccess by remember { mutableStateOf(false) }
var expandedMataKuliah by remember { mutableStateOf(false) }
var selectedMataKuliah by remember { mutableStateOf("Pilih Mata Kuliah") }
val fusedLocationClient = val mataKuliahList = listOf(
LocationServices.getFusedLocationProviderClient(context) "Pemrograman Perangkat Bergerak",
"Deep Learning",
"Keamanan Siber"
)
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
val scale by animateFloatAsState(
targetValue = if (showSuccess) 1f else 0f,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy), label = ""
)
/* ===== Permission Lokasi ===== */ /* ===== Permission Lokasi ===== */
val locationPermissionLauncher = val locationPermissionLauncher =
rememberLauncherForActivityResult( rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) { if (granted) {
if (ContextCompat.checkSelfPermission(
if (
ContextCompat.checkSelfPermission(
context, context,
Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED ) == PackageManager.PERMISSION_GRANTED
) { ) {
fusedLocationClient.lastLocation fusedLocationClient.lastLocation
.addOnSuccessListener { location -> .addOnSuccessListener { location ->
if (location != null) { if (location != null) {
latitude = location.latitude latitude = location.latitude
longitude = location.longitude longitude = location.longitude
lokasi = dalamRadius = cekDalamRadius(location.latitude, location.longitude)
"Lat: ${location.latitude}\nLon: ${location.longitude}" jarak = hitungJarak(
location.latitude,
location.longitude,
KAMPUS_LATITUDE,
KAMPUS_LONGITUDE
)
} else { } else {
lokasi = "Lokasi tidak tersedia" Toast.makeText(context, "Lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
} }
} }
.addOnFailureListener {
lokasi = "Gagal mengambil lokasi"
}
} }
} else { } else {
Toast.makeText( Toast.makeText(context, "Izin lokasi ditolak", Toast.LENGTH_SHORT).show()
context,
"Izin lokasi ditolak",
Toast.LENGTH_SHORT
).show()
} }
} }
/* ===== Kamera ===== */ /* ===== 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 = val bitmap = result.data?.extras?.get("data") as? Bitmap
result.data?.extras?.getParcelable("data", Bitmap::class.java)
if (bitmap != null) { if (bitmap != null) {
foto = bitmap foto = bitmap
Toast.makeText( Toast.makeText(context, "Foto berhasil diambil", Toast.LENGTH_SHORT).show()
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 = val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
Intent(MediaStore.ACTION_IMAGE_CAPTURE)
cameraLauncher.launch(intent) cameraLauncher.launch(intent)
} else { } else {
Toast.makeText( Toast.makeText(context, "Izin kamera ditolak", Toast.LENGTH_SHORT).show()
context,
"Izin kamera ditolak",
Toast.LENGTH_SHORT
).show()
} }
} }
/* ===== Request Awal ===== */ /* ===== 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() .fillMaxSize()
.padding(24.dp), .background(
verticalArrangement = Arrangement.Center Brush.verticalGradient(
) { colors = listOf(
Color(0xFF1a1a2e),
Text( Color(0xFF16213e),
text = "Absensi Akademik", Color(0xFF0f3460)
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() ) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
) { ) {
Text("Ambil Foto") Spacer(modifier = Modifier.height(40.dp))
// Header
Text(
text = "Absensi Akademik",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
color = Color.White
)
Text(
text = "Universitas Bhayangkara Jakarta Raya",
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(alpha = 0.9f)
)
Spacer(modifier = Modifier.height(20.dp))
// Status Lokasi Card
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF0a0a0f).copy(alpha = 0.7f)
),
elevation = CardDefaults.cardElevation(8.dp)
) {
Column(
modifier = Modifier.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Icon Status
Box(
modifier = Modifier
.size(80.dp)
.background(
color = if (dalamRadius) Color(0xFF10B981).copy(alpha = 0.2f)
else Color(0xFFEF4444).copy(alpha = 0.2f),
shape = RoundedCornerShape(40.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = if (dalamRadius) "" else "",
style = MaterialTheme.typography.displayMedium,
color = if (dalamRadius) Color(0xFF10B981) else Color(0xFFEF4444),
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(12.dp))
Text(
text = if (dalamRadius) "Dalam Area Kampus" else "Di Luar Area Kampus",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Normal,
color = if (dalamRadius) Color(0xFF9D4EDD) else Color(0xFFEF4444)
)
Spacer(modifier = Modifier.height(8.dp))
if (jarak != null) {
Text(
text = "Jarak: ${String.format("%.1f", jarak)} meter",
style = MaterialTheme.typography.bodyMedium,
color = Color(0xFFB8B8D0)
)
}
if (latitude != null && longitude != null) {
Text(
text = "Lat: ${String.format("%.6f", latitude)}, Lon: ${String.format("%.6f", longitude)}",
style = MaterialTheme.typography.bodySmall,
color = Color(0xFF8B8BA0)
)
}
}
}
// Foto Preview
AnimatedVisibility(
visible = foto != null,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Card(
modifier = Modifier.size(200.dp),
shape = RoundedCornerShape(20.dp),
elevation = CardDefaults.cardElevation(8.dp)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
foto?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = "Foto Absensi",
modifier = Modifier.fillMaxSize()
)
}
}
}
}
Spacer(modifier = Modifier.weight(1f))
// Dropdown Mata Kuliah
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { expandedMataKuliah = !expandedMataKuliah },
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF0a0a0f).copy(alpha = 0.7f)
),
elevation = CardDefaults.cardElevation(4.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = selectedMataKuliah,
fontSize = 16.sp,
color = if (selectedMataKuliah == "Pilih Mata Kuliah")
Color(0xFF8B8BA0) else Color.White,
fontWeight = FontWeight.Medium
)
Text(
text = if (expandedMataKuliah) "" else "",
color = Color(0xFF9D4EDD),
fontSize = 12.sp
)
}
AnimatedVisibility(visible = expandedMataKuliah) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp)
) {
mataKuliahList.forEach { matkul ->
Text(
text = matkul,
modifier = Modifier
.fillMaxWidth()
.clickable {
selectedMataKuliah = matkul
expandedMataKuliah = false
}
.padding(vertical = 12.dp),
fontSize = 15.sp,
color = Color(0xFFB8B8D0),
fontWeight = FontWeight.Normal
)
if (matkul != mataKuliahList.last()) {
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(Color(0xFF3a3a4e))
)
}
}
}
}
}
}
Spacer(modifier = Modifier.height(12.dp))
// Tombol Ambil Foto
Button(
onClick = {
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
},
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF7B2CBF),
contentColor = Color.White
),
elevation = ButtonDefaults.buttonElevation(8.dp)
) {
Text(
text = "📷 ${if (foto == null) "Ambil Foto" else "Ambil Ulang Foto"}",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
// Tombol Kirim Absensi
Button(
onClick = {
when {
!dalamRadius -> {
Toast.makeText(
context,
"Anda di luar area kampus!",
Toast.LENGTH_LONG
).show()
}
foto == null -> {
Toast.makeText(
context,
"Silakan ambil foto terlebih dahulu!",
Toast.LENGTH_SHORT
).show()
}
selectedMataKuliah == "Pilih Mata Kuliah" -> {
Toast.makeText(
context,
"Silakan pilih mata kuliah terlebih dahulu!",
Toast.LENGTH_SHORT
).show()
}
latitude == null || longitude == null -> {
Toast.makeText(
context,
"Lokasi belum terdeteksi!",
Toast.LENGTH_SHORT
).show()
}
else -> {
isLoading = true
kirimKeN8n(
activity,
latitude!!,
longitude!!,
foto!!,
onSuccess = {
isLoading = false
showSuccess = true
Toast.makeText(
context,
"Absensi berhasil dikirim!",
Toast.LENGTH_SHORT
).show()
},
onError = {
isLoading = false
Toast.makeText(
context,
"Gagal mengirim absensi!",
Toast.LENGTH_SHORT
).show()
}
)
}
}
},
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = if (dalamRadius && foto != null && selectedMataKuliah != "Pilih Mata Kuliah")
Color(0xFF9D4EDD) else Color(0xFF3a3a4e),
contentColor = Color.White
),
elevation = ButtonDefaults.buttonElevation(8.dp),
enabled = !isLoading
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = Color.White,
strokeWidth = 3.dp
)
Spacer(modifier = Modifier.width(8.dp))
Text("Mengirim...")
} else {
Text(
text = "📤 Kirim Absensi",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
}
Spacer(modifier = Modifier.height(20.dp))
} }
Spacer(modifier = Modifier.height(12.dp)) // Success Animation
if (showSuccess) {
Button( Box(
onClick = { modifier = Modifier
if (latitude != null && longitude != null && foto != null) { .fillMaxSize()
kirimKeN8n( .background(Color.Black.copy(alpha = 0.5f)),
activity, contentAlignment = Alignment.Center
latitude!!, ) {
longitude!!, Card(
foto!! modifier = Modifier
) .scale(scale)
} else { .padding(40.dp),
Toast.makeText( shape = RoundedCornerShape(24.dp),
context, colors = CardDefaults.cardColors(containerColor = Color(0xFF0a0a0f))
"Lokasi atau foto belum lengkap", ) {
Toast.LENGTH_SHORT Column(
).show() modifier = Modifier.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "",
style = MaterialTheme.typography.displayLarge,
color = Color(0xFF9D4EDD),
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Absensi Berhasil!",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
color = Color.White
)
}
} }
}, }
modifier = Modifier.fillMaxWidth()
) { LaunchedEffect(Unit) {
Text("Kirim Absensi") kotlinx.coroutines.delay(2000)
showSuccess = false
}
} }
} }
} }