add draft report
This commit is contained in:
parent
42d9ff8510
commit
5151379922
|
|
@ -0,0 +1,2 @@
|
|||
package com.syaroful.agrilinkvocpro.core.utils.extention
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.syaroful.agrilinkvocpro.data.model
|
||||
|
||||
data class DhtGraphicDataResponse(
|
||||
val `data`: Data?,
|
||||
val message: String?,
|
||||
val statusCode: Int?
|
||||
)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
|
||||
|
||||
class DatePickerComponent {
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
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.core.utils.extention.getValuesForSensor
|
||||
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse
|
||||
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
|
||||
|
||||
@Composable
|
||||
fun DetailContent(
|
||||
modifier: Modifier = Modifier,
|
||||
npkDataState: ResultState<NpkGraphicDataResponse?>,
|
||||
currentData: SensorDataResponse?,
|
||||
sensorId: String,
|
||||
isRefreshing: MutableState<Boolean>,
|
||||
currentDate: String,
|
||||
) {
|
||||
val selectedSensor = remember { mutableStateOf("Nitrogen") }
|
||||
val options = listOf(
|
||||
"Nitrogen",
|
||||
"Pospor",
|
||||
"Kalium",
|
||||
"Suhu Tanah",
|
||||
"PH Tanah",
|
||||
"Kelembapan",
|
||||
"Konduktivitas"
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
when (npkDataState) {
|
||||
is ResultState.Loading -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp)
|
||||
.shimmerEffect()
|
||||
)
|
||||
}
|
||||
|
||||
is ResultState.Error -> {
|
||||
isRefreshing.value = false
|
||||
DefaultErrorComponent(
|
||||
modifier = Modifier.padding(vertical = 20.dp),
|
||||
label = "Oops!",
|
||||
message = npkDataState.message,
|
||||
painter = painterResource(id = R.drawable.mascot_confused)
|
||||
)
|
||||
}
|
||||
|
||||
is ResultState.Success -> {
|
||||
isRefreshing.value = false
|
||||
val dataList = npkDataState.data?.data?.get(sensorId).orEmpty()
|
||||
|
||||
DynamicBottomSheet(
|
||||
options = options
|
||||
) { selected ->
|
||||
selectedSensor.value = selected
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
val hours = dataList.mapNotNull { it.hour?.toInt() }
|
||||
val values = getValuesForSensor(selectedSensor.value, dataList)
|
||||
|
||||
LineChart(
|
||||
hours = hours,
|
||||
values = values,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(180.dp)
|
||||
.align(CenterHorizontally)
|
||||
)
|
||||
}
|
||||
|
||||
ResultState.Idle -> {}
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
DetailSensorData(currentData = currentData, sensorId = sensorId)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SensorSelector(
|
||||
selectedSensor: String,
|
||||
onSensorSelected: (String) -> Unit,
|
||||
options: List<String>
|
||||
) {
|
||||
DynamicBottomSheet(
|
||||
options = options,
|
||||
) {
|
||||
onSensorSelected
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
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.core.utils.extention.getValuesForSensorNpk
|
||||
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse
|
||||
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
|
||||
|
||||
@Composable
|
||||
fun DetailNpkContent(
|
||||
modifier: Modifier = Modifier,
|
||||
npkDataState: ResultState<NpkGraphicDataResponse?>,
|
||||
currentData: SensorDataResponse?,
|
||||
sensorId: String,
|
||||
isRefreshing: MutableState<Boolean>,
|
||||
currentDate: String,
|
||||
) {
|
||||
val selectedSensor = remember { mutableStateOf("Nitrogen") }
|
||||
val options = listOf(
|
||||
"Nitrogen",
|
||||
"Pospor",
|
||||
"Kalium",
|
||||
"Suhu Tanah",
|
||||
"PH Tanah",
|
||||
"Kelembapan",
|
||||
"Konduktivitas"
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
when (npkDataState) {
|
||||
is ResultState.Loading -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(200.dp)
|
||||
.shimmerEffect()
|
||||
)
|
||||
}
|
||||
|
||||
is ResultState.Error -> {
|
||||
isRefreshing.value = false
|
||||
DefaultErrorComponent(
|
||||
modifier = Modifier.padding(vertical = 20.dp),
|
||||
label = "Oops!",
|
||||
message = npkDataState.message,
|
||||
painter = painterResource(id = R.drawable.mascot_confused)
|
||||
)
|
||||
}
|
||||
|
||||
is ResultState.Success -> {
|
||||
isRefreshing.value = false
|
||||
val dataList = npkDataState.data?.data?.get(sensorId).orEmpty()
|
||||
|
||||
DynamicBottomSheet(
|
||||
options = options
|
||||
) { selected ->
|
||||
selectedSensor.value = selected
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
val hours = dataList.mapNotNull { it.hour?.toInt() }
|
||||
val values = getValuesForSensorNpk(selectedSensor.value, dataList)
|
||||
|
||||
LineChart(
|
||||
hours = hours,
|
||||
values = values,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(180.dp)
|
||||
.align(CenterHorizontally)
|
||||
)
|
||||
}
|
||||
|
||||
ResultState.Idle -> {}
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
DetailSensorData(currentData = currentData, sensorId = sensorId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
|
||||
|
||||
|
|
@ -28,6 +28,7 @@ import androidx.compose.ui.unit.dp
|
|||
fun DynamicBottomSheet(
|
||||
modifier: Modifier = Modifier,
|
||||
options: List<String> = listOf("Pilih Item"),
|
||||
width: Float = 0.5f,
|
||||
onValueSelected: (String) -> Unit,
|
||||
) {
|
||||
val (showSheet, setShowSheet) = remember { mutableStateOf(false) }
|
||||
|
|
@ -44,7 +45,7 @@ fun DynamicBottomSheet(
|
|||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.3f)
|
||||
)
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth(0.5f),
|
||||
.fillMaxWidth(width),
|
||||
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
@ -11,9 +11,11 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.asAndroidPath
|
||||
import androidx.compose.ui.graphics.asComposePath
|
||||
|
|
@ -30,13 +32,17 @@ import kotlin.math.roundToInt
|
|||
fun LineChart(
|
||||
hours: List<Int> = emptyList(),
|
||||
values: List<Double> = emptyList(),
|
||||
horizontalValue: Double? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
graphColor: Color = MainGreen
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(8.dp))
|
||||
.padding(top = 24.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.padding(top = 32.dp)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
val spacing = 100f
|
||||
|
|
@ -62,29 +68,26 @@ fun LineChart(
|
|||
|
||||
Canvas(modifier = modifier) {
|
||||
val spacePerHour = (size.width - spacing) / values.size
|
||||
(0 until hours.size - 1 step 2).forEach { i ->
|
||||
val value = values[i]
|
||||
val hour = hours[i]
|
||||
drawContext.canvas.nativeCanvas.apply {
|
||||
drawText(
|
||||
hour.toString(),
|
||||
spacing + i * spacePerHour,
|
||||
size.height - 5,
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
|
||||
hours.forEachIndexed { i, hour ->
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
hour.toString(),
|
||||
spacing + i * spacePerHour,
|
||||
size.height - 5,
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
val priceStep = (upperValue - lowerValue) / 5f
|
||||
|
||||
val step = (upperValue - lowerValue) / 5f
|
||||
(0..4).forEach { i ->
|
||||
drawContext.canvas.nativeCanvas.apply {
|
||||
drawText(
|
||||
round(lowerValue + priceStep * i).toString(),
|
||||
30f,
|
||||
size.height - spacing - i * size.height / 5f,
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
round(lowerValue + step * i).toString(),
|
||||
30f,
|
||||
size.height - spacing - i * size.height / 5f,
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
|
||||
var lastX = 0f
|
||||
val strokePath = Path().apply {
|
||||
val height = size.height
|
||||
|
|
@ -105,6 +108,7 @@ fun LineChart(
|
|||
quadraticTo(x1, y1, lastX, (y1 + y2) / 2f)
|
||||
}
|
||||
}
|
||||
|
||||
val fillPath = android.graphics.Path(strokePath.asAndroidPath())
|
||||
.asComposePath()
|
||||
.apply {
|
||||
|
|
@ -112,6 +116,7 @@ fun LineChart(
|
|||
lineTo(spacing, size.height - spacing)
|
||||
close()
|
||||
}
|
||||
|
||||
drawPath(
|
||||
path = fillPath,
|
||||
brush = Brush.verticalGradient(
|
||||
|
|
@ -130,21 +135,32 @@ fun LineChart(
|
|||
cap = StrokeCap.Round
|
||||
)
|
||||
)
|
||||
val labelPadding = 12.dp.toPx()
|
||||
|
||||
val labelPadding = 12.dp.toPx()
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
"Jumlah",
|
||||
spacing / 2f,
|
||||
labelPadding - 80,
|
||||
labelPadding - 100,
|
||||
textPaint
|
||||
)
|
||||
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
"Jam",
|
||||
size.width - labelPadding,
|
||||
size.width,
|
||||
size.height - 4,
|
||||
textPaint
|
||||
)
|
||||
|
||||
horizontalValue?.let { value ->
|
||||
val ratio = (value - lowerValue) / (upperValue - lowerValue)
|
||||
val y = size.height - spacing - (ratio * size.height).toFloat()
|
||||
drawLine(
|
||||
color = Color.Red,
|
||||
start = Offset(spacing, y),
|
||||
end = Offset(size.width, y),
|
||||
strokeWidth = 2.dp.toPx(),
|
||||
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
agrilinkvocpro/app/src/main/res/drawable/solar_user.png
Normal file
BIN
agrilinkvocpro/app/src/main/res/drawable/solar_user.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
agrilinkvocpro/app/src/main/res/drawable/solar_user_circle.png
Normal file
BIN
agrilinkvocpro/app/src/main/res/drawable/solar_user_circle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,157 @@
|
|||
package com.syaroful.agrilinkvocpro.commodity_price_prediction_feature.presentation.prediction
|
||||
|
||||
import android.graphics.Paint
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.asAndroidPath
|
||||
import androidx.compose.ui.graphics.asComposePath
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.nativeCanvas
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
import kotlin.math.round
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun PriceChart(
|
||||
day: List<Int> = emptyList(),
|
||||
values: List<Double> = emptyList(),
|
||||
horizontalValue: Double? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
graphColor: Color = MainGreen
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.padding(top = 32.dp, start = 16.dp)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
val spacing = 100f
|
||||
val transparentGraphColor = remember {
|
||||
graphColor.copy(alpha = 0.5f)
|
||||
}
|
||||
val upperValue = remember(values) {
|
||||
(values.maxOrNull()?.plus(1))?.roundToInt() ?: 0
|
||||
}
|
||||
val lowerValue = remember(values) {
|
||||
values.minOrNull()?.toInt() ?: 0
|
||||
}
|
||||
val density = LocalDensity.current
|
||||
val isDarkMode = isSystemInDarkTheme()
|
||||
val textPaint = remember(density) {
|
||||
Paint().apply {
|
||||
color =
|
||||
if (isDarkMode) android.graphics.Color.WHITE else android.graphics.Color.BLACK
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = density.run { 12.sp.toPx() }
|
||||
}
|
||||
}
|
||||
|
||||
Canvas(modifier = modifier) {
|
||||
val spacePerDay = (size.width - spacing) / values.size
|
||||
day.forEachIndexed { i, day ->
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
"h $day",
|
||||
spacing + 40f + i * spacePerDay,
|
||||
size.height - 5,
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
|
||||
val step = (upperValue - lowerValue) / 5f
|
||||
(0..4).forEach { i ->
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
"Rp " + round(lowerValue + step * i).toInt().toString(),
|
||||
30f,
|
||||
size.height - spacing - i * size.height / 5f,
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
|
||||
var lastX = 0f
|
||||
val strokePath = Path().apply {
|
||||
val height = size.height
|
||||
for (i in values.indices) {
|
||||
val value = values[i]
|
||||
val nextValue = values.getOrNull(i + 1) ?: values.last()
|
||||
val leftRatio = (value - lowerValue) / (upperValue - lowerValue)
|
||||
val rightRatio = (nextValue - lowerValue) / (upperValue - lowerValue)
|
||||
|
||||
val x1 = spacing + 40f + i * spacePerDay
|
||||
val y1 = height - spacing - (leftRatio * height).toFloat()
|
||||
val x2 = spacing + (i + 1) * spacePerDay
|
||||
val y2 = height - spacing - (rightRatio * height).toFloat()
|
||||
if (i == 0) {
|
||||
moveTo(x1, y1)
|
||||
}
|
||||
lastX = (x1 + x2) / 2f
|
||||
quadraticTo(x1, y1, lastX, (y1 + y2) / 2f)
|
||||
}
|
||||
}
|
||||
val fillPath = android.graphics.Path(strokePath.asAndroidPath())
|
||||
.asComposePath()
|
||||
.apply {
|
||||
lineTo(lastX, size.height - spacing)
|
||||
lineTo(spacing, size.height - spacing)
|
||||
close()
|
||||
}
|
||||
drawPath(
|
||||
path = fillPath,
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
transparentGraphColor,
|
||||
Color.Transparent
|
||||
),
|
||||
endY = size.height - spacing
|
||||
)
|
||||
)
|
||||
drawPath(
|
||||
path = strokePath,
|
||||
color = graphColor,
|
||||
style = Stroke(
|
||||
width = 3.dp.toPx(),
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
)
|
||||
val labelPadding = 12.dp.toPx()
|
||||
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
"Harga",
|
||||
spacing / 2f,
|
||||
labelPadding - 100,
|
||||
textPaint.apply { color = android.graphics.Color.GREEN }
|
||||
)
|
||||
|
||||
horizontalValue?.let { value ->
|
||||
val ratio = (value - lowerValue) / (upperValue - lowerValue)
|
||||
val y = size.height - spacing - (ratio * size.height).toFloat()
|
||||
drawLine(
|
||||
color = Color.Red,
|
||||
start = Offset(spacing, y),
|
||||
end = Offset(size.width, y),
|
||||
strokeWidth = 2.dp.toPx(),
|
||||
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
package com.syaroful.agrilinkvocpro.commodity_price_prediction_feature.presentation.prediction
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.detail.DynamicBottomSheet
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PricePredictionScreen() {
|
||||
val commodityOptions = listOf("Labu Kabocha", "Melon", "Strawberry", "kentang", "Bayam")
|
||||
val locationOptions = listOf("Malang", "Jombang", "Surabaya")
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
"Prediksi Harga Komoditas",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { }) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(text = "Pilih Komoditas", style = MaterialTheme.typography.labelLarge)
|
||||
DynamicBottomSheet(
|
||||
width = 1f,
|
||||
options = commodityOptions,
|
||||
modifier = Modifier.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
) {
|
||||
|
||||
}
|
||||
Text(text = "Pilih Lokasi", style = MaterialTheme.typography.labelLarge)
|
||||
DynamicBottomSheet(
|
||||
width = 1f,
|
||||
options = locationOptions,
|
||||
modifier = Modifier.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Harga Hari Ini",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier.alpha(0.5f)
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "Rp 30.000 ",
|
||||
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.W400)
|
||||
)
|
||||
Text(
|
||||
text = "↑ 2.5%",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MainGreen
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Prediksi Besok",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier.alpha(0.5f)
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "Rp 28.000 ",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MainGreen
|
||||
)
|
||||
Text(
|
||||
text = "↓ 7,6%",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = Color.Red
|
||||
)
|
||||
}
|
||||
PriceChart(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(140.dp),
|
||||
day = listOf(1, 2, 3, 4, 5, 6, 7),
|
||||
values = listOf(5000.0, 6000.0, 4000.0, 7000.0, 8500.0, 5450.0, 6400.0),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.syaroful.agrilinkvocpro.growth_recipe_feature.presentation.recipe
|
||||
|
||||
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,
|
||||
width: Float = 0.5f,
|
||||
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(width),
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.syaroful.agrilinkvocpro.growth_recipe_feature.presentation.recipe
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.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 com.syaroful.agrilinkvocpro.growth_recipe_feature.R
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.detail.LineChart
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GrowthRecipeScreen() {
|
||||
|
||||
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,
|
||||
) {
|
||||
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
DynamicBottomSheet(
|
||||
options = sensorOptions,
|
||||
) {
|
||||
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
fun Long.toFormattedDate(): String {
|
||||
val date = Date(this)
|
||||
val format = SimpleDateFormat("dd MMM yyyy", Locale("id"))
|
||||
return format.format(date)
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity.PlantDiagnosisEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface PlantDiagnosisDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertDiagnosis(diagnosis: PlantDiagnosisEntity)
|
||||
|
||||
@Query("SELECT * FROM plant_diagnosis ORDER BY timestamp DESC")
|
||||
fun getAllDiagnosis(): Flow<List<PlantDiagnosisEntity>>
|
||||
|
||||
@Query("SELECT * FROM plant_diagnosis WHERE id = :id")
|
||||
fun getDiagnosisById(id: Int): Flow<PlantDiagnosisEntity?>
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Suppress("DEPRECATED_ANNOTATION")
|
||||
@Parcelize
|
||||
@Entity(tableName = "plant_diagnosis")
|
||||
data class PlantDiagnosisEntity(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
val diagnosis: String,
|
||||
val cause: String,
|
||||
val description: String,
|
||||
val prevention: String,
|
||||
val treatment: String,
|
||||
val image: ByteArray,
|
||||
val timestamp: Long = System.currentTimeMillis()
|
||||
) : Parcelable
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository
|
||||
|
||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.dao.PlantDiagnosisDao
|
||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity.PlantDiagnosisEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class PlantDiagnosisLocalRepository(
|
||||
private val dao: PlantDiagnosisDao
|
||||
) {
|
||||
suspend fun saveDiagnosis(entity: PlantDiagnosisEntity) {
|
||||
dao.insertDiagnosis(entity)
|
||||
}
|
||||
|
||||
fun getAllDiagnosis(): Flow<List<PlantDiagnosisEntity>> = dao.getAllDiagnosis()
|
||||
|
||||
fun getDiagnosisById(id: Int): Flow<PlantDiagnosisEntity?> {
|
||||
return dao.getDiagnosisById(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history
|
||||
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
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.data.local.entity.PlantDiagnosisEntity
|
||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.detail.TextCardComponent
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DetailHistoryScreen(
|
||||
navController: NavHostController,
|
||||
diagnosisId: Int,
|
||||
viewModel: HistoryViewModel
|
||||
) {
|
||||
var plantDiagnosis by remember { mutableStateOf<PlantDiagnosisEntity?>(null) }
|
||||
|
||||
LaunchedEffect(diagnosisId) {
|
||||
viewModel.getDiagnosisById(diagnosisId).collect {
|
||||
plantDiagnosis = it
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
"Riwayat Deteksi",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
navController.popBackStack()
|
||||
}) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(innerPadding)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
10.dp,
|
||||
Alignment.Top
|
||||
)
|
||||
) {
|
||||
when (val data = plantDiagnosis) {
|
||||
null -> {
|
||||
DefaultErrorComponent(
|
||||
label = "Waduh!",
|
||||
message = "Data Tidak Ditemukan",
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val bitmap = try {
|
||||
data.image.toBitmap().asImageBitmap()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
bitmap?.let {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 180.dp),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bitmap = it,
|
||||
contentDescription = "Gambar Tanaman",
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = Color.Red,
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
)
|
||||
.padding(vertical = 4.dp, horizontal = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Terkena Penyakit",
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Penyakit Terdeteksi :",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color(0xFFFF3D3D)
|
||||
)
|
||||
Text(
|
||||
plantDiagnosis?.diagnosis ?: "-",
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Text(
|
||||
text = "Penyebab :",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color(0xFFFF3D3D)
|
||||
)
|
||||
Text(
|
||||
plantDiagnosis?.cause ?: "-",
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
TextCardComponent(
|
||||
label = "Informasi Umum",
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = plantDiagnosis?.description ?: "-",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
TextCardComponent(
|
||||
label = "Pengobatan",
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = plantDiagnosis?.treatment ?: "-",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
TextCardComponent(
|
||||
label = "Pencegahan",
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = plantDiagnosis?.prevention ?: "-",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
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.toFormattedDate
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HistoryScreen(
|
||||
navController: NavHostController,
|
||||
viewModel: HistoryViewModel
|
||||
) {
|
||||
val diagnoses by viewModel.diagnoses.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
"Riwayat Deteksi",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
navController.popBackStack()
|
||||
}) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
|
||||
when {
|
||||
|
||||
(diagnoses.isEmpty()) -> DefaultErrorComponent(
|
||||
label = "Waduh!",
|
||||
message = "Ga ada data sama sekali, coba tambahkan",
|
||||
painter = painterResource(id = R.drawable.mascot_surprised)
|
||||
)
|
||||
|
||||
else -> LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
items(diagnoses) { diagnosis ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
navController.currentBackStackEntry?.savedStateHandle?.set(
|
||||
"plantData",
|
||||
diagnosis
|
||||
)
|
||||
navController.navigate("detail-history/${diagnosis.id}")
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(80.dp)
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
) {
|
||||
Image(
|
||||
bitmap = diagnosis.image.toBitmap().asImageBitmap(),
|
||||
contentDescription = "Diagnosis Image"
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column {
|
||||
Text(
|
||||
"Diagnosis: ${diagnosis.diagnosis}",
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
diagnosis.timestamp.toFormattedDate(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.End
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity.PlantDiagnosisEntity
|
||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.PlantDiagnosisLocalRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class HistoryViewModel(
|
||||
private val repository: PlantDiagnosisLocalRepository
|
||||
) : ViewModel() {
|
||||
|
||||
val diagnoses = repository.getAllDiagnosis().stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
emptyList()
|
||||
)
|
||||
|
||||
fun getDiagnosisById(id: Int): Flow<PlantDiagnosisEntity?> {
|
||||
return repository.getDiagnosisById(id)
|
||||
}
|
||||
}
|
||||
BIN
skripsi/Draft Skripsi - Muhamad Syaroful Anam.docx
Normal file
BIN
skripsi/Draft Skripsi - Muhamad Syaroful Anam.docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user