feat: implement Growth Recipe Screen UI and logic

This commit is contained in:
Cutiful 2025-07-10 18:19:36 +07:00
parent d9ecb961aa
commit c35e388bda

View File

@ -1,15 +1,19 @@
package com.syaroful.agrilinkvocpro.growth_recipe_feature.presentation.recipe
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@ -17,77 +21,205 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
import com.syaroful.agrilinkvocpro.core.placeholder.shimmerEffect
import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.growth_recipe_feature.R
import com.syaroful.agrilinkvocpro.presentation.screen.detail.component.LineChart
import com.syaroful.agrilinkvocpro.growth_recipe_feature.core.component.CustomButton
import com.syaroful.agrilinkvocpro.growth_recipe_feature.core.component.GrowthLineChart
import com.syaroful.agrilinkvocpro.growth_recipe_feature.core.component.GrowthRecipeFeatureBanner
import com.syaroful.agrilinkvocpro.growth_recipe_feature.core.utils.getValuesForSensorNpk
import com.syaroful.agrilinkvocpro.growth_recipe_feature.data.model.NpkGraphicDayResponse
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GrowthRecipeScreen() {
fun GrowthRecipeScreen(
navController: NavController,
viewModel: GrowthRecipeViewModel = koinViewModel()
) {
val commodityOptions = listOf("Labu Kabocha", "Melon", "Strawberry")
val sensorOptions = listOf("Nitrogen", "Pospor", "Kalium")
Scaffold(
topBar = {
TopAppBar(
title = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text("Optimal Growth Recipe", style = MaterialTheme.typography.titleMedium)
}
},
navigationIcon = {
IconButton(onClick = { }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
},
)
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, top = 16.dp)
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
DynamicBottomSheet(
options = commodityOptions,
) {
val selectedSensor = remember { mutableStateOf("Nitrogen") }
val graphicState by viewModel.getGraphicState.collectAsState()
val isRefreshing = remember { mutableStateOf(false) }
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.clip(RoundedCornerShape(8.dp))
) {
Image(
painter = painterResource(id = R.drawable.kabocha_pumpkin),
contentDescription = "commodity image",
contentScale = ContentScale.Crop
LaunchedEffect(Unit) {
viewModel.getGraphicData("npk1")
}
PullToRefreshBox(
isRefreshing = isRefreshing.value,
onRefresh = {
isRefreshing.value = true
viewModel.getGraphicData("npk1")
},
) {
Scaffold(
topBar = {
TopAppBar(
title = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
) {
Text(
"Optimal Growth Recipe",
style = MaterialTheme.typography.titleMedium
)
}
},
)
}
DynamicBottomSheet(
options = sensorOptions,
) { innerPadding ->
Column(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, top = 16.dp)
.padding(innerPadding)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
GrowthRecipeFeatureBanner()
CustomButton(
onClick = {
navController.navigate("cluster")
},
title = "✨ Lihat saran perawatan"
)
Text(
"🪴 Grafik nutrisi dalam 10 hari",
style = MaterialTheme.typography.titleMedium
)
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
modifier = Modifier
.background(
shape = RoundedCornerShape(4.dp),
color = MainGreen.copy(alpha = 0.1f)
)
.size(40.dp),
onClick = { },
) {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.plant_icon),
tint = MainGreen,
contentDescription = "Choose bet"
)
}
Spacer(modifier = Modifier.width(8.dp))
DynamicBottomSheet(
options = sensorOptions,
) { selected ->
selectedSensor.value = selected
}
}
when (graphicState) {
is ResultState.Loading -> {
isRefreshing.value = true
Row {
Box(
modifier = Modifier
.size(50.dp)
.shimmerEffect()
)
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.height(50.dp)
.width(150.dp)
.shimmerEffect()
)
}
Box(
modifier = Modifier
.height(200.dp)
.fillMaxWidth()
.shimmerEffect()
)
}
is ResultState.Success -> {
isRefreshing.value = false
val dataList =
(graphicState as ResultState.Success<NpkGraphicDayResponse>).data?.data?.get(
"npk1"
).orEmpty()
val days = dataList.mapNotNull { it.day?.toInt() }
val values = getValuesForSensorNpk(selectedSensor.value, dataList)
val avgValues = values.average()
GrowthLineChart(
modifier = Modifier
.fillMaxWidth()
.height(180.dp),
dates = days,
values = values,
horizontalValue = avgValues
)
}
is ResultState.Error -> {
isRefreshing.value = false
DefaultErrorComponent(
label = "Oops!",
message = (graphicState as ResultState.Error).message
)
}
ResultState.Idle -> {
}
}
Text(
"🌱 Saran Nutrisi Optimal",
style = MaterialTheme.typography.titleMedium
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(horizontalAlignment = Alignment.Start) {
Text("Nutrisi")
Text("N")
Text("P")
Text("K")
}
Column(horizontalAlignment = Alignment.Start) {
Text("Vegetatif")
Text("30 ppm")
Text("40 ppm")
Text("50 ppm")
}
Column(horizontalAlignment = Alignment.Start) {
Text("Generatif")
Text("50 ppm")
Text("60 ppm")
Text("70 ppm")
}
}
}
LineChart(
modifier = Modifier.fillMaxWidth().height(140.dp),
hours = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
values = listOf(5.0, 6.0, 4.0, 7.0, 8.9, 5.0, 6.0, 4.9, 8.0, 7.0, 9.0),
horizontalValue = 6.0
)
}
}
}