feat: implement detail screen UI

This commit is contained in:
Cutiful 2025-06-12 11:37:47 +07:00
parent a3d819417e
commit bd4c5ea0f3
4 changed files with 394 additions and 0 deletions

View File

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

View File

@ -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),
)
}

View File

@ -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()
}

View File

@ -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<String> = 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)
)
}
}
}
}