feat: enhance UI/UX of plant disease detection feature

This commit is contained in:
Cutiful 2025-07-05 11:25:42 +07:00
parent 0dae042e54
commit 90df005a3f
4 changed files with 173 additions and 82 deletions

View File

@ -20,7 +20,7 @@ fun NavGraph(navController: NavHostController) {
val historyViewModel: HistoryViewModel = koinViewModel() val historyViewModel: HistoryViewModel = koinViewModel()
NavHost(navController, startDestination = "camera_screen") { NavHost(navController, startDestination = "history") {
composable("camera_screen") { composable("camera_screen") {
CameraScreen(navController, cameraViewModel) CameraScreen(navController, cameraViewModel)
} }

View File

@ -5,8 +5,10 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@ -68,6 +70,7 @@ fun DetailScreen(
title = { title = {
Text( Text(
"Deteksi Penyakit Tanaman", "Deteksi Penyakit Tanaman",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
@ -82,14 +85,15 @@ fun DetailScreen(
) { innerPadding -> ) { innerPadding ->
Box( Box(
modifier = Modifier modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(innerPadding)
.padding(24.dp), .padding(horizontal = 16.dp),
contentAlignment = Alignment.TopCenter contentAlignment = Alignment.TopCenter
) { ) {
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy( verticalArrangement = Arrangement.spacedBy(
10.dp, 10.dp,
Alignment.Top Alignment.Top
@ -215,12 +219,7 @@ fun DetailScreen(
} }
} }
} }
AppButton( Spacer(modifier = Modifier.height(56.dp))
label = if (saveLoading) "Menyimpan..." else "Simpan Hasil Deteksi",
isEnable = !saveLoading
) {
diagnosisViewModel.saveResultToLocal(data, bitmap!!)
}
} else { } else {
Text("Gagal parsing response") Text("Gagal parsing response")
} }
@ -228,6 +227,18 @@ fun DetailScreen(
} }
} }
} }
diagnosisState?.let { result ->
if (!result.isFailure && result.isSuccess && result.getOrNull()?.diagnosis != null) {
AppButton(
modifier = Modifier
.align(Alignment.BottomCenter),
label = if (saveLoading) "Menyimpan..." else "Simpan Hasil Deteksi",
isEnable = !saveLoading
) {
diagnosisViewModel.saveResultToLocal(result.getOrNull()!!, bitmap!!)
}
}
}
} }
} }
} }

View File

@ -164,7 +164,7 @@ fun DetailHistoryScreen(
) { ) {
Text( Text(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = plantDiagnosis?.treatment ?: "-", text = plantDiagnosis?.treatment?.replace("., ", ".\n\n") ?: "-",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
} }
@ -173,7 +173,7 @@ fun DetailHistoryScreen(
) { ) {
Text( Text(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = plantDiagnosis?.prevention ?: "-", text = plantDiagnosis?.prevention?.replace("., ", ".\n\n") ?: "-",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
} }

View File

@ -1,5 +1,6 @@
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -16,11 +17,9 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material3.Button
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -28,17 +27,23 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toBitmap import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toBitmap
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toFormattedDate import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toFormattedDate
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity.PlantDiagnosisEntity
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -47,50 +52,85 @@ fun HistoryScreen(
viewModel: HistoryViewModel viewModel: HistoryViewModel
) { ) {
val diagnoses by viewModel.diagnoses.collectAsState() val diagnoses by viewModel.diagnoses.collectAsState()
val fillAndPadding = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
Text( Text(
"Riwayat Deteksi", "Deteksi Penyakit Tanaman",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
}, },
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
}
}
) )
} }
) { innerPadding -> ) { innerPadding ->
val baseModifier = fillAndPadding.then(Modifier.padding(innerPadding))
when { if (diagnoses.isEmpty()) {
Column(
(diagnoses.isEmpty()) -> DefaultErrorComponent( modifier = baseModifier,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
DiseaseDetectionBanner()
StartDiagnosisButton { navController.navigate("camera_screen") }
DefaultErrorComponent(
label = "Waduh!", label = "Waduh!",
message = "Ga ada data sama sekali, coba tambahkan", message = "Ga ada data sama sekali, coba tambahkan",
painter = painterResource(id = R.drawable.mascot_surprised) painter = painterResource(id = R.drawable.mascot_surprised)
) )
}
else -> LazyColumn( } else {
modifier = Modifier LazyColumn(
.padding(innerPadding) modifier = baseModifier,
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
item { DiseaseDetectionBanner() }
item { StartDiagnosisButton { navController.navigate("camera_screen") } }
item {
Text(
"Riwayat Deteksi",
style = MaterialTheme.typography.titleMedium
)
}
items(diagnoses) { diagnosis -> items(diagnoses) { diagnosis ->
DiagnosisListItem(diagnosis, navController)
}
}
}
}
}
@Composable
private fun StartDiagnosisButton(onClick: () -> Unit) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onClick,
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = MainGreen
),
shape = RoundedCornerShape(8.dp),
border = BorderStroke(color = MainGreen, width = 1.dp)
) {
Text("Mulai diagnosis")
}
}
@Composable
private fun DiagnosisListItem(
diagnosis: PlantDiagnosisEntity,
navController: NavHostController
) {
Box( Box(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
navController.currentBackStackEntry?.savedStateHandle?.set( navController.currentBackStackEntry?.savedStateHandle?.set("plantData", diagnosis)
"plantData",
diagnosis
)
navController.navigate("detail-history/${diagnosis.id}") navController.navigate("detail-history/${diagnosis.id}")
} }
.fillMaxWidth() .fillMaxWidth()
@ -99,11 +139,7 @@ fun HistoryScreen(
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(8.dp)
) )
) { ) {
Row( Row(modifier = Modifier.padding(12.dp).fillMaxWidth()) {
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(80.dp) .size(80.dp)
@ -115,22 +151,66 @@ fun HistoryScreen(
) )
} }
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Column { Column(
modifier = Modifier.height(80.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
Text( Text(
"Diagnosis: ${diagnosis.diagnosis}", "Diagnosis: ${diagnosis.diagnosis}",
style = MaterialTheme.typography.labelLarge style = MaterialTheme.typography.labelLarge
) )
Spacer(modifier = Modifier.height(12.dp)) Text(
diagnosis.description,
style = MaterialTheme.typography.bodySmall,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Row {
Image(
modifier = Modifier.size(14.dp),
painter = painterResource(R.drawable.ic_calendar),
contentDescription = "Date Icon",
colorFilter = ColorFilter.tint(Color.Gray)
)
Spacer(modifier = Modifier.width(4.dp))
Text( Text(
diagnosis.timestamp.toFormattedDate(), diagnosis.timestamp.toFormattedDate(),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.End
) )
} }
} }
} }
} }
} }
@Composable
private fun DiseaseDetectionBanner() {
Box(
modifier = Modifier
.fillMaxWidth()
.background(color = MainGreen, shape = RoundedCornerShape(8.dp))
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.fillMaxWidth(0.6f)) {
Text(
"Lindungi Tanamanmu dari Hama dan Penyakit",
style = MaterialTheme.typography.titleMedium.copy(color = Color.White)
)
Text(
"Deteksi, lihat hasil dan lakukan perawatan",
style = MaterialTheme.typography.bodySmall.copy(Color.White.copy(alpha = 0.5f))
)
}
Image(
modifier = Modifier.fillMaxWidth(0.8f),
painter = painterResource(id = R.drawable.plant_in_pot),
contentDescription = "Plant Image",
)
} }
} }
} }