feat: refactor package

This commit is contained in:
Cutiful 2025-06-13 13:47:25 +07:00
parent 6b5010d37d
commit b0a3130cb9
41 changed files with 674 additions and 296 deletions

View File

@ -102,7 +102,7 @@ dependencies {
// pull to refresh // pull to refresh
implementation(libs.accompanist.swiperefresh) implementation(libs.accompanist.swiperefresh)
//graphic chat by Ycharts // placeholder or shimmer loading
implementation(libs.ycharts) implementation(libs.accompanist.placeholder.material)
} }

View File

@ -8,8 +8,8 @@ import androidx.activity.enableEdgeToEdge
import com.syaroful.agrilinkvocpro.core.components.DownloadModuleConfirmationDialog import com.syaroful.agrilinkvocpro.core.components.DownloadModuleConfirmationDialog
import com.syaroful.agrilinkvocpro.data.UserPreferences import com.syaroful.agrilinkvocpro.data.UserPreferences
import com.syaroful.agrilinkvocpro.navigation.SetupNavigation import com.syaroful.agrilinkvocpro.navigation.SetupNavigation
import com.syaroful.agrilinkvocpro.ui.screen.home.DynamicModuleViewModel import com.syaroful.agrilinkvocpro.presentation.screen.home.DynamicModuleViewModel
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel

View File

@ -16,7 +16,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@Composable @Composable

View File

@ -26,9 +26,9 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.ui.theme.DarkGrey import com.syaroful.agrilinkvocpro.presentation.theme.DarkGrey
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@Composable @Composable

View File

@ -21,8 +21,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)

View File

@ -14,7 +14,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@Composable @Composable
fun DownloadProgressDialog( fun DownloadProgressDialog(

View File

@ -22,7 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@Composable @Composable
fun MenuItemButton( fun MenuItemButton(

View File

@ -4,7 +4,7 @@ import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
val textTheme = Typography( val textTheme = Typography(
displayLarge = TextStyle( displayLarge = TextStyle(

View File

@ -0,0 +1,61 @@
package com.syaroful.agrilinkvocpro.core.placeholder
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
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,
cornerRadius: Dp = 16.dp
): Modifier = composed {
var size by remember { mutableStateOf(IntSize.Zero) }
val transition = rememberInfiniteTransition()
val xShimmer by transition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = shimmerDuration, easing = LinearEasing)
)
)
val brush = Brush.linearGradient(
colors = shimmerColors,
start = Offset.Zero,
end = Offset(
x = size.width * xShimmer,
y = size.height * xShimmer
)
)
this
.onGloballyPositioned { coordinates ->
size = coordinates.size
}
.clip(RoundedCornerShape(cornerRadius))
.background(brush)
}

View File

@ -0,0 +1,10 @@
package com.syaroful.agrilinkvocpro.core.utils.extention
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
fun Date.toFormattedString(): String {
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return sdf.format(this)
}

View File

@ -0,0 +1,19 @@
package com.syaroful.agrilinkvocpro.core.utils.extention
fun mapToUserFriendlyError(e: Exception): String {
return when (e) {
is java.net.UnknownHostException -> "Tidak dapat terhubung ke server. Periksa koneksi internet Anda."
is java.net.SocketTimeoutException -> "Waktu koneksi habis. Mohon coba lagi."
is java.io.IOException -> "Terjadi kesalahan jaringan. Silakan cek koneksi Anda."
is retrofit2.HttpException -> {
when (e.code()) {
401 -> "Akses ditolak. Silakan logout dan login kembali."
403 -> "Anda tidak memiliki izin untuk mengakses ini."
404 -> "Data tidak ditemukan."
500 -> "Terjadi kesalahan pada server. Silakan coba lagi nanti."
else -> "Terjadi kesalahan. Kode: ${e.code()}"
}
}
else -> "Terjadi kesalahan. Silakan coba lagi."
}
}

View File

@ -1,25 +1,11 @@
package com.syaroful.agrilinkvocpro.data.model package com.syaroful.agrilinkvocpro.data.model
data class Npk1GraphicDataResponse( data class NpkGraphicDataResponse(
val data: ListDataNpk1?, val data: Map<String, List<NpkWithHour>>?,
val statusCode: Int?, val statusCode: Int?,
val message: String?, val message: String?,
) )
data class ListDataNpk1(
val npk1: List<NpkWithHour>?,
)
data class Npk2GraphicDataResponse(
val data: ListDataNpk2?,
val statusCode: Int?,
val message: String?,
)
data class ListDataNpk2(
val npk1: List<NpkWithHour>?,
)
data class NpkWithHour( data class NpkWithHour(
val hour: Int?, val hour: Int?,
val date: String?, val date: String?,

View File

@ -1,12 +1,14 @@
package com.syaroful.agrilinkvocpro.data.network package com.syaroful.agrilinkvocpro.data.network
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.SensorDataResponse import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
import retrofit2.Response import retrofit2.Response
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Query
data class LoginRequest( data class LoginRequest(
@ -25,4 +27,14 @@ interface ApiService {
suspend fun getLatestSensorData( suspend fun getLatestSensorData(
@Header("Authorization") authHeader: String @Header("Authorization") authHeader: String
): Response<SensorDataResponse> ): Response<SensorDataResponse>
@GET("api/sensor/getData")
suspend fun getNpk1DataSensor(
@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<NpkGraphicDataResponse>
} }

View File

@ -1,12 +1,30 @@
package com.syaroful.agrilinkvocpro.data.repository package com.syaroful.agrilinkvocpro.data.repository
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
import retrofit2.Response import retrofit2.Response
class SensorDataRepository(private val apiService: ApiService) { class SensorDataRepository(private val apiService: ApiService) {
private var _latestSensorData: SensorDataResponse? = null
val latestSensorData: SensorDataResponse?
get() = _latestSensorData
suspend fun getLatestSensorData(authHeader: String): Response<SensorDataResponse> { suspend fun getLatestSensorData(authHeader: String): Response<SensorDataResponse> {
return apiService.getLatestSensorData(authHeader) val response = apiService.getLatestSensorData(authHeader)
if (response.isSuccessful) {
_latestSensorData = response.body()
}
return response
}
suspend fun getNpkDataSensor(
authHeader: String,
startDate: String,
endDate: String,
timeRange: String = "HOURLY",
sensor: String
): Response<NpkGraphicDataResponse> {
return apiService.getNpk1DataSensor(authHeader, startDate, endDate, timeRange, sensor)
} }
} }

View File

@ -1,9 +1,10 @@
package com.syaroful.agrilinkvocpro.di package com.syaroful.agrilinkvocpro.di
import com.syaroful.agrilinkvocpro.ui.screen.home.DynamicModuleViewModel import com.syaroful.agrilinkvocpro.presentation.screen.detail.DetailViewModel
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeViewModel import com.syaroful.agrilinkvocpro.presentation.screen.home.DynamicModuleViewModel
import com.syaroful.agrilinkvocpro.ui.screen.login.LoginViewModel import com.syaroful.agrilinkvocpro.presentation.screen.home.HomeViewModel
import com.syaroful.agrilinkvocpro.ui.screen.profile.ProfileViewModel import com.syaroful.agrilinkvocpro.presentation.screen.login.LoginViewModel
import com.syaroful.agrilinkvocpro.presentation.screen.profile.ProfileViewModel
import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
@ -12,4 +13,5 @@ val viewModelModule = module {
viewModel { LoginViewModel(get(), get()) } viewModel { LoginViewModel(get(), get()) }
viewModel { ProfileViewModel(get()) } viewModel { ProfileViewModel(get()) }
viewModel { HomeViewModel(get(), get()) } viewModel { HomeViewModel(get(), get()) }
viewModel { DetailViewModel(get(), get()) }
} }

View File

@ -1,18 +1,20 @@
package com.syaroful.agrilinkvocpro.navigation package com.syaroful.agrilinkvocpro.navigation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.syaroful.agrilinkvocpro.data.UserPreferences import com.syaroful.agrilinkvocpro.data.UserPreferences
import com.syaroful.agrilinkvocpro.ui.screen.detail.DetailScreen import com.syaroful.agrilinkvocpro.presentation.screen.detail.DetailScreen
import com.syaroful.agrilinkvocpro.ui.screen.home.DynamicModuleViewModel import com.syaroful.agrilinkvocpro.presentation.screen.home.DynamicModuleViewModel
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeScreen import com.syaroful.agrilinkvocpro.presentation.screen.home.HomeScreen
import com.syaroful.agrilinkvocpro.ui.screen.login.LoginScreen import com.syaroful.agrilinkvocpro.presentation.screen.login.LoginScreen
import com.syaroful.agrilinkvocpro.ui.screen.login.LoginViewModel import com.syaroful.agrilinkvocpro.presentation.screen.login.LoginViewModel
import com.syaroful.agrilinkvocpro.ui.screen.profile.ProfileScreen import com.syaroful.agrilinkvocpro.presentation.screen.profile.ProfileScreen
import com.syaroful.agrilinkvocpro.ui.screen.register.RegisterScreen import com.syaroful.agrilinkvocpro.presentation.screen.register.RegisterScreen
import com.syaroful.agrilinkvocpro.ui.screen.splash.SplashScreen import com.syaroful.agrilinkvocpro.presentation.screen.splash.SplashScreen
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@Composable @Composable
@ -83,8 +85,12 @@ fun SetupNavigation(
} }
) )
} }
composable("detail-screen") { composable(
DetailScreen() route = "detail-screen/{sensorId}",
arguments = listOf(navArgument("sensorId") { type = NavType.StringType })
) { backStackEntry ->
val sensorId = backStackEntry.arguments?.getString("sensorId") ?: "npk1"
DetailScreen(sensorId = sensorId)
} }
} }
} }

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.detail package com.syaroful.agrilinkvocpro.presentation.screen.detail
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -25,7 +25,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@Composable @Composable
fun DataSensorBar( fun DataSensorBar(
@ -95,7 +95,7 @@ private fun CustomLinearProgressIndicator(
fun DataSensorBarPreview() { fun DataSensorBarPreview() {
DataSensorBar( DataSensorBar(
label = "Nitrogen", label = "Nitrogen",
percentage = 0.5f, percentage = 20f,
painter = painterResource(id = R.drawable.npk), painter = painterResource(id = R.drawable.npk),
) )

View File

@ -0,0 +1,229 @@
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.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.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.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 org.koin.androidx.compose.koinViewModel
import java.text.SimpleDateFormat
import java.util.Date
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailScreen(
sensorId: String,
modifier: Modifier = Modifier,
detailViewModel: DetailViewModel = koinViewModel(),
) {
LaunchedEffect(sensorId) {
detailViewModel.fetchNpkData(sensorId)
}
val currentData = detailViewModel.currentSensorData
val sdf = SimpleDateFormat("dd MMMM yyyy", java.util.Locale.getDefault())
val currentDate = sdf.format(Date())
val options = listOf(
"Nitrogen",
"Pospor",
"Kalium",
"Suhu Tanah",
"PH Tanah",
"Kelembapan",
"Konduktivitas"
)
Scaffold(
topBar = {
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(
isRefreshing = isRefreshing.value,
onRefresh = {
isRefreshing.value = true
detailViewModel.fetchNpkData(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 {
Text("data Sesnor kosong")
}
}
}
}
}

View File

@ -0,0 +1,70 @@
package com.syaroful.agrilinkvocpro.presentation.screen.detail
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.core.utils.extention.mapToUserFriendlyError
import com.syaroful.agrilinkvocpro.core.utils.extention.toFormattedString
import com.syaroful.agrilinkvocpro.data.UserPreferences
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
import com.syaroful.agrilinkvocpro.data.repository.SensorDataRepository
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.util.Date
private const val TAG = "DetailViewModel"
class DetailViewModel(
private val sensorDataRepository: SensorDataRepository,
private val userPreferences: UserPreferences
) : ViewModel() {
val currentSensorData: SensorDataResponse?
get() = sensorDataRepository.latestSensorData
private val _npkDataState = MutableStateFlow<ResultState<NpkGraphicDataResponse>>(ResultState.Idle)
val npkDataState: StateFlow<ResultState<NpkGraphicDataResponse>> = _npkDataState.asStateFlow()
private val today = Date()
fun fetchNpkData(
sensor: String,
) {
viewModelScope.launch {
_npkDataState.value = ResultState.Loading
val token = userPreferences.tokenFlow.first()
val authHeader = "Bearer $token"
val formattedToday = today.toFormattedString()
try {
delay(2000L)
val response = sensorDataRepository.getNpkDataSensor(
authHeader = authHeader,
startDate = formattedToday,
endDate = formattedToday,
timeRange = "HOURLY",
sensor = sensor
)
if (response.isSuccessful) {
response.body()?.let { body ->
_npkDataState.value = ResultState.Success(body)
} ?: run {
_npkDataState.value = ResultState.Error("Data tidak ditemukan")
}
} else {
_npkDataState.value = ResultState.Error("Error: ${response.code()} - ${response.message()}")
}
} catch (e: Exception) {
val errorMessage = mapToUserFriendlyError(e)
_npkDataState.value = ResultState.Error(errorMessage)
Log.d(TAG, "Failed to fetch data: ${e.message}")
}
}
}
}

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.detail package com.syaroful.agrilinkvocpro.presentation.screen.detail
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.ui.screen.detail package com.syaroful.agrilinkvocpro.presentation.screen.detail
import android.graphics.Paint import android.graphics.Paint
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
@ -22,7 +22,7 @@ import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
import kotlin.math.round import kotlin.math.round
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -36,7 +36,7 @@ fun LineChart(
Box( Box(
modifier = Modifier modifier = Modifier
.background(color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(8.dp)) .background(color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(8.dp))
.padding(top = 20.dp) .padding(top = 24.dp)
.padding(8.dp) .padding(8.dp)
) { ) {
val spacing = 100f val spacing = 100f
@ -130,6 +130,21 @@ fun LineChart(
cap = StrokeCap.Round cap = StrokeCap.Round
) )
) )
val labelPadding = 12.dp.toPx()
drawContext.canvas.nativeCanvas.drawText(
"Jumlah",
spacing / 2f,
labelPadding - 80,
textPaint
)
drawContext.canvas.nativeCanvas.drawText(
"Jam",
size.width - labelPadding,
size.height - 4,
textPaint
)
} }
} }
} }

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.home package com.syaroful.agrilinkvocpro.presentation.screen.home
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel

View File

@ -1,7 +1,5 @@
package com.syaroful.agrilinkvocpro.ui.screen.home package com.syaroful.agrilinkvocpro.presentation.screen.home
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.util.Log
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
@ -21,8 +19,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -31,12 +27,11 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -44,22 +39,19 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
import com.syaroful.agrilinkvocpro.core.components.DownloadProgressDialog import com.syaroful.agrilinkvocpro.core.components.DownloadProgressDialog
import com.syaroful.agrilinkvocpro.core.components.MenuItemButton import com.syaroful.agrilinkvocpro.core.components.MenuItemButton
import com.syaroful.agrilinkvocpro.core.components.textTheme import com.syaroful.agrilinkvocpro.core.components.textTheme
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.data.model.SensorDataResponse import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import kotlin.math.log
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -76,6 +68,10 @@ fun HomeScreen(
val homeState by homeViewModel.homeState.collectAsState() val homeState by homeViewModel.homeState.collectAsState()
val isRefreshing = remember { mutableStateOf(false) } val isRefreshing = remember { mutableStateOf(false) }
val sensors = listOf("NPK", "DHT")
var selectedSensor by remember { mutableStateOf("NPK") }
AgrilinkVocproTheme { AgrilinkVocproTheme {
Scaffold { padding -> Scaffold { padding ->
PullToRefreshBox( PullToRefreshBox(
@ -98,20 +94,26 @@ fun HomeScreen(
onFeatureClick = onFeatureClick onFeatureClick = onFeatureClick
) )
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
Text( SensorTabBar(
modifier = Modifier sensors = sensors,
.padding(horizontal = 20.dp) selectedSensor = selectedSensor,
.fillMaxWidth(), onSensorSelected = { selectedSensor = it }
text = "Data Sensor Green House",
textAlign = TextAlign.Center,
style = textTheme.bodyMedium
) )
when (homeState) { when (homeState) {
is ResultState.Loading -> { is ResultState.Loading -> {
CircularProgressIndicator( Box(
modifier = Modifier modifier = Modifier
.padding(16.dp) .padding(16.dp)
.align(Alignment.CenterHorizontally) .fillMaxWidth()
.height(100.dp)
.shimmerEffect()
)
Box(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(100.dp)
.shimmerEffect()
) )
} }
@ -119,9 +121,11 @@ fun HomeScreen(
isRefreshing.value = false isRefreshing.value = false
val data = (homeState as ResultState.Success).data val data = (homeState as ResultState.Success).data
data?.data?.let { sensorData -> data?.data?.let { sensorData ->
when (selectedSensor) {
"NPK" -> {
BetDataComponent( BetDataComponent(
onClick = { onClick = {
navController.navigate("detail-screen") navController.navigate("detail-screen/npk1")
}, },
betNUmber = 1, betNUmber = 1,
commodity = "Kabocha", commodity = "Kabocha",
@ -132,7 +136,7 @@ fun HomeScreen(
) )
BetDataComponent( BetDataComponent(
onClick = { onClick = {
navController.navigate("detail-screen") navController.navigate("detail-screen/npk2")
}, },
betNUmber = 2, betNUmber = 2,
commodity = "Melon", commodity = "Melon",
@ -142,6 +146,20 @@ fun HomeScreen(
commodityImage = painterResource(id = R.drawable.melon) commodityImage = painterResource(id = R.drawable.melon)
) )
} }
"DHT" -> {
DhtDataComponent(
onClick = {
},
temperatureValue = sensorData.dht?.vicitemperature.toString(),
humidityValue = sensorData.dht?.vicihumidity.toString(),
luminosityValue = sensorData.dht?.viciluminosity.toString(),
)
}
}
}
} }
is ResultState.Error -> { is ResultState.Error -> {
@ -237,6 +255,50 @@ private fun BetDataComponent(
} }
} }
@Composable
private fun DhtDataComponent(
onClick: () -> Unit,
temperatureValue: String,
humidityValue: String,
luminosityValue: String,
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 20.dp, vertical = 8.dp)
.clip(shape = RoundedCornerShape(16.dp))
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.surfaceContainer,
)
.border(
width = 1.dp,
color = MainGreen.copy(alpha = 0.4f),
shape = RoundedCornerShape(16.dp)
)
.clickable { onClick() }
.padding(horizontal = 16.dp, vertical = 8.dp)
)
{
SensorDataItem(
icon = "Temp",
label = "Suhu\nGreen House",
value = temperatureValue
)
SensorDataItem(
icon = "Hum",
label = "Kelembaban\nUdara",
value = humidityValue
)
SensorDataItem(
icon = "Lux",
label = "Intensitas\ncahaya",
value = luminosityValue
)
}
}
@Composable @Composable
private fun SensorDataItem( private fun SensorDataItem(
icon: String = "-", icon: String = "-",
@ -245,7 +307,7 @@ private fun SensorDataItem(
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = icon, color = MainGreen, style = textTheme.headlineMedium) Text(text = icon, color = MainGreen, style = textTheme.headlineMedium)
Text(text = label, color = LightGrey, style = textTheme.bodySmall) Text(text = label, color = LightGrey, style = textTheme.bodySmall, textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text(text = value, style = textTheme.headlineSmall) Text(text = value, style = textTheme.headlineSmall)
} }

View File

@ -1,12 +1,14 @@
package com.syaroful.agrilinkvocpro.ui.screen.home package com.syaroful.agrilinkvocpro.presentation.screen.home
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.syaroful.agrilinkvocpro.core.utils.ResultState import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.core.utils.extention.mapToUserFriendlyError
import com.syaroful.agrilinkvocpro.data.UserPreferences import com.syaroful.agrilinkvocpro.data.UserPreferences
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
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -22,6 +24,9 @@ class HomeViewModel(
private val _homeState = MutableStateFlow<ResultState<SensorDataResponse>>(ResultState.Idle) private val _homeState = MutableStateFlow<ResultState<SensorDataResponse>>(ResultState.Idle)
val homeState: StateFlow<ResultState<SensorDataResponse>> = _homeState val homeState: StateFlow<ResultState<SensorDataResponse>> = _homeState
// var currentDataSensor: SensorDataResponse? = null
// private set
init { init {
getGreenHouseData() getGreenHouseData()
} }
@ -38,9 +43,12 @@ class HomeViewModel(
val token = userPreferences.tokenFlow.first() val token = userPreferences.tokenFlow.first()
val authHeader = "Bearer $token" val authHeader = "Bearer $token"
try { try {
delay(1000L)
val response = sensorDataRepository.getLatestSensorData(authHeader) val response = sensorDataRepository.getLatestSensorData(authHeader)
if (response.isSuccessful) { if (response.isSuccessful) {
_homeState.value = ResultState.Success(response.body()) val responseBody = response.body()
_homeState.value = ResultState.Success(responseBody)
// currentDataSensor = responseBody
Log.d(TAG, "Success to fetch data: ${response.body()} \nCurl response: ${response.raw()}") Log.d(TAG, "Success to fetch data: ${response.body()} \nCurl response: ${response.raw()}")
} else { } else {
val errorBody = response.errorBody()?.string() val errorBody = response.errorBody()?.string()
@ -50,7 +58,6 @@ class HomeViewModel(
.optString("message", "Failed to fetch data: ${response.code()}") .optString("message", "Failed to fetch data: ${response.code()}")
} ?: "Failed to fetch data: ${response.code()}" } ?: "Failed to fetch data: ${response.code()}"
_homeState.value = ResultState.Error(errorMessage) _homeState.value = ResultState.Error(errorMessage)
// Log.d(TAG, errorMessage) // Sudah di-log di atas
} }
} catch (e: Exception){ } catch (e: Exception){
val errorMessage = mapToUserFriendlyError(e) val errorMessage = mapToUserFriendlyError(e)
@ -59,21 +66,4 @@ class HomeViewModel(
} }
} }
} }
private fun mapToUserFriendlyError(e: Exception): String {
return when (e) {
is java.net.UnknownHostException -> "Tidak dapat terhubung ke server. Periksa koneksi internet Anda."
is java.net.SocketTimeoutException -> "Waktu koneksi habis. Mohon coba lagi."
is java.io.IOException -> "Terjadi kesalahan jaringan. Silakan cek koneksi Anda."
is retrofit2.HttpException -> {
when (e.code()) {
401 -> "Akses ditolak. Silakan logout dan login kembali."
403 -> "Anda tidak memiliki izin untuk mengakses ini."
404 -> "Data tidak ditemukan."
500 -> "Terjadi kesalahan pada server. Silakan coba lagi nanti."
else -> "Terjadi kesalahan. Kode: ${e.code()}"
}
}
else -> "Terjadi kesalahan. Silakan coba lagi."
}
}
} }

View File

@ -0,0 +1,52 @@
package com.syaroful.agrilinkvocpro.presentation.screen.home
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.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@Composable
fun SensorTabBar(
sensors: List<String>,
selectedSensor: String,
onSensorSelected: (String) -> Unit,
modifier: Modifier = Modifier
) {
Row(
horizontalArrangement = Arrangement.Center,
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp)
) {
sensors.forEach { sensor ->
val isSelected = sensor == selectedSensor
Box(
modifier = Modifier
.padding(horizontal = 4.dp)
.clip(RoundedCornerShape(50))
.background(if (isSelected) MainGreen else MaterialTheme.colorScheme.surfaceContainer)
.clickable { onSensorSelected(sensor) }
.padding(horizontal = 24.dp, vertical = 8.dp)
) {
Text(
text = sensor,
color = if (isSelected) Color.White else MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelMedium
)
}
}
}
}

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.login package com.syaroful.agrilinkvocpro.presentation.screen.login
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@ -36,9 +36,9 @@ import com.syaroful.agrilinkvocpro.core.components.AppPasswordField
import com.syaroful.agrilinkvocpro.core.components.AppTextField import com.syaroful.agrilinkvocpro.core.components.AppTextField
import com.syaroful.agrilinkvocpro.core.components.textTheme import com.syaroful.agrilinkvocpro.core.components.textTheme
import com.syaroful.agrilinkvocpro.core.utils.ResultState import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
import com.syaroful.agrilinkvocpro.ui.theme.DarkGrey import com.syaroful.agrilinkvocpro.presentation.theme.DarkGrey
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.login package com.syaroful.agrilinkvocpro.presentation.screen.login
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.profile package com.syaroful.agrilinkvocpro.presentation.screen.profile
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.border import androidx.compose.foundation.border
@ -38,8 +38,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.core.components.textTheme import com.syaroful.agrilinkvocpro.core.components.textTheme
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.profile package com.syaroful.agrilinkvocpro.presentation.screen.profile
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.register package com.syaroful.agrilinkvocpro.presentation.screen.register
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -29,8 +29,8 @@ import com.syaroful.agrilinkvocpro.core.components.AppButton
import com.syaroful.agrilinkvocpro.core.components.AppPasswordField import com.syaroful.agrilinkvocpro.core.components.AppPasswordField
import com.syaroful.agrilinkvocpro.core.components.AppTextField import com.syaroful.agrilinkvocpro.core.components.AppTextField
import com.syaroful.agrilinkvocpro.core.components.textTheme import com.syaroful.agrilinkvocpro.core.components.textTheme
import com.syaroful.agrilinkvocpro.ui.theme.DarkGrey import com.syaroful.agrilinkvocpro.presentation.theme.DarkGrey
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@Composable @Composable
fun RegisterScreen( fun RegisterScreen(

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.screen.splash package com.syaroful.agrilinkvocpro.presentation.screen.splash
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -20,8 +20,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.data.UserPreferences import com.syaroful.agrilinkvocpro.data.UserPreferences
import com.syaroful.agrilinkvocpro.ui.theme.DarkGreen import com.syaroful.agrilinkvocpro.presentation.theme.DarkGreen
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@Composable @Composable

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.theme package com.syaroful.agrilinkvocpro.presentation.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.theme package com.syaroful.agrilinkvocpro.presentation.theme
import android.os.Build import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.ui.theme package com.syaroful.agrilinkvocpro.presentation.theme
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle

View File

@ -1,155 +0,0 @@
package com.syaroful.agrilinkvocpro.ui.screen.detail
import androidx.compose.foundation.layout.Arrangement
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.ExperimentalMaterial3Api
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.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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.random.Random
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailScreen(
modifier: Modifier = Modifier
) {
val sdf = SimpleDateFormat("dd MMMM yyyy", java.util.Locale.getDefault())
val currentDate = sdf.format(Date())
val options = listOf(
"Nitrogen",
"Pospor",
"Kalium",
"Suhu Tanah",
"PH Tanah",
"Kelembapan",
"Konduktivitas"
)
Scaffold(
topBar = {
TopAppBar(
title = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(
"Detail Grafik",
style = MaterialTheme.typography.titleMedium
)
}
},
)
},
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.padding(top = 16.dp)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState())
) {
DynamicBottomSheet(
options = options
) {
}
Spacer(modifier = Modifier.height(16.dp))
LineChart(
hours = listOf(0, 1, 2, 3, 5, 6, 8, 10),
values = listOf(20.0, 22.0, 25.0, 23.0, 28.0, 30.0, 20.0, 22.0),
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)
)
Column(
verticalArrangement = Arrangement.spacedBy(
8.dp,
Alignment.Top
)
) {
DataSensorBar(
label = "Nitrogen",
value = Random.nextInt(0, 101),
painter = painterResource(id = R.drawable.npk),
percentage = 0.5f
)
DataSensorBar(
label = "Pospor",
value = Random.nextInt(0, 101),
painter = painterResource(id = R.drawable.npk),
percentage = 0.43f
)
DataSensorBar(
label = "Kalium",
value = Random.nextInt(0, 101),
painter = painterResource(id = R.drawable.npk),
percentage = 0.3f
)
DataSensorBar(
label = "Kelembaban",
value = Random.nextInt(0, 101),
painter = painterResource(id = R.drawable.soil_humidity),
percentage = 0.2f
)
DataSensorBar(
label = "Suhu Tanah",
value = Random.nextInt(0, 101),
painter = painterResource(id = R.drawable.soil_temperature),
percentage = 0.6f
)
DataSensorBar(
label = "PH Tanah",
value = Random.nextInt(0, 101),
painter = painterResource(id = R.drawable.meters),
percentage = 0.8f
)
DataSensorBar(
label = "Konduktivitas",
value = Random.nextInt(0, 101),
painter = painterResource(id = R.drawable.electricity),
percentage = 0.6f
)
}
}
}
}
@Preview
@Composable
fun PreviewDetailScreen() {
DetailScreen()
}

View File

@ -11,7 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
class PricePredictionActivity : ComponentActivity() { class PricePredictionActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -37,6 +37,7 @@ runtimeAndroid = "1.8.1"
ycharts = "2.1.0" ycharts = "2.1.0"
[libraries] [libraries]
accompanist-placeholder-material = { module = "com.google.accompanist:accompanist-placeholder-material", version.ref = "accompanistSwiperefresh" }
accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanistSwiperefresh" } accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanistSwiperefresh" }
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" }
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCore" } androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCore" }

View File

@ -11,7 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
class GrowthRecipeActivity : ComponentActivity() { class GrowthRecipeActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -13,7 +13,7 @@ import androidx.navigation.compose.rememberNavController
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.di.cameraModule import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.di.cameraModule
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.di.diagnosisModule import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.di.diagnosisModule
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.navigation.NavGraph import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.navigation.NavGraph
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
import org.koin.core.context.loadKoinModules import org.koin.core.context.loadKoinModules
import org.koin.core.context.unloadKoinModules import org.koin.core.context.unloadKoinModules

View File

@ -19,7 +19,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.R import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.R
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
@Composable @Composable
fun CustomCameraShutter( fun CustomCameraShutter(

View File

@ -40,7 +40,7 @@ import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.AppConstant import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.AppConstant
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera.CameraViewModel import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera.CameraViewModel
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)