feat: add cluster screen for displaying recommendation
This commit introduces the `ClusterScreen.kt` composable, which displays plant care recommendations. The screen includes: - A top app bar with the title "Rekomendasi Perawatan". - Pull-to-refresh functionality to update recommendations. - Display of an image based on the cluster number (1, 2, or 3). - "Diperbarui Pada" (Updated On) timestamp, formatted to Indonesian locale. - A "Rekomendasi" section showing general advice and specific care instructions. - An image of a plant. - `ListItemClustering` to display humidity, temperature, and pH values. - Loading and error states handling. A private `formatDate` utility function is included to format timestamps to "dd MMMM yyyy, HH:mm" in Indonesian.
This commit is contained in:
parent
acc732871f
commit
e274873e43
|
|
@ -0,0 +1,222 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.growth_recipe_feature.presentation.recomendation
|
||||||
|
|
||||||
|
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.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
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.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.alpha
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
|
||||||
|
import com.syaroful.agrilinkvocpro.core.utils.ResultState
|
||||||
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.R
|
||||||
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.core.component.ListItemClustering
|
||||||
|
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ClusterScreen(
|
||||||
|
navController: NavController,
|
||||||
|
viewModel: ClusterViewModel = koinViewModel()
|
||||||
|
) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val isRefreshing = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
if (state == ResultState.Idle) {
|
||||||
|
viewModel.getRecommendation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Rekomendasi Perawatan",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
PullToRefreshBox(
|
||||||
|
isRefreshing = isRefreshing.value,
|
||||||
|
onRefresh = {
|
||||||
|
isRefreshing.value = true
|
||||||
|
viewModel.getRecommendation()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(innerPadding)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
when (state) {
|
||||||
|
is ResultState.Loading -> {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color = MainGreen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ResultState.Error -> {
|
||||||
|
isRefreshing.value = false
|
||||||
|
DefaultErrorComponent(
|
||||||
|
label = "Oops!",
|
||||||
|
message = (state as ResultState.Error).message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ResultState.Success -> {
|
||||||
|
isRefreshing.value = false
|
||||||
|
val data = (state as ResultState.Success).data
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.fillMaxWidth(0.7f),
|
||||||
|
painter = painterResource(
|
||||||
|
id =
|
||||||
|
when (data?.cluster) {
|
||||||
|
1 -> R.drawable.cluster_1
|
||||||
|
2 -> R.drawable.cluster_2
|
||||||
|
3 -> R.drawable.cluster_3
|
||||||
|
else -> R.drawable.cluster_2
|
||||||
|
}
|
||||||
|
),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
contentDescription = "Recommendation action"
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Diperbarui Pada",
|
||||||
|
modifier = Modifier
|
||||||
|
.alpha(0.6f),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
formatDate(data?.createdAt ?: "-"),
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(fontStyle = FontStyle.Italic),
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(0.7f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Rekomendasi:",
|
||||||
|
modifier = Modifier.alpha(0.6f),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
data?.rekomendasi ?: "-",
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Rekomendasi Perawatan:",
|
||||||
|
modifier = Modifier.alpha(0.6f),
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
data?.rekomendasiPerawatan ?: "-",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
painter = painterResource(id = R.drawable.vegeatatif),
|
||||||
|
contentDescription = "plant"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListItemClustering(
|
||||||
|
humidity = data?.humidity ?: 0.0,
|
||||||
|
temp = data?.temperature ?: 0.0,
|
||||||
|
pH = data?.ph ?: 0.0
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun formatDate(time: String): String {
|
||||||
|
val inputFormatter = DateTimeFormatter.RFC_1123_DATE_TIME
|
||||||
|
val zonedDateTime = ZonedDateTime.parse(time, inputFormatter)
|
||||||
|
.withZoneSameInstant(ZoneId.of("Asia/Jakarta"))
|
||||||
|
|
||||||
|
val dateFormatter = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale("id", "ID"))
|
||||||
|
val timeFormatter = DateTimeFormatter.ofPattern("HH:mm")
|
||||||
|
|
||||||
|
val date = zonedDateTime.format(dateFormatter)
|
||||||
|
val jam = zonedDateTime.format(timeFormatter)
|
||||||
|
|
||||||
|
return "$date, $jam"
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user