EAS-202310715060-MuhammadYusronAmrullah.update

This commit is contained in:
202310715060 MUHAMMAD YUSRON AMRULLAH 2026-01-14 23:10:20 +07:00
parent 8326d50979
commit 2b93266ee1
3 changed files with 279 additions and 243 deletions

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

@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

View File

@ -15,18 +15,40 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInVertically
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.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import id.ac.ubharajaya.sistemakademik.ui.theme.SistemAkademikTheme
import kotlinx.coroutines.delay
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.net.HttpURLConnection
@ -37,13 +59,9 @@ import kotlin.concurrent.thread
const val KAMPUS_LAT = -6.222967558410948
const val KAMPUS_LON = 107.00931291609834
const val MAX_RADIUS = 50
// meter
const val MAX_RADIUS = 50 // meter
fun hitungJarak(
lat1: Double, lon1: Double,
lat2: Double, lon2: Double
): Float {
fun hitungJarak(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Float {
val hasil = FloatArray(1)
Location.distanceBetween(lat1, lon1, lat2, lon2, hasil)
return hasil[0]
@ -58,62 +76,39 @@ fun bitmapToBase64(bitmap: Bitmap): String {
}
fun kirimKeN8n(
context: ComponentActivity,
npm: String,
nama: String,
mataKuliah: String,
latitude: Double,
longitude: Double,
foto: Bitmap,
status: String
context: ComponentActivity, npm: String, nama: String, mataKuliah: String,
latitude: Double, longitude: Double, foto: Bitmap, status: String,
onFinished: () -> Unit
) {
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("npm", npm)
put("nama", nama)
put("mata_kuliah", mataKuliah)
put("latitude", latitude)
put("longitude", longitude)
put("timestamp", System.currentTimeMillis())
put("status", status)
put("npm", npm); put("nama", nama); put("mata_kuliah", mataKuliah)
put("latitude", latitude); put("longitude", longitude)
put("timestamp", System.currentTimeMillis()); put("status", status)
put("foto_base64", bitmapToBase64(foto))
}
conn.outputStream.use {
it.write(json.toString().toByteArray())
}
conn.outputStream.use { it.write(json.toString().toByteArray()) }
val responseCode = conn.responseCode
context.runOnUiThread {
Toast.makeText(
context,
if (responseCode == 200)
"Absensi $status"
else
"Server menolak absensi",
if (responseCode == 200) "Absensi $status berhasil dikirim" else "Server menolak absensi",
Toast.LENGTH_SHORT
).show()
}
conn.disconnect()
} catch (_: Exception) {
context.runOnUiThread {
Toast.makeText(
context,
"Gagal kirim ke server",
Toast.LENGTH_SHORT
).show()
Toast.makeText(context, "Gagal kirim ke server", Toast.LENGTH_SHORT).show()
}
} finally {
context.runOnUiThread(onFinished)
}
}
}
@ -121,247 +116,234 @@ fun kirimKeN8n(
/* ================= 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(activity = this)
}
}
}
}
/* ================= UI ================= */
/* ================= UI UTAMA ================= */
@Composable
fun AbsensiScreen(
modifier: Modifier = Modifier,
activity: ComponentActivity
) {
fun AbsensiScreen(activity: ComponentActivity) {
val context = LocalContext.current
// States
var npm by remember { mutableStateOf("") }
var nama by remember { mutableStateOf("") }
var mataKuliah by remember { mutableStateOf("") }
var lokasi by remember { mutableStateOf("Koordinat: -") }
var lokasi by remember { mutableStateOf<String?>(null) }
var latitude by remember { mutableStateOf<Double?>(null) }
var longitude by remember { mutableStateOf<Double?>(null) }
var foto by remember { mutableStateOf<Bitmap?>(null) }
var isUploading by remember { mutableStateOf(false) }
var isFetchingLocation by remember { mutableStateOf(false) }
val fusedLocationClient =
LocationServices.getFusedLocationProviderClient(context)
val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) }
/* ===== Permission Lokasi ===== */
val locationPermissionLauncher =
rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) {
if (
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
fusedLocationClient.lastLocation
.addOnSuccessListener { location ->
fun requestLocationUpdate() {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
lokasi = "Izin lokasi belum diberikan."
return
}
isFetchingLocation = true
lokasi = "Mencari lokasi..."
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
.addOnSuccessListener { location: Location? ->
if (location != null) {
latitude = location.latitude
longitude = location.longitude
lokasi =
"Lat: ${location.latitude}\nLon: ${location.longitude}"
lokasi = "Lat: ${String.format("%.6f", location.latitude)}\nLon: ${String.format("%.6f", location.longitude)}"
} else {
lokasi = "Lokasi tidak tersedia"
lokasi = "Gagal mendapatkan lokasi. Coba lagi."
}
isFetchingLocation = false
}
}
} else {
Toast.makeText(
context,
"Izin lokasi ditolak",
Toast.LENGTH_SHORT
).show()
.addOnFailureListener {
lokasi = "Gagal mendapatkan lokasi. Coba lagi."
isFetchingLocation = false
}
}
/* ===== Kamera ===== */
val locationPermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) requestLocationUpdate() else {
Toast.makeText(context, "Izin lokasi ditolak", Toast.LENGTH_SHORT).show()
lokasi = "Izin lokasi ditolak."
}
}
val cameraLauncher =
rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val bitmap = result.data?.extras?.get("data") as? Bitmap
if (bitmap != null) {
foto = bitmap
Toast.makeText(
context,
"Foto berhasil diambil",
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
context,
"Gagal mengambil foto",
Toast.LENGTH_SHORT
).show()
}
}
}
val cameraPermissionLauncher =
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()
if (bitmap != null) foto = bitmap else Toast.makeText(context, "Gagal mengambil foto", Toast.LENGTH_SHORT).show()
}
}
LaunchedEffect(Unit) {
locationPermissionLauncher.launch(
Manifest.permission.ACCESS_FINE_LOCATION
val cameraPermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) cameraLauncher.launch(Intent(MediaStore.ACTION_IMAGE_CAPTURE))
else Toast.makeText(context, "Izin kamera ditolak", Toast.LENGTH_SHORT).show()
}
LaunchedEffect(Unit) { locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) }
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
val gradient = Brush.linearGradient(
colors = listOf(MaterialTheme.colorScheme.primary.copy(alpha = 0.18f), Color.Transparent),
start = Offset(0f, 0f),
end = Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)
)
}
/* ===== UI ===== */
Column(
modifier = modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center
LazyColumn(
modifier = Modifier.fillMaxSize().background(gradient).padding(WindowInsets.systemBars.asPaddingValues()),
horizontalAlignment = Alignment.CenterHorizontally,
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp)
) {
item { AnimatedHeader(delay = 0) }
item { Spacer(modifier = Modifier.height(24.dp)) }
Text(
text = "Absensi Akademik",
style = MaterialTheme.typography.titleLarge
item { AnimatedInputCard(npm, nama, mataKuliah, { npm = it }, { nama = it }, { mataKuliah = it }) }
item { Spacer(modifier = Modifier.height(24.dp)) }
item {
AnimatedActionSection(isFetchingLocation, { requestLocationUpdate() }, { cameraPermissionLauncher.launch(Manifest.permission.CAMERA) }, lokasi)
}
item { Spacer(modifier = Modifier.height(24.dp)) }
item { AnimatedPhotoPreview(foto) }
item { Spacer(modifier = Modifier.height(24.dp)) }
item {
val canSubmit = npm.isNotEmpty() && nama.isNotEmpty() && mataKuliah.isNotEmpty() && latitude != null && longitude != null && foto != null
AnimatedSubmitButton(
enabled = canSubmit && !isUploading,
isUploading = isUploading,
onClick = {
isUploading = true
val jarak = hitungJarak(latitude!!, longitude!!, KAMPUS_LAT, KAMPUS_LON)
val statusAbsen = if (jarak <= MAX_RADIUS) "HADIR" else "DITOLAK"
if (statusAbsen == "HADIR") {
kirimKeN8n(activity, npm, nama, mataKuliah, latitude!!, longitude!!, foto!!, statusAbsen) { isUploading = false }
} else {
val jarakKm = String.format("%.2f", jarak / 1000)
Toast.makeText(context, "Absensi DITOLAK: Anda berada ${jarakKm}km dari kampus.", Toast.LENGTH_LONG).show()
isUploading = false
}
}
)
}
}
}
}
/* ================= KOMPONEN UI DENGAN ANIMASI ================= */
@Composable
fun AnimatedHeader(delay: Long = 0) {
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { delay(delay); visible = true }
AnimatedVisibility(visible = visible, enter = fadeIn(spring(stiffness = Spring.StiffnessLow)) + slideInVertically(spring(Spring.DampingRatioMediumBouncy, Spring.StiffnessLow), initialOffsetY = { -it })) {
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) {
Text("E-ABSENSI", style = MaterialTheme.typography.displaySmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary)
Text("Universitas Bhayangkara Jakarta Raya", style = MaterialTheme.typography.titleMedium, color = Color.Gray)
}
}
}
@Composable
fun AnimatedInputCard(
npm: String, nama: String, mataKuliah: String,
onNpmChange: (String) -> Unit, onNamaChange: (String) -> Unit, onMataKuliahChange: (String) -> Unit
) {
var cardVisible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { delay(200); cardVisible = true }
AnimatedVisibility(visible = cardVisible, enter = fadeIn(spring(stiffness = Spring.StiffnessLow)) + slideInVertically(spring(Spring.DampingRatioMediumBouncy, Spring.StiffnessLow), initialOffsetY = { it / 2 })) {
Card(modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp), elevation = CardDefaults.cardElevation(4.dp)) {
Column(modifier = Modifier.padding(20.dp)) {
Text("Data Mahasiswa", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = npm,
onValueChange = { npm = it },
label = { Text("NPM") },
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = nama,
onValueChange = { nama = it },
label = { Text("Nama") },
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = mataKuliah,
onValueChange = { mataKuliah = it },
label = { Text("Mata Kuliah") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(12.dp))
Text(text = lokasi)
Spacer(modifier = Modifier.height(12.dp))
Button(
onClick = {
cameraPermissionLauncher.launch(
Manifest.permission.CAMERA
)
},
modifier = Modifier.fillMaxWidth()
) {
Text("Ambil Foto")
OutlinedTextField(value = npm, onValueChange = onNpmChange, label = { Text("NPM") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), leadingIcon = { Icon(Icons.Default.Person, "NPM") })
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(value = nama, onValueChange = onNamaChange, label = { Text("Nama Lengkap") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), leadingIcon = { Icon(Icons.Default.Badge, "Nama") })
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(value = mataKuliah, onValueChange = onMataKuliahChange, label = { Text("Mata Kuliah") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), leadingIcon = { Icon(Icons.Default.Book, "Mata Kuliah") })
}
}
}
}
/* ===== PREVIEW FOTO ===== */
@Composable
fun AnimatedActionSection(isFetchingLocation: Boolean, onRefresh: () -> Unit, onTakePhoto: () -> Unit, lokasi: String?) {
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { delay(400); visible = true }
if (foto != null) {
Spacer(modifier = Modifier.height(12.dp))
Image(
bitmap = foto!!.asImageBitmap(),
contentDescription = "Preview Foto Absensi",
modifier = Modifier
.fillMaxWidth()
.height(220.dp),
contentScale = ContentScale.Crop
)
AnimatedVisibility(visible = visible, enter = fadeIn(spring(stiffness = Spring.StiffnessLow)) + slideInVertically(spring(Spring.DampingRatioMediumBouncy, Spring.StiffnessLow), initialOffsetY = { it / 2 })) {
Column {
StatusRowWithRefresh(icon = Icons.Default.LocationOn, title = "Lokasi GPS", status = lokasi ?: "Meminta izin...", isRefreshing = isFetchingLocation, onRefresh = onRefresh)
Spacer(modifier = Modifier.height(16.dp))
ActionButton(text = "Ambil Foto Kehadiran", icon = Icons.Default.CameraAlt, onClick = onTakePhoto)
}
}
}
Spacer(modifier = Modifier.height(12.dp))
@Composable
fun AnimatedPhotoPreview(foto: Bitmap?) {
AnimatedVisibility(visible = foto != null, enter = fadeIn(spring(stiffness = Spring.StiffnessMedium)) + slideInVertically(spring(Spring.DampingRatioMediumBouncy, Spring.StiffnessMedium), initialOffsetY = { it / 2 })) {
Card(shape = RoundedCornerShape(16.dp), elevation = CardDefaults.cardElevation(4.dp)) {
Box(contentAlignment = Alignment.Center) {
Image(bitmap = foto!!.asImageBitmap(), "Preview Foto Absensi", modifier = Modifier.fillMaxWidth().height(250.dp).clip(RoundedCornerShape(16.dp)), contentScale = ContentScale.Crop)
Icon(Icons.Default.CheckCircle, "Foto Diambil", tint = Color.White.copy(alpha = 0.8f), modifier = Modifier.size(60.dp))
}
}
}
}
Button(
onClick = {
if (
npm.isNotEmpty() &&
nama.isNotEmpty() &&
mataKuliah.isNotEmpty() &&
latitude != null &&
longitude != null &&
foto != null
) {
@Composable
fun AnimatedSubmitButton(enabled: Boolean, isUploading: Boolean, onClick: () -> Unit) {
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { delay(600); visible = true }
val jarak = hitungJarak(
latitude!!,
longitude!!,
KAMPUS_LAT,
KAMPUS_LON
)
val status =
if (jarak <= MAX_RADIUS) "HADIR" else "DITOLAK"
if (status == "HADIR") {
kirimKeN8n(
activity,
npm,
nama,
mataKuliah,
latitude!!,
longitude!!,
foto!!,
status
)
AnimatedVisibility(visible = visible, enter = fadeIn(spring(stiffness = Spring.StiffnessLow)) + slideInVertically(spring(Spring.DampingRatioMediumBouncy, Spring.StiffnessLow), initialOffsetY = { it / 2 })) {
Button(onClick = onClick, enabled = enabled, modifier = Modifier.fillMaxWidth().height(56.dp)) {
if (isUploading) {
CircularProgressIndicator(color = Color.White, modifier = Modifier.size(24.dp))
} else {
Toast.makeText(
context,
"Absensi ditolak (di luar area)",
Toast.LENGTH_LONG
).show()
Text("KIRIM ABSENSI", fontWeight = FontWeight.Bold)
}
}
}
}
/* ================= KOMPONEN UI STANDAR ================= */
@Composable
fun StatusRowWithRefresh(icon: ImageVector, title: String, status: String, isRefreshing: Boolean, onRefresh: () -> Unit) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Icon(icon, contentDescription = title, tint = MaterialTheme.colorScheme.primary)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(title, fontWeight = FontWeight.Bold, fontSize = 16.sp)
Text(status, style = MaterialTheme.typography.bodySmall, color = Color.Gray)
}
if (isRefreshing) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
} else {
IconButton(onClick = onRefresh) {
Icon(Icons.Default.Refresh, "Cari Ulang Lokasi")
}
}
}
}
} else {
Toast.makeText(
context,
"Data absensi belum lengkap",
Toast.LENGTH_SHORT
).show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Kirim Absensi")
}
@Composable
fun ActionButton(text: String, icon: ImageVector, onClick: () -> Unit) {
FilledTonalButton(onClick = onClick, modifier = Modifier.fillMaxWidth().height(56.dp), shape = RoundedCornerShape(16.dp)) {
Icon(icon, contentDescription = null)
Spacer(modifier = Modifier.width(12.dp))
Text(text, fontWeight = FontWeight.Bold)
}
}