feat: implement detail screen for NPK and DHT sensors

This commit is contained in:
Cutiful 2025-06-29 07:53:51 +07:00
parent f48a96c96c
commit c2aa13e4ce
29 changed files with 511 additions and 375 deletions

View File

@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-06-11T17:05:21.600763700Z"> <DropdownSelection timestamp="2025-06-20T02:45:08.711114800Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\syaro\.android\avd\Pixel_8_Pro_API_31.avd" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=RRCX401ZEKL" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@ -7,6 +7,7 @@ import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -16,7 +17,6 @@ import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
@ -24,14 +24,14 @@ import androidx.compose.ui.unit.dp
fun Modifier.shimmerEffect( fun Modifier.shimmerEffect(
shimmerColors: List<Color> = listOf(
Color.LightGray.copy(alpha = 0.6f),
Color.LightGray.copy(alpha = 0.2f),
Color.LightGray.copy(alpha = 0.6f)
),
shimmerDuration: Int = 1000, shimmerDuration: Int = 1000,
cornerRadius: Dp = 16.dp cornerRadius: Dp = 16.dp
): Modifier = composed { ): Modifier = composed {
val shimmerColors = listOf(
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f),
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.2f),
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f)
)
var size by remember { mutableStateOf(IntSize.Zero) } var size by remember { mutableStateOf(IntSize.Zero) }
val transition = rememberInfiniteTransition() val transition = rememberInfiniteTransition()

View File

@ -8,3 +8,8 @@ fun Date.toFormattedString(): String {
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return sdf.format(this) return sdf.format(this)
} }
fun Date.toDDMMYYYYString(): String {
val sdf = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault())
return sdf.format(this)
}

View File

@ -1,2 +1,33 @@
package com.syaroful.agrilinkvocpro.core.utils.extention package com.syaroful.agrilinkvocpro.core.utils.extention
import com.syaroful.agrilinkvocpro.data.model.DhtWithHour
import com.syaroful.agrilinkvocpro.data.model.NpkWithHour
import java.text.SimpleDateFormat
import java.util.Date
fun getValuesForSensorNpk(sensor: String, data: List<NpkWithHour>): List<Double> {
return when (sensor) {
"Nitrogen" -> data.mapNotNull { it.soilNitrogen?.toDouble() }
"Pospor" -> data.mapNotNull { it.soilPhosphorus?.toDouble() }
"Kalium" -> data.mapNotNull { it.soilPotassium?.toDouble() }
"Suhu Tanah" -> data.mapNotNull { it.soilTemperature?.toDouble() }
"PH Tanah" -> data.mapNotNull { it.soilPh?.toDouble() }
"Kelembapan" -> data.mapNotNull { it.soilHumidity?.toDouble() }
"Konduktivitas" -> data.mapNotNull { it.soilConductivity?.toDouble() }
else -> emptyList()
}
}
fun getValuesForSensorDht(sensor: String, data: List<DhtWithHour>): List<Double> {
return when (sensor) {
"Kelembaban Udara" -> data.mapNotNull { it.viciHumidity?.toDouble() }
"Suhu Ruang" -> data.mapNotNull { it.viciTemperature?.toDouble() }
"Intensitas Cahaya" -> data.mapNotNull { it.viciLuminosity?.toDouble() }
else -> emptyList()
}
}
fun getCurrentDate(): String {
val sdf = SimpleDateFormat("dd MMMM yyyy", java.util.Locale.getDefault())
return sdf.format(Date())
}

View File

@ -1,7 +1,16 @@
package com.syaroful.agrilinkvocpro.data.model package com.syaroful.agrilinkvocpro.data.model
data class DhtGraphicDataResponse( data class DhtGraphicDataResponse(
val `data`: Data?, val data: Map<String, List<DhtWithHour>>?,
val message: String?, val message: String?,
val statusCode: Int? val statusCode: Int?
) )
data class DhtWithHour(
val date: String?,
val hour: Int?,
val viciHumidity: Number?,
val viciLuminosity: Number?,
val viciTemperature: Number?
)

View File

@ -9,11 +9,11 @@ data class NpkGraphicDataResponse(
data class NpkWithHour( data class NpkWithHour(
val hour: Int?, val hour: Int?,
val date: String?, val date: String?,
val soiltemperature: Number?, val soilTemperature: Number?,
val soilhumidity: Number?, val soilHumidity: Number?,
val soilconductivity: Number?, val soilConductivity: Number?,
val soilph: Number?, val soilPh: Number?,
val soilnitrogen: Number?, val soilNitrogen: Number?,
val soilphosphorus: Number?, val soilPhosphorus: Number?,
val soilpotassium: Number?, val soilPotassium: Number?,
) )

View File

@ -13,17 +13,17 @@ data class SensorData(
) )
data class Dht( data class Dht(
val vicitemperature: Number?, val viciTemperature: Number?,
val vicihumidity: Number?, val viciHumidity: Number?,
val viciluminosity: Number? val viciLuminosity: Number?
) )
data class Npk( data class Npk(
val soiltemperature: Number?, val soilTemperature: Number?,
val soilhumidity: Number?, val soilHumidity: Number?,
val soilconductivity: Number?, val soilConductivity: Number?,
val soilph: Number?, val soilPh: Number?,
val soilnitrogen: Number?, val soilNitrogen: Number?,
val soilphosphorus: Number?, val soilPhosphorus: Number?,
val soilpotassium: Number? val soilPotassium: Number?
) )

View File

@ -1,5 +1,6 @@
package com.syaroful.agrilinkvocpro.data.network package com.syaroful.agrilinkvocpro.data.network
import com.syaroful.agrilinkvocpro.data.model.DhtGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.LoginResponse import com.syaroful.agrilinkvocpro.data.model.LoginResponse
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
@ -13,23 +14,24 @@ import retrofit2.http.Query
data class LoginRequest( data class LoginRequest(
val username: String, val username: String,
val password: String val password: String,
val remember_me: Boolean = true
) )
interface ApiService { interface ApiService {
@POST("auth/login") @POST("auth/login")
suspend fun login( suspend fun login(
@Header("Authorization") authHeader: String, @Header("Authorization") authHeader: String,
@Body body: LoginRequest @Body body: LoginRequest,
): Response<LoginResponse> ): Response<LoginResponse>
@GET("api/sensor/getLatest") @GET("api/sensor-readings/latest")
suspend fun getLatestSensorData( suspend fun getLatestSensorData(
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Response<SensorDataResponse> ): Response<SensorDataResponse>
@GET("api/sensor/getData") @GET("api/sensor-readings/search")
suspend fun getNpk1DataSensor( suspend fun getNpkDataSensor(
@Header("Authorization") authHeader: String, @Header("Authorization") authHeader: String,
@Query("range[start]") startDate: String, @Query("range[start]") startDate: String,
@Query("range[end]") endDate: String, @Query("range[end]") endDate: String,
@ -37,4 +39,13 @@ interface ApiService {
@Query("sensor") sensor: String @Query("sensor") sensor: String
): Response<NpkGraphicDataResponse> ): Response<NpkGraphicDataResponse>
@GET("api/sensor-readings/search")
suspend fun getDhtDataSensor(
@Header("Authorization") authHeader: String,
@Query("range[start]") startDate: String,
@Query("range[end]") endDate: String,
@Query("range[time_range]") timeRange: String = "HOURLY",
@Query("sensor") sensor: String
): Response<DhtGraphicDataResponse>
} }

View File

@ -11,6 +11,6 @@ class AuthRepository(
suspend fun login(username: String, password: String): Response<LoginResponse> { suspend fun login(username: String, password: String): Response<LoginResponse> {
val credential = okhttp3.Credentials.basic(username, password) val credential = okhttp3.Credentials.basic(username, password)
val requestBody = LoginRequest(username, password) val requestBody = LoginRequest(username, password)
return apiService.login(credential, requestBody) return apiService.login(authHeader = credential, body = requestBody)
} }
} }

View File

@ -1,5 +1,6 @@
package com.syaroful.agrilinkvocpro.data.repository package com.syaroful.agrilinkvocpro.data.repository
import com.syaroful.agrilinkvocpro.data.model.DhtGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
import com.syaroful.agrilinkvocpro.data.network.ApiService import com.syaroful.agrilinkvocpro.data.network.ApiService
@ -25,6 +26,16 @@ class SensorDataRepository(private val apiService: ApiService) {
timeRange: String = "HOURLY", timeRange: String = "HOURLY",
sensor: String sensor: String
): Response<NpkGraphicDataResponse> { ): Response<NpkGraphicDataResponse> {
return apiService.getNpk1DataSensor(authHeader, startDate, endDate, timeRange, sensor) return apiService.getNpkDataSensor(authHeader, startDate, endDate, timeRange, sensor)
}
suspend fun getDhtDataSensor(
authHeader: String,
startDate: String,
endDate: String,
timeRange: String = "HOURLY",
sensor: String
): Response<DhtGraphicDataResponse> {
return apiService.getDhtDataSensor(authHeader, startDate, endDate, timeRange, sensor)
} }
} }

View File

@ -5,10 +5,12 @@ import okhttp3.OkHttpClient
import org.koin.dsl.module import org.koin.dsl.module
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
val networkModule = module { val networkModule = module {
single { single {
OkHttpClient.Builder() OkHttpClient.Builder()
.connectTimeout(2, TimeUnit.SECONDS)
.build() .build()
} }

View File

@ -48,7 +48,7 @@ fun SetupNavigation(
}, },
onNavigateToRegister = { onNavigateToRegister = {
navController.navigate("register") { navController.navigate("register") {
popUpTo("login") { inclusive = true } popUpTo("register") { inclusive = true }
} }
} }
) )

View File

@ -1,14 +1,8 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail package com.syaroful.agrilinkvocpro.presentation.screen.detail
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -22,19 +16,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import com.syaroful.agrilinkvocpro.core.utils.extention.getCurrentDate
import androidx.compose.ui.res.painterResource import com.syaroful.agrilinkvocpro.core.utils.extention.toFormattedString
import androidx.compose.ui.text.font.FontStyle import com.syaroful.agrilinkvocpro.presentation.screen.detail.component.DetailDhtContent
import androidx.compose.ui.text.style.TextAlign import com.syaroful.agrilinkvocpro.presentation.screen.detail.component.DetailNpkContent
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 org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -45,185 +32,69 @@ fun DetailScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
detailViewModel: DetailViewModel = koinViewModel(), detailViewModel: DetailViewModel = koinViewModel(),
) { ) {
val date = remember { mutableStateOf(Date().toFormattedString()) }
LaunchedEffect(sensorId) { LaunchedEffect(sensorId) {
detailViewModel.fetchNpkData(sensorId) if (sensorId == "dht") {
detailViewModel.fetchDhtData(date = date.value, sensor = sensorId)
} else {
detailViewModel.fetchNpkData(date = date.value, sensor = sensorId)
}
} }
val npkDataState by detailViewModel.npkDataState.collectAsState()
val dhtDataState by detailViewModel.dhtDataState.collectAsState()
val currentData = detailViewModel.currentSensorData val currentData = detailViewModel.currentSensorData
val sdf = SimpleDateFormat("dd MMMM yyyy", java.util.Locale.getDefault()) val isRefreshing = remember { mutableStateOf(false) }
val currentDate = sdf.format(Date()) val currentDate = remember { mutableStateOf(getCurrentDate()) }
val options = listOf(
"Nitrogen",
"Pospor",
"Kalium",
"Suhu Tanah",
"PH Tanah",
"Kelembapan",
"Konduktivitas"
)
Scaffold( Scaffold(
topBar = { topBar = { DetailTopBar() }) { innerPadding ->
TopAppBar(
title = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(
"Detail Grafik",
style = MaterialTheme.typography.titleMedium
)
}
},
)
},
) { innerPadding ->
val npkDataState by detailViewModel.npkDataState.collectAsState()
val isRefreshing = remember { mutableStateOf(false) }
PullToRefreshBox( PullToRefreshBox(
isRefreshing = isRefreshing.value, isRefreshing = isRefreshing.value, onRefresh = {
onRefresh = {
isRefreshing.value = true isRefreshing.value = true
detailViewModel.fetchNpkData(sensorId) if (sensorId == "dht") {
}, detailViewModel.fetchDhtData(date = date.value, sensor = sensorId)
) {
Column(
modifier = Modifier
.padding(innerPadding)
.padding(top = 16.dp)
.padding(horizontal = 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 as ResultState.Error).message,
painter = painterResource(id = R.drawable.mascot_confused)
)
}
ResultState.Idle -> {
}
is ResultState.Success -> {
isRefreshing.value = false
val selectedSensor = remember { mutableStateOf(options[0]) }
val result = (npkDataState as ResultState.Success).data
val npkData = result?.data?.get(sensorId)
val hours = npkData?.filter { it.hour != null }?.map { it.hour!!.toInt() }
?: emptyList()
val values = when (selectedSensor.value) {
"Nitrogen" -> npkData?.mapNotNull { it.soilnitrogen?.toDouble() } ?: emptyList()
"Pospor" -> npkData?.mapNotNull { it.soilphosphorus?.toDouble() } ?: emptyList()
"Kalium" -> npkData?.mapNotNull { it.soilpotassium?.toDouble() } ?: emptyList()
"Suhu Tanah" -> npkData?.mapNotNull { it.soiltemperature?.toDouble() } ?: emptyList()
"PH Tanah" -> npkData?.mapNotNull { it.soilph?.toDouble() } ?: emptyList()
"Kelembapan" -> npkData?.mapNotNull { it.soilhumidity?.toDouble() } ?: emptyList()
"Konduktivitas" -> npkData?.mapNotNull { it.soilconductivity?.toDouble() } ?: emptyList()
else -> emptyList()
}
DynamicBottomSheet(
options = options
) { selected ->
selectedSensor.value = selected
}
Spacer(modifier = Modifier.height(16.dp))
LineChart(
hours = hours,
values = values,
modifier = Modifier
.fillMaxWidth()
.height(180.dp)
.align(CenterHorizontally)
)
}
}
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)
)
if (currentData != null) {
val dataSensor = when (sensorId) {
"npk1" -> currentData.data?.npk1
"npk2" -> currentData.data?.npk2
else -> null
}
Column(
verticalArrangement = Arrangement.spacedBy(
8.dp,
Alignment.Top
)
) {
DataSensorBar(
label = "Nitrogen",
value = dataSensor?.soilnitrogen ?: 0,
painter = painterResource(id = R.drawable.npk), // Assuming the max value is 255
percentage = (dataSensor?.soilnitrogen?.toFloat() ?: 0f) / 50f
)
DataSensorBar(
label = "Pospor",
value = dataSensor?.soilphosphorus ?: 0,
painter = painterResource(id = R.drawable.npk), // Assuming the max value is 255
percentage = (dataSensor?.soilphosphorus?.toFloat() ?: 0f) / 255f
)
DataSensorBar(
label = "Kalium",
value = dataSensor?.soilpotassium ?: 0,
painter = painterResource(id = R.drawable.npk), // Assuming the max value is 255
percentage = (dataSensor?.soilpotassium?.toFloat() ?: 0f) / 255f
)
DataSensorBar(
label = "Kelembaban",
value = dataSensor?.soilhumidity ?: 0,
painter = painterResource(id = R.drawable.soil_humidity), // Assuming the max value is 100
percentage = (dataSensor?.soilhumidity?.toFloat() ?: 0f) / 100f
)
DataSensorBar(
label = "Suhu Tanah",
value = dataSensor?.soiltemperature ?: 0,
painter = painterResource(id = R.drawable.soil_temperature), // Assuming the max value is 50 (adjust as needed)
percentage = (dataSensor?.soiltemperature?.toFloat() ?: 0f) / 50f
)
DataSensorBar(
label = "PH Tanah",
value = dataSensor?.soilph ?: 0,
painter = painterResource(id = R.drawable.meters), // Assuming the max value is 14
percentage = (dataSensor?.soilph?.toFloat() ?: 0f) / 14f
)
DataSensorBar(
label = "Konduktivitas",
value = dataSensor?.soilconductivity ?: 0,
painter = painterResource(id = R.drawable.electricity),
percentage = (dataSensor?.soilconductivity?.toFloat() ?: 0f) / 200f
)
}
} else { } else {
Text("data Sesnor kosong") detailViewModel.fetchNpkData(date = date.value, sensor = sensorId)
} }
}) {
} if (sensorId == "dht") DetailDhtContent(
modifier = Modifier.padding(innerPadding),
viewModel = detailViewModel,
dhtDataState = dhtDataState,
currentData = currentData,
sensorId = sensorId,
isRefreshing = isRefreshing,
date = date,
currentDate = currentDate
)
else DetailNpkContent(
modifier = Modifier.padding(innerPadding),
viewModel = detailViewModel,
npkDataState = npkDataState,
currentData = currentData,
sensorId = sensorId,
isRefreshing = isRefreshing,
date = date,
currentDate = currentDate
)
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailTopBar() {
TopAppBar(
title = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text("Detail Grafik", style = MaterialTheme.typography.titleMedium)
}
})
}

View File

@ -7,6 +7,7 @@ import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.core.utils.extention.mapToUserFriendlyError import com.syaroful.agrilinkvocpro.core.utils.extention.mapToUserFriendlyError
import com.syaroful.agrilinkvocpro.core.utils.extention.toFormattedString import com.syaroful.agrilinkvocpro.core.utils.extention.toFormattedString
import com.syaroful.agrilinkvocpro.data.UserPreferences import com.syaroful.agrilinkvocpro.data.UserPreferences
import com.syaroful.agrilinkvocpro.data.model.DhtGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
import com.syaroful.agrilinkvocpro.data.repository.SensorDataRepository import com.syaroful.agrilinkvocpro.data.repository.SensorDataRepository
@ -32,22 +33,25 @@ class DetailViewModel(
private val _npkDataState = MutableStateFlow<ResultState<NpkGraphicDataResponse>>(ResultState.Idle) private val _npkDataState = MutableStateFlow<ResultState<NpkGraphicDataResponse>>(ResultState.Idle)
val npkDataState: StateFlow<ResultState<NpkGraphicDataResponse>> = _npkDataState.asStateFlow() val npkDataState: StateFlow<ResultState<NpkGraphicDataResponse>> = _npkDataState.asStateFlow()
private val _dhtDataState = MutableStateFlow<ResultState<DhtGraphicDataResponse>>(ResultState.Idle)
val dhtDataState: StateFlow<ResultState<DhtGraphicDataResponse>> = _dhtDataState.asStateFlow()
private val today = Date() private val today = Date()
fun fetchNpkData( fun fetchNpkData(
date: String = today.toFormattedString(),
sensor: String, sensor: String,
) { ) {
viewModelScope.launch { viewModelScope.launch {
_npkDataState.value = ResultState.Loading _npkDataState.value = ResultState.Loading
val token = userPreferences.tokenFlow.first() val token = userPreferences.tokenFlow.first()
val authHeader = "Bearer $token" val authHeader = "Bearer $token"
val formattedToday = today.toFormattedString()
try { try {
delay(2000L) delay(1000L)
val response = sensorDataRepository.getNpkDataSensor( val response = sensorDataRepository.getNpkDataSensor(
authHeader = authHeader, authHeader = authHeader,
startDate = formattedToday, startDate = date,
endDate = formattedToday, endDate = date,
timeRange = "HOURLY", timeRange = "HOURLY",
sensor = sensor sensor = sensor
) )
@ -67,4 +71,38 @@ class DetailViewModel(
} }
} }
} }
fun fetchDhtData(
date: String = today.toFormattedString(),
sensor: String,
) {
viewModelScope.launch {
_dhtDataState.value = ResultState.Loading
val token = userPreferences.tokenFlow.first()
val authHeader = "Bearer $token"
try {
delay(1000L)
val response = sensorDataRepository.getDhtDataSensor(
authHeader = authHeader,
startDate = date,
endDate = date,
timeRange = "HOURLY",
sensor = sensor
)
if (response.isSuccessful) {
response.body()?.let { body ->
_dhtDataState.value = ResultState.Success(body)
} ?: run {
_dhtDataState.value = ResultState.Error("Data tidak ditemukan")
}
} else {
_dhtDataState.value = ResultState.Error("Error: ${response.code()} - ${response.message()}")
}
} catch (e: Exception) {
val errorMessage = mapToUserFriendlyError(e)
_dhtDataState.value = ResultState.Error(errorMessage)
Log.d(TAG, "Failed to fetch data: ${e.message}")
}
}
}
} }

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background

View File

@ -1,4 +1,50 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail.component package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
class DatePickerComponent { import android.app.DatePickerDialog
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.core.utils.extention.toFormattedString
import java.util.Calendar
@Composable
fun DatePickerComponent(
modifier: Modifier = Modifier,
onDateSelected: (String) -> Unit,
) {
val context = LocalContext.current
val calendar = remember { Calendar.getInstance() }
IconButton(
onClick = {
val datePickerDialog = DatePickerDialog(
context,
{ _, year, month, dayOfMonth ->
calendar.set(year, month, dayOfMonth)
val selectedDate = calendar.time.toFormattedString()
onDateSelected(selectedDate)
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
datePickerDialog.show()
},
modifier = modifier
) {
Icon(
painter = painterResource(id = R.drawable.ic_calendar),
modifier = Modifier.size(24.dp),
contentDescription = "Pilih Tanggal"
)
}
} }

View File

@ -1,7 +1,9 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail.component package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -25,36 +27,36 @@ import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
import com.syaroful.agrilinkvocpro.core.placeholder.shimmerEffect import com.syaroful.agrilinkvocpro.core.placeholder.shimmerEffect
import com.syaroful.agrilinkvocpro.core.utils.ResultState import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.core.utils.extention.getValuesForSensor import com.syaroful.agrilinkvocpro.core.utils.extention.getValuesForSensorDht
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse import com.syaroful.agrilinkvocpro.data.model.DhtGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
import com.syaroful.agrilinkvocpro.presentation.screen.detail.DetailViewModel
@Composable @Composable
fun DetailContent( fun DetailDhtContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
npkDataState: ResultState<NpkGraphicDataResponse?>, viewModel: DetailViewModel,
dhtDataState: ResultState<DhtGraphicDataResponse?>,
currentData: SensorDataResponse?, currentData: SensorDataResponse?,
sensorId: String, sensorId: String,
isRefreshing: MutableState<Boolean>, isRefreshing: MutableState<Boolean>,
currentDate: String, date: MutableState<String>,
currentDate: MutableState<String>,
) { ) {
val selectedSensor = remember { mutableStateOf("Nitrogen") }
val options = listOf( val options = listOf(
"Nitrogen", "Kelembaban Udara",
"Pospor", "Suhu Ruang",
"Kalium", "Intensitas Cahaya",
"Suhu Tanah",
"PH Tanah",
"Kelembapan",
"Konduktivitas"
) )
val selectedSensor = remember { mutableStateOf("Kelembaban Udara") }
Column( Column(
modifier = modifier modifier = modifier
.padding(16.dp) .padding(16.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
when (npkDataState) { when (dhtDataState) {
is ResultState.Loading -> { is ResultState.Loading -> {
Box( Box(
modifier = Modifier modifier = Modifier
@ -69,25 +71,35 @@ fun DetailContent(
DefaultErrorComponent( DefaultErrorComponent(
modifier = Modifier.padding(vertical = 20.dp), modifier = Modifier.padding(vertical = 20.dp),
label = "Oops!", label = "Oops!",
message = npkDataState.message, message = dhtDataState.message,
painter = painterResource(id = R.drawable.mascot_confused) painter = painterResource(id = R.drawable.mascot_confused)
) )
} }
is ResultState.Success -> { is ResultState.Success -> {
isRefreshing.value = false isRefreshing.value = false
val dataList = npkDataState.data?.data?.get(sensorId).orEmpty() val dataList = dhtDataState.data?.data?.get(sensorId).orEmpty()
DynamicBottomSheet( Row(
options = options modifier = Modifier.fillMaxWidth(),
) { selected -> horizontalArrangement = Arrangement.SpaceBetween
selectedSensor.value = selected ) {
DynamicBottomSheet(
options = options
) { selected ->
selectedSensor.value = selected
}
DatePickerComponent() {
date.value = it
viewModel.fetchDhtData(date = date.value, sensor = sensorId)
currentDate.value = date.value
}
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
val hours = dataList.mapNotNull { it.hour?.toInt() } val hours = dataList.mapNotNull { it.hour?.toInt() }
val values = getValuesForSensor(selectedSensor.value, dataList) val values = getValuesForSensorDht(selectedSensor.value, dataList)
LineChart( LineChart(
hours = hours, hours = hours,
@ -103,7 +115,7 @@ fun DetailContent(
} }
Text( Text(
"Grafik ini adalah grafik per hari ini tanggal $currentDate", "Grafik ini adalah grafik per tanggal ${currentDate.value}",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,
@ -116,16 +128,3 @@ fun DetailContent(
DetailSensorData(currentData = currentData, sensorId = sensorId) DetailSensorData(currentData = currentData, sensorId = sensorId)
} }
} }
@Composable
fun SensorSelector(
selectedSensor: String,
onSensorSelected: (String) -> Unit,
options: List<String>
) {
DynamicBottomSheet(
options = options,
) {
onSensorSelected
}
}

View File

@ -1,7 +1,9 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail.component package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -28,15 +30,18 @@ import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.core.utils.extention.getValuesForSensorNpk import com.syaroful.agrilinkvocpro.core.utils.extention.getValuesForSensorNpk
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
import com.syaroful.agrilinkvocpro.presentation.screen.detail.DetailViewModel
@Composable @Composable
fun DetailNpkContent( fun DetailNpkContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: DetailViewModel,
npkDataState: ResultState<NpkGraphicDataResponse?>, npkDataState: ResultState<NpkGraphicDataResponse?>,
currentData: SensorDataResponse?, currentData: SensorDataResponse?,
sensorId: String, sensorId: String,
isRefreshing: MutableState<Boolean>, isRefreshing: MutableState<Boolean>,
currentDate: String, date: MutableState<String>,
currentDate: MutableState<String>,
) { ) {
val selectedSensor = remember { mutableStateOf("Nitrogen") } val selectedSensor = remember { mutableStateOf("Nitrogen") }
val options = listOf( val options = listOf(
@ -78,10 +83,20 @@ fun DetailNpkContent(
isRefreshing.value = false isRefreshing.value = false
val dataList = npkDataState.data?.data?.get(sensorId).orEmpty() val dataList = npkDataState.data?.data?.get(sensorId).orEmpty()
DynamicBottomSheet( Row(
options = options modifier = Modifier.fillMaxWidth(),
) { selected -> horizontalArrangement = Arrangement.SpaceBetween
selectedSensor.value = selected ) {
DynamicBottomSheet(
options = options
) { selected ->
selectedSensor.value = selected
}
DatePickerComponent() {
date.value = it
viewModel.fetchNpkData(date = date.value, sensor = sensorId)
currentDate.value = date.value
}
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@ -103,7 +118,7 @@ fun DetailNpkContent(
} }
Text( Text(
"Grafik ini adalah grafik per hari ini tanggal $currentDate", "Grafik ini adalah grafik per tanggal ${currentDate.value}",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,

View File

@ -1,2 +1,119 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail.component package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.data.model.Dht
import com.syaroful.agrilinkvocpro.data.model.Npk
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
@Composable
fun DetailSensorData(
currentData: SensorDataResponse?,
sensorId: String
) {
val dataSensor = when (sensorId) {
"npk1" -> currentData?.data?.npk1
"npk2" -> currentData?.data?.npk2
"dht" -> currentData?.data?.dht
else -> null
}
if (dataSensor == null) {
Text("Data sensor kosong")
return
}
when (dataSensor) {
is Npk -> {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
SensorBar(
label = "Nitrogen",
value = dataSensor.soilNitrogen,
max = 50f,
icon = R.drawable.npk
)
SensorBar(
label = "Pospor",
value = dataSensor.soilPhosphorus,
max = 255f,
icon = R.drawable.npk
)
SensorBar(
label = "Kalium",
value = dataSensor.soilPotassium,
max = 255f,
icon = R.drawable.npk
)
SensorBar(
label = "Kelembaban",
value = dataSensor.soilHumidity,
max = 100f,
icon = R.drawable.soil_humidity
)
SensorBar(
label = "Suhu Tanah",
value = dataSensor.soilTemperature,
max = 50f,
icon = R.drawable.soil_temperature
)
SensorBar(
label = "PH Tanah",
value = dataSensor.soilPh,
max = 14f,
icon = R.drawable.meters
)
SensorBar(
label = "Konduktivitas",
value = dataSensor.soilConductivity,
max = 200f,
icon = R.drawable.electricity
)
}
}
is Dht -> {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
SensorBar(
label = "Kelembaban Udara",
value = dataSensor.viciHumidity,
max = 50f,
icon = R.drawable.humidity
)
SensorBar(
label = "Intensitas Cahaya",
value = dataSensor.viciLuminosity,
max = 255f,
icon = R.drawable.luminosity
)
SensorBar(
label = "Suhu Udara",
value = dataSensor.viciTemperature,
max = 255f,
icon = R.drawable.weather
)
}
}
else -> {}
}
}
@Composable
fun SensorBar(
label: String,
value: Number?,
max: Float,
icon: Int
) {
DataSensorBar(
label = label,
value = value ?: 0,
percentage = (value?.toFloat() ?: 0f) / max,
painter = painterResource(id = icon)
)
}

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail package com.syaroful.agrilinkvocpro.presentation.screen.detail.component
import android.graphics.Paint import android.graphics.Paint
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas

View File

@ -129,9 +129,9 @@ fun HomeScreen(
}, },
betNUmber = 1, betNUmber = 1,
commodity = "Kabocha", commodity = "Kabocha",
nitrogenValue = sensorData.npk1?.soilnitrogen.toString(), nitrogenValue = sensorData.npk1?.soilNitrogen.toString(),
phosphorValue = sensorData.npk1?.soilphosphorus.toString(), phosphorValue = sensorData.npk1?.soilPhosphorus.toString(),
potassiumValue = sensorData.npk1?.soilpotassium.toString(), potassiumValue = sensorData.npk1?.soilPotassium.toString(),
commodityImage = painterResource(id = R.drawable.kabocha) commodityImage = painterResource(id = R.drawable.kabocha)
) )
BetDataComponent( BetDataComponent(
@ -140,9 +140,9 @@ fun HomeScreen(
}, },
betNUmber = 2, betNUmber = 2,
commodity = "Melon", commodity = "Melon",
nitrogenValue = sensorData.npk2?.soilnitrogen.toString(), nitrogenValue = sensorData.npk2?.soilNitrogen.toString(),
phosphorValue = sensorData.npk2?.soilphosphorus.toString(), phosphorValue = sensorData.npk2?.soilPhosphorus.toString(),
potassiumValue = sensorData.npk2?.soilpotassium.toString(), potassiumValue = sensorData.npk2?.soilPotassium.toString(),
commodityImage = painterResource(id = R.drawable.melon) commodityImage = painterResource(id = R.drawable.melon)
) )
} }
@ -150,11 +150,11 @@ fun HomeScreen(
"DHT" -> { "DHT" -> {
DhtDataComponent( DhtDataComponent(
onClick = { onClick = {
navController.navigate("detail-screen/dht")
}, },
temperatureValue = sensorData.dht?.vicitemperature.toString(), temperatureValue = sensorData.dht?.viciTemperature.toString(),
humidityValue = sensorData.dht?.vicihumidity.toString(), humidityValue = sensorData.dht?.viciHumidity.toString(),
luminosityValue = sensorData.dht?.viciluminosity.toString(), luminosityValue = sensorData.dht?.viciLuminosity.toString(),
) )
} }

View File

@ -5,11 +5,13 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -47,8 +49,10 @@ fun RegisterScreen(
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column( Column(
modifier = Modifier modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth() .fillMaxWidth()
.padding(innerPadding), .padding(innerPadding)
.imePadding(),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Image( Image(
@ -69,14 +73,32 @@ fun RegisterScreen(
style = textTheme.titleSmall.copy(color = DarkGrey), style = textTheme.titleSmall.copy(color = DarkGrey),
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
AppTextField(
hint = "Name",
keyboardType = KeyboardType.Email,
leadingIcon = painterResource(
R.drawable.solar_user_circle
),
value = name,
onValueChange = { name = it }
)
AppTextField( AppTextField(
hint = "Username", hint = "Username",
keyboardType = KeyboardType.Email, keyboardType = KeyboardType.Email,
leadingIcon = painterResource(
R.drawable.solar_user
),
value = username,
onValueChange = { username = it }
)
AppTextField(
hint = "Email",
keyboardType = KeyboardType.Email,
leadingIcon = painterResource( leadingIcon = painterResource(
R.drawable.icon_email R.drawable.icon_email
), ),
value = username, value = email,
onValueChange = { username = it } onValueChange = { email = it }
) )
AppPasswordField( AppPasswordField(
hint = "Password", hint = "Password",
@ -84,30 +106,12 @@ fun RegisterScreen(
value = password, value = password,
onValueChange = { password = it } onValueChange = { password = it }
) )
AppButton( AppButton(
label = "Daftar", label = "Daftar",
) { ) {
} }
Spacer(modifier = Modifier.height(16.dp))
// Error Message
// if (loginState is ResultState.Error) {
// Text(
// modifier = Modifier.padding(16.dp),
// text = (loginState as ResultState.Error).message,
// color = Color.Red
// )
// }
// Navigate to Home if Success
// LaunchedEffect(loginState) {
// if (loginState is ResultState.Success<*>) {
// onLoginSuccess()
// }
// }
Spacer(modifier = Modifier.height(16.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center

View File

@ -0,0 +1,28 @@
package com.syaroful.agrilinkvocpro.presentation.screen.register
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.data.model.LoginResponse
import com.syaroful.agrilinkvocpro.data.repository.AuthRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class RegisterViewModel(
authRepository: AuthRepository,
) : ViewModel(){
private val _signupState = MutableStateFlow<ResultState<LoginResponse>>(ResultState.Idle)
val signupState: StateFlow<ResultState<LoginResponse>> = _signupState
fun signup(name: String, username: String, email: String, password: String){
_signupState.value = ResultState.Loading
viewModelScope.launch {
try {
}catch (e: Exception){
}
}
}
}

View File

@ -4,13 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize import com.syaroful.agrilinkvocpro.commodity_price_prediction_feature.presentation.prediction.PricePredictionScreen
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
class PricePredictionActivity : ComponentActivity() { class PricePredictionActivity : ComponentActivity() {
@ -19,30 +13,8 @@ class PricePredictionActivity : ComponentActivity() {
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
AgrilinkVocproTheme { AgrilinkVocproTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> PricePredictionScreen()
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
} }
} }
} }
} }
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
AgrilinkVocproTheme {
Greeting("Android")
}
}

View File

@ -24,7 +24,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.presentation.screen.detail.DynamicBottomSheet import com.syaroful.agrilinkvocpro.presentation.screen.detail.component.DynamicBottomSheet
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)

View File

@ -32,6 +32,7 @@ navigationCompose = "2.9.0"
okhttp = "4.12.0" okhttp = "4.12.0"
retrofit = "2.9.0" retrofit = "2.9.0"
retrofit2KotlinxSerializationConverter = "0.8.0" retrofit2KotlinxSerializationConverter = "0.8.0"
roomRuntime = "2.7.1"
runtime = "1.8.2" runtime = "1.8.2"
runtimeAndroid = "1.8.1" runtimeAndroid = "1.8.1"
ycharts = "2.1.0" ycharts = "2.1.0"
@ -48,6 +49,9 @@ androidx-datastore-preferences = { module = "androidx.datastore:datastore-prefer
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
androidx-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "runtime" } androidx-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "runtime" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" } converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
feature-delivery = { module = "com.google.android.play:feature-delivery", version.ref = "featureDelivery" } feature-delivery = { module = "com.google.android.play:feature-delivery", version.ref = "featureDelivery" }

View File

@ -4,13 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize import com.syaroful.agrilinkvocpro.growth_recipe_feature.presentation.recipe.GrowthRecipeScreen
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
class GrowthRecipeActivity : ComponentActivity() { class GrowthRecipeActivity : ComponentActivity() {
@ -19,29 +13,8 @@ class GrowthRecipeActivity : ComponentActivity() {
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
AgrilinkVocproTheme { AgrilinkVocproTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> GrowthRecipeScreen()
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
} }
} }
} }
} }
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
AgrilinkVocproTheme {
Greeting("Android")
}
}

View File

@ -25,7 +25,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.growth_recipe_feature.R import com.syaroful.agrilinkvocpro.growth_recipe_feature.R
import com.syaroful.agrilinkvocpro.presentation.screen.detail.LineChart import com.syaroful.agrilinkvocpro.presentation.screen.detail.component.LineChart
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)