From bd4c5ea0f3544d0273a05d3d38c7584e86936de4 Mon Sep 17 00:00:00 2001 From: Cutiful <113351087+Syaroful@users.noreply.github.com> Date: Thu, 12 Jun 2025 11:37:47 +0700 Subject: [PATCH] feat: implement detail screen UI --- .../core/components/DefaultErrorComponent.kt | 57 +++++++ .../ui/screen/detail/DataSensorBar.kt | 103 ++++++++++++ .../ui/screen/detail/DetailScreen.kt | 155 ++++++++++++++++++ .../ui/screen/detail/DynamicBottomSheet.kt | 79 +++++++++ 4 files changed, 394 insertions(+) create mode 100644 agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/DefaultErrorComponent.kt create mode 100644 agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DataSensorBar.kt create mode 100644 agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DetailScreen.kt create mode 100644 agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DynamicBottomSheet.kt diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/DefaultErrorComponent.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/DefaultErrorComponent.kt new file mode 100644 index 0000000..cec95da --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/DefaultErrorComponent.kt @@ -0,0 +1,57 @@ +package com.syaroful.agrilinkvocpro.core.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.syaroful.agrilinkvocpro.R + + +@Composable +fun DefaultErrorComponent( + modifier: Modifier = Modifier, + label: String, + message: String, + painter: Painter = painterResource(id = R.drawable.mascot_depressed), +) { + Column( + modifier = modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + + ) { + Image( + painter = painter, + contentDescription = "Error Image", + modifier= Modifier.size(140.dp) + ) + Spacer(modifier = Modifier.padding(vertical = 8.dp)) + Text(text = label, style = textTheme.titleMedium) + Spacer(modifier = Modifier.padding(vertical = 4.dp)) + Text(modifier = Modifier.width(200.dp), text = message, style = MaterialTheme.typography.bodySmall, textAlign = TextAlign.Center) + } +} + +@Preview +@Composable +fun ErrorPreview() { + DefaultErrorComponent( + label = "Terjadi kesalahan", + message = "Waktu koneksi habis, coba beberapa sat lagi" + + ) +} \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DataSensorBar.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DataSensorBar.kt new file mode 100644 index 0000000..eba0eb0 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DataSensorBar.kt @@ -0,0 +1,103 @@ +package com.syaroful.agrilinkvocpro.ui.screen.detail + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.syaroful.agrilinkvocpro.R +import com.syaroful.agrilinkvocpro.ui.theme.MainGreen + +@Composable +fun DataSensorBar( + label: String, + percentage: Float, + painter: Painter, + value: Number = 0 +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .background( + color = MaterialTheme.colorScheme.surfaceContainer, + shape = RoundedCornerShape(8.dp) + ) + .fillMaxWidth() + .padding(16.dp) + ) { + Image( + painter = painter, + contentDescription = "Sensor Icon" + ) + Spacer(modifier = Modifier.width(16.dp)) + Column( + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(label, style = MaterialTheme.typography.titleMedium) + Text("$value", style = MaterialTheme.typography.titleMedium) + } + Spacer(modifier = Modifier.height(8.dp)) + CustomLinearProgressIndicator( + progress = percentage, + modifier = Modifier.fillMaxWidth() + ) + } + } +} + +@Composable +private fun CustomLinearProgressIndicator( + progress: Float, + modifier: Modifier = Modifier, + color: Color = MainGreen, + backgroundColor: Color = MainGreen.copy(alpha = 0.1f) +) { + Box( + modifier = modifier + .height(6.dp) + .clip(RoundedCornerShape(8.dp)) // pastikan tidak ada rounded corner + .background(backgroundColor) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(progress) + .background(color) + ) + } +} + + +@Preview +@Composable +fun DataSensorBarPreview() { + DataSensorBar( + label = "Nitrogen", + percentage = 0.5f, + painter = painterResource(id = R.drawable.npk), + + ) +} + diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DetailScreen.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DetailScreen.kt new file mode 100644 index 0000000..3fccdd4 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DetailScreen.kt @@ -0,0 +1,155 @@ +package com.syaroful.agrilinkvocpro.ui.screen.detail + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.syaroful.agrilinkvocpro.R +import java.text.SimpleDateFormat +import java.util.Date +import kotlin.random.Random + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DetailScreen( + modifier: Modifier = Modifier +) { + val sdf = SimpleDateFormat("dd MMMM yyyy", java.util.Locale.getDefault()) + val currentDate = sdf.format(Date()) + val options = listOf( + "Nitrogen", + "Pospor", + "Kalium", + "Suhu Tanah", + "PH Tanah", + "Kelembapan", + "Konduktivitas" + ) + Scaffold( + topBar = { + TopAppBar( + title = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + Text( + "Detail Grafik", + style = MaterialTheme.typography.titleMedium + ) + } + + }, + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .padding(top = 16.dp) + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) + ) { + DynamicBottomSheet( + options = options + ) { + + } + Spacer(modifier = Modifier.height(16.dp)) + LineChart( + hours = listOf(0, 1, 2, 3, 5, 6, 8, 10), + values = listOf(20.0, 22.0, 25.0, 23.0, 28.0, 30.0, 20.0, 22.0), + modifier = Modifier + .fillMaxWidth() + .height(180.dp) + .align(CenterHorizontally) + ) + + Text( + "Grafik ini adalah grafik per hari ini tanggal $currentDate", + style = MaterialTheme.typography.bodySmall, + textAlign = TextAlign.Center, + fontStyle = FontStyle.Italic, + modifier = Modifier + .fillMaxWidth() + .alpha(0.5f) + .padding(vertical = 8.dp) + ) + Column( + verticalArrangement = Arrangement.spacedBy( + 8.dp, + Alignment.Top + ) + ) { + DataSensorBar( + label = "Nitrogen", + value = Random.nextInt(0, 101), + painter = painterResource(id = R.drawable.npk), + percentage = 0.5f + ) + DataSensorBar( + label = "Pospor", + value = Random.nextInt(0, 101), + painter = painterResource(id = R.drawable.npk), + percentage = 0.43f + ) + DataSensorBar( + label = "Kalium", + value = Random.nextInt(0, 101), + painter = painterResource(id = R.drawable.npk), + percentage = 0.3f + ) + DataSensorBar( + label = "Kelembaban", + value = Random.nextInt(0, 101), + painter = painterResource(id = R.drawable.soil_humidity), + percentage = 0.2f + ) + DataSensorBar( + label = "Suhu Tanah", + value = Random.nextInt(0, 101), + painter = painterResource(id = R.drawable.soil_temperature), + percentage = 0.6f + ) + DataSensorBar( + label = "PH Tanah", + value = Random.nextInt(0, 101), + painter = painterResource(id = R.drawable.meters), + percentage = 0.8f + ) + DataSensorBar( + label = "Konduktivitas", + value = Random.nextInt(0, 101), + painter = painterResource(id = R.drawable.electricity), + percentage = 0.6f + ) + } + } + } +} + +@Preview +@Composable +fun PreviewDetailScreen() { + DetailScreen() +} \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DynamicBottomSheet.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DynamicBottomSheet.kt new file mode 100644 index 0000000..808d7ca --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/detail/DynamicBottomSheet.kt @@ -0,0 +1,79 @@ +package com.syaroful.agrilinkvocpro.ui.screen.detail + +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DynamicBottomSheet( + modifier: Modifier = Modifier, + options: List = listOf("Pilih Item"), + onValueSelected: (String) -> Unit, +) { + val (showSheet, setShowSheet) = remember { mutableStateOf(false) } + val (selectedOption, setSelectedOption) = remember { mutableStateOf(options[0]) } + val sheetState = rememberModalBottomSheetState() + + // Trigger UI to open the sheet + Row( + modifier = modifier + .clickable { setShowSheet(true) } + .border( + width = 1.dp, + shape = RoundedCornerShape(8.dp), + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.3f) + ) + .padding(8.dp) + .fillMaxWidth(0.5f), + + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = selectedOption) + Spacer(modifier = Modifier.weight(1f)) + Icon( + imageVector = Icons.Filled.ArrowDropDown, + contentDescription = "Dropdown Arrow" + ) + } + + if (showSheet) { + ModalBottomSheet( + onDismissRequest = { setShowSheet(false) }, + sheetState = sheetState + ) { + options.forEach { option -> + Text( + text = option, + modifier = Modifier + .fillMaxWidth() + .clickable { + onValueSelected(option) + setSelectedOption(option) + setShowSheet(false) + } + .padding(16.dp) + ) + } + } + } +}