diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/utils/extention/SensorUtils.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/utils/extention/SensorUtils.kt new file mode 100644 index 0000000..be18adc --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/utils/extention/SensorUtils.kt @@ -0,0 +1,2 @@ +package com.syaroful.agrilinkvocpro.core.utils.extention + diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/DhtGraphicDataResponse.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/DhtGraphicDataResponse.kt new file mode 100644 index 0000000..ef65c75 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/model/DhtGraphicDataResponse.kt @@ -0,0 +1,7 @@ +package com.syaroful.agrilinkvocpro.data.model + +data class DhtGraphicDataResponse( + val `data`: Data?, + val message: String?, + val statusCode: Int? +) \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/DataSensorBar.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DataSensorBar.kt similarity index 100% rename from agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/DataSensorBar.kt rename to agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DataSensorBar.kt diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DatePickerComponent.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DatePickerComponent.kt new file mode 100644 index 0000000..c9bc60b --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DatePickerComponent.kt @@ -0,0 +1,4 @@ +package com.syaroful.agrilinkvocpro.presentation.screen.detail.component + +class DatePickerComponent { +} \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailDhtContent.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailDhtContent.kt new file mode 100644 index 0000000..8a73f08 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailDhtContent.kt @@ -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, + currentData: SensorDataResponse?, + sensorId: String, + isRefreshing: MutableState, + 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 +) { + DynamicBottomSheet( + options = options, + ) { + onSensorSelected + } +} diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailNpkContent.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailNpkContent.kt new file mode 100644 index 0000000..60f8da7 --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailNpkContent.kt @@ -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, + currentData: SensorDataResponse?, + sensorId: String, + isRefreshing: MutableState, + 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) + } +} diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailSensorData.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailSensorData.kt new file mode 100644 index 0000000..8715ded --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DetailSensorData.kt @@ -0,0 +1,2 @@ +package com.syaroful.agrilinkvocpro.presentation.screen.detail.component + diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/DynamicBottomSheet.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DynamicBottomSheet.kt similarity index 97% rename from agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/DynamicBottomSheet.kt rename to agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DynamicBottomSheet.kt index 2452a49..8cb93d5 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/DynamicBottomSheet.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/DynamicBottomSheet.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.unit.dp fun DynamicBottomSheet( modifier: Modifier = Modifier, options: List = 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 ) { diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/LineChart.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/LineChart.kt similarity index 75% rename from agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/LineChart.kt rename to agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/LineChart.kt index 2a40579..375b5c6 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/LineChart.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/screen/detail/component/LineChart.kt @@ -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 = emptyList(), values: List = 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)) + ) + } } } -} \ No newline at end of file +} diff --git a/agrilinkvocpro/app/src/main/res/drawable/solar_user.png b/agrilinkvocpro/app/src/main/res/drawable/solar_user.png new file mode 100644 index 0000000..b01f1f0 Binary files /dev/null and b/agrilinkvocpro/app/src/main/res/drawable/solar_user.png differ diff --git a/agrilinkvocpro/app/src/main/res/drawable/solar_user_circle.png b/agrilinkvocpro/app/src/main/res/drawable/solar_user_circle.png new file mode 100644 index 0000000..fb5d60c Binary files /dev/null and b/agrilinkvocpro/app/src/main/res/drawable/solar_user_circle.png differ diff --git a/agrilinkvocpro/commodity_price_prediction_feature/src/main/java/com/syaroful/agrilinkvocpro/commodity_price_prediction_feature/presentation/prediction/PriceChart.kt b/agrilinkvocpro/commodity_price_prediction_feature/src/main/java/com/syaroful/agrilinkvocpro/commodity_price_prediction_feature/presentation/prediction/PriceChart.kt new file mode 100644 index 0000000..4dc2037 --- /dev/null +++ b/agrilinkvocpro/commodity_price_prediction_feature/src/main/java/com/syaroful/agrilinkvocpro/commodity_price_prediction_feature/presentation/prediction/PriceChart.kt @@ -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 = emptyList(), + values: List = 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)) + ) + } + } + } +} \ No newline at end of file diff --git a/agrilinkvocpro/commodity_price_prediction_feature/src/main/java/com/syaroful/agrilinkvocpro/commodity_price_prediction_feature/presentation/prediction/PricePredictionScreen.kt b/agrilinkvocpro/commodity_price_prediction_feature/src/main/java/com/syaroful/agrilinkvocpro/commodity_price_prediction_feature/presentation/prediction/PricePredictionScreen.kt new file mode 100644 index 0000000..365a740 --- /dev/null +++ b/agrilinkvocpro/commodity_price_prediction_feature/src/main/java/com/syaroful/agrilinkvocpro/commodity_price_prediction_feature/presentation/prediction/PricePredictionScreen.kt @@ -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), + ) + } + } + } + +} \ No newline at end of file diff --git a/agrilinkvocpro/growth_recipe_feature/src/main/java/com/syaroful/agrilinkvocpro/growth_recipe_feature/presentation/recipe/DynamicBottomSheet.kt b/agrilinkvocpro/growth_recipe_feature/src/main/java/com/syaroful/agrilinkvocpro/growth_recipe_feature/presentation/recipe/DynamicBottomSheet.kt new file mode 100644 index 0000000..bfebf5b --- /dev/null +++ b/agrilinkvocpro/growth_recipe_feature/src/main/java/com/syaroful/agrilinkvocpro/growth_recipe_feature/presentation/recipe/DynamicBottomSheet.kt @@ -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 = 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) + ) + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..9045494 --- /dev/null +++ b/agrilinkvocpro/growth_recipe_feature/src/main/java/com/syaroful/agrilinkvocpro/growth_recipe_feature/presentation/recipe/GrowthRecipeScreen.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/core/extention/Long.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/core/extention/Long.kt new file mode 100644 index 0000000..2b00a44 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/core/extention/Long.kt @@ -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) +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/local/dao/PlantDiagnosisDao.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/local/dao/PlantDiagnosisDao.kt new file mode 100644 index 0000000..bfc7776 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/local/dao/PlantDiagnosisDao.kt @@ -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> + + @Query("SELECT * FROM plant_diagnosis WHERE id = :id") + fun getDiagnosisById(id: Int): Flow +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/local/entity/PlantDiagnosisEntity.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/local/entity/PlantDiagnosisEntity.kt new file mode 100644 index 0000000..76c12a0 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/local/entity/PlantDiagnosisEntity.kt @@ -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 \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/PlantDiagnosisLocalRepository.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/PlantDiagnosisLocalRepository.kt new file mode 100644 index 0000000..be130ef --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/PlantDiagnosisLocalRepository.kt @@ -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> = dao.getAllDiagnosis() + + fun getDiagnosisById(id: Int): Flow { + return dao.getDiagnosisById(id) + } +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/DetailHistoryScreen.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/DetailHistoryScreen.kt new file mode 100644 index 0000000..4cd5082 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/DetailHistoryScreen.kt @@ -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(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 + ) + } + } + } + + + } + } +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/HistoryScreen.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/HistoryScreen.kt new file mode 100644 index 0000000..48ac767 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/HistoryScreen.kt @@ -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 + ) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/HistoryViewModel.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/HistoryViewModel.kt new file mode 100644 index 0000000..ed087e8 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/history/HistoryViewModel.kt @@ -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 { + return repository.getDiagnosisById(id) + } +} diff --git a/skripsi/Draft Skripsi - Muhamad Syaroful Anam.docx b/skripsi/Draft Skripsi - Muhamad Syaroful Anam.docx new file mode 100644 index 0000000..84fc0ab Binary files /dev/null and b/skripsi/Draft Skripsi - Muhamad Syaroful Anam.docx differ