From c35e388bda9e13836177c9cb9bbf9369da31b6a2 Mon Sep 17 00:00:00 2001 From: Cutiful <113351087+Syaroful@users.noreply.github.com> Date: Thu, 10 Jul 2025 18:19:36 +0700 Subject: [PATCH] feat: implement Growth Recipe Screen UI and logic --- .../presentation/recipe/GrowthRecipeScreen.kt | 242 ++++++++++++++---- 1 file changed, 187 insertions(+), 55 deletions(-) diff --git a/agrilinkvocpro/growth_recipe_feature/src/main/java/com/syaroful/agrilinkvocpro/growth_recipe_feature/presentation/recipe/GrowthRecipeScreen.kt b/agrilinkvocpro/growth_recipe_feature/src/main/java/com/syaroful/agrilinkvocpro/growth_recipe_feature/presentation/recipe/GrowthRecipeScreen.kt index a7ee3c4..cac9172 100644 --- a/agrilinkvocpro/growth_recipe_feature/src/main/java/com/syaroful/agrilinkvocpro/growth_recipe_feature/presentation/recipe/GrowthRecipeScreen.kt +++ b/agrilinkvocpro/growth_recipe_feature/src/main/java/com/syaroful/agrilinkvocpro/growth_recipe_feature/presentation/recipe/GrowthRecipeScreen.kt @@ -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).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 - ) } } } \ No newline at end of file