feat: refactor package
This commit is contained in:
parent
6b5010d37d
commit
b0a3130cb9
|
|
@ -102,7 +102,7 @@ dependencies {
|
|||
// pull to refresh
|
||||
implementation(libs.accompanist.swiperefresh)
|
||||
|
||||
//graphic chat by Ycharts
|
||||
implementation(libs.ycharts)
|
||||
// placeholder or shimmer loading
|
||||
implementation(libs.accompanist.placeholder.material)
|
||||
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import androidx.activity.enableEdgeToEdge
|
|||
import com.syaroful.agrilinkvocpro.core.components.DownloadModuleConfirmationDialog
|
||||
import com.syaroful.agrilinkvocpro.data.UserPreferences
|
||||
import com.syaroful.agrilinkvocpro.navigation.SetupNavigation
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.DynamicModuleViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.home.DynamicModuleViewModel
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.DarkGrey
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.DarkGrey
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
@Composable
|
||||
fun DownloadProgressDialog(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
@Composable
|
||||
fun MenuItemButton(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.compose.material3.Typography
|
|||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
|
||||
|
||||
val textTheme = Typography(
|
||||
displayLarge = TextStyle(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,11 @@
|
|||
package com.syaroful.agrilinkvocpro.data.model
|
||||
|
||||
data class Npk1GraphicDataResponse(
|
||||
val data: ListDataNpk1?,
|
||||
data class NpkGraphicDataResponse(
|
||||
val data: Map<String, List<NpkWithHour>>?,
|
||||
val statusCode: Int?,
|
||||
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(
|
||||
val hour: Int?,
|
||||
val date: String?,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package com.syaroful.agrilinkvocpro.data.network
|
||||
|
||||
import com.syaroful.agrilinkvocpro.data.model.LoginResponse
|
||||
import com.syaroful.agrilinkvocpro.data.model.NpkGraphicDataResponse
|
||||
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
data class LoginRequest(
|
||||
|
|
@ -25,4 +27,14 @@ interface ApiService {
|
|||
suspend fun getLatestSensorData(
|
||||
@Header("Authorization") authHeader: String
|
||||
): 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>
|
||||
|
||||
}
|
||||
|
|
@ -1,12 +1,30 @@
|
|||
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.network.ApiService
|
||||
import retrofit2.Response
|
||||
|
||||
class SensorDataRepository(private val apiService: ApiService) {
|
||||
private var _latestSensorData: SensorDataResponse? = null
|
||||
val latestSensorData: SensorDataResponse?
|
||||
get() = _latestSensorData
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package com.syaroful.agrilinkvocpro.di
|
||||
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.DynamicModuleViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.login.LoginViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.profile.ProfileViewModel
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.detail.DetailViewModel
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.home.DynamicModuleViewModel
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.home.HomeViewModel
|
||||
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.dsl.module
|
||||
|
||||
|
|
@ -12,4 +13,5 @@ val viewModelModule = module {
|
|||
viewModel { LoginViewModel(get(), get()) }
|
||||
viewModel { ProfileViewModel(get()) }
|
||||
viewModel { HomeViewModel(get(), get()) }
|
||||
viewModel { DetailViewModel(get(), get()) }
|
||||
}
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
package com.syaroful.agrilinkvocpro.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.syaroful.agrilinkvocpro.data.UserPreferences
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.detail.DetailScreen
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.DynamicModuleViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeScreen
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.login.LoginScreen
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.login.LoginViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.profile.ProfileScreen
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.register.RegisterScreen
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.splash.SplashScreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.detail.DetailScreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.home.DynamicModuleViewModel
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.home.HomeScreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.login.LoginScreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.login.LoginViewModel
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.profile.ProfileScreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.register.RegisterScreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.screen.splash.SplashScreen
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@Composable
|
||||
|
|
@ -83,8 +85,12 @@ fun SetupNavigation(
|
|||
}
|
||||
)
|
||||
}
|
||||
composable("detail-screen") {
|
||||
DetailScreen()
|
||||
composable(
|
||||
route = "detail-screen/{sensorId}",
|
||||
arguments = listOf(navArgument("sensorId") { type = NavType.StringType })
|
||||
) { backStackEntry ->
|
||||
val sensorId = backStackEntry.arguments?.getString("sensorId") ?: "npk1"
|
||||
DetailScreen(sensorId = sensorId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.background
|
||||
|
|
@ -25,7 +25,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
@Composable
|
||||
fun DataSensorBar(
|
||||
|
|
@ -95,7 +95,7 @@ private fun CustomLinearProgressIndicator(
|
|||
fun DataSensorBarPreview() {
|
||||
DataSensorBar(
|
||||
label = "Nitrogen",
|
||||
percentage = 0.5f,
|
||||
percentage = 20f,
|
||||
painter = painterResource(id = R.drawable.npk),
|
||||
|
||||
)
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.clickable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.syaroful.agrilinkvocpro.ui.screen.detail
|
||||
package com.syaroful.agrilinkvocpro.presentation.screen.detail
|
||||
|
||||
import android.graphics.Paint
|
||||
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.unit.dp
|
||||
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.roundToInt
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ fun LineChart(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(8.dp))
|
||||
.padding(top = 20.dp)
|
||||
.padding(top = 24.dp)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
val spacing = 100f
|
||||
|
|
@ -130,6 +130,21 @@ fun LineChart(
|
|||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.lifecycle.ViewModel
|
||||
|
|
@ -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.background
|
||||
import androidx.compose.foundation.border
|
||||
|
|
@ -21,8 +19,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
|
|
@ -31,12 +27,11 @@ import androidx.compose.material3.Scaffold
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -44,22 +39,19 @@ import androidx.compose.ui.graphics.painter.Painter
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
|
||||
import com.syaroful.agrilinkvocpro.core.components.DownloadProgressDialog
|
||||
import com.syaroful.agrilinkvocpro.core.components.MenuItemButton
|
||||
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.data.model.SensorDataResponse
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import kotlin.math.log
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -76,6 +68,10 @@ fun HomeScreen(
|
|||
|
||||
val homeState by homeViewModel.homeState.collectAsState()
|
||||
val isRefreshing = remember { mutableStateOf(false) }
|
||||
|
||||
val sensors = listOf("NPK", "DHT")
|
||||
var selectedSensor by remember { mutableStateOf("NPK") }
|
||||
|
||||
AgrilinkVocproTheme {
|
||||
Scaffold { padding ->
|
||||
PullToRefreshBox(
|
||||
|
|
@ -98,20 +94,26 @@ fun HomeScreen(
|
|||
onFeatureClick = onFeatureClick
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp)
|
||||
.fillMaxWidth(),
|
||||
text = "Data Sensor Green House",
|
||||
textAlign = TextAlign.Center,
|
||||
style = textTheme.bodyMedium
|
||||
SensorTabBar(
|
||||
sensors = sensors,
|
||||
selectedSensor = selectedSensor,
|
||||
onSensorSelected = { selectedSensor = it }
|
||||
)
|
||||
when (homeState) {
|
||||
is ResultState.Loading -> {
|
||||
CircularProgressIndicator(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.fillMaxWidth()
|
||||
.height(100.dp)
|
||||
.shimmerEffect()
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
.height(100.dp)
|
||||
.shimmerEffect()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -119,28 +121,44 @@ fun HomeScreen(
|
|||
isRefreshing.value = false
|
||||
val data = (homeState as ResultState.Success).data
|
||||
data?.data?.let { sensorData ->
|
||||
BetDataComponent(
|
||||
onClick = {
|
||||
navController.navigate("detail-screen")
|
||||
},
|
||||
betNUmber = 1,
|
||||
commodity = "Kabocha",
|
||||
nitrogenValue = sensorData.npk1?.soilnitrogen.toString(),
|
||||
phosphorValue = sensorData.npk1?.soilphosphorus.toString(),
|
||||
potassiumValue = sensorData.npk1?.soilpotassium.toString(),
|
||||
commodityImage = painterResource(id = R.drawable.kabocha)
|
||||
)
|
||||
BetDataComponent(
|
||||
onClick = {
|
||||
navController.navigate("detail-screen")
|
||||
},
|
||||
betNUmber = 2,
|
||||
commodity = "Melon",
|
||||
nitrogenValue = sensorData.npk2?.soilnitrogen.toString(),
|
||||
phosphorValue = sensorData.npk2?.soilphosphorus.toString(),
|
||||
potassiumValue = sensorData.npk2?.soilpotassium.toString(),
|
||||
commodityImage = painterResource(id = R.drawable.melon)
|
||||
)
|
||||
when (selectedSensor) {
|
||||
"NPK" -> {
|
||||
BetDataComponent(
|
||||
onClick = {
|
||||
navController.navigate("detail-screen/npk1")
|
||||
},
|
||||
betNUmber = 1,
|
||||
commodity = "Kabocha",
|
||||
nitrogenValue = sensorData.npk1?.soilnitrogen.toString(),
|
||||
phosphorValue = sensorData.npk1?.soilphosphorus.toString(),
|
||||
potassiumValue = sensorData.npk1?.soilpotassium.toString(),
|
||||
commodityImage = painterResource(id = R.drawable.kabocha)
|
||||
)
|
||||
BetDataComponent(
|
||||
onClick = {
|
||||
navController.navigate("detail-screen/npk2")
|
||||
},
|
||||
betNUmber = 2,
|
||||
commodity = "Melon",
|
||||
nitrogenValue = sensorData.npk2?.soilnitrogen.toString(),
|
||||
phosphorValue = sensorData.npk2?.soilphosphorus.toString(),
|
||||
potassiumValue = sensorData.npk2?.soilpotassium.toString(),
|
||||
commodityImage = painterResource(id = R.drawable.melon)
|
||||
)
|
||||
}
|
||||
|
||||
"DHT" -> {
|
||||
DhtDataComponent(
|
||||
onClick = {
|
||||
|
||||
},
|
||||
temperatureValue = sensorData.dht?.vicitemperature.toString(),
|
||||
humidityValue = sensorData.dht?.vicihumidity.toString(),
|
||||
luminosityValue = sensorData.dht?.viciluminosity.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
private fun SensorDataItem(
|
||||
icon: String = "-",
|
||||
|
|
@ -245,7 +307,7 @@ private fun SensorDataItem(
|
|||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
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))
|
||||
Text(text = value, style = textTheme.headlineSmall)
|
||||
}
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
package com.syaroful.agrilinkvocpro.ui.screen.home
|
||||
package com.syaroful.agrilinkvocpro.presentation.screen.home
|
||||
|
||||
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.data.UserPreferences
|
||||
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.first
|
||||
|
|
@ -22,6 +24,9 @@ class HomeViewModel(
|
|||
private val _homeState = MutableStateFlow<ResultState<SensorDataResponse>>(ResultState.Idle)
|
||||
val homeState: StateFlow<ResultState<SensorDataResponse>> = _homeState
|
||||
|
||||
// var currentDataSensor: SensorDataResponse? = null
|
||||
// private set
|
||||
|
||||
init {
|
||||
getGreenHouseData()
|
||||
}
|
||||
|
|
@ -38,9 +43,12 @@ class HomeViewModel(
|
|||
val token = userPreferences.tokenFlow.first()
|
||||
val authHeader = "Bearer $token"
|
||||
try {
|
||||
delay(1000L)
|
||||
val response = sensorDataRepository.getLatestSensorData(authHeader)
|
||||
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()}")
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string()
|
||||
|
|
@ -50,7 +58,6 @@ class HomeViewModel(
|
|||
.optString("message", "Failed to fetch data: ${response.code()}")
|
||||
} ?: "Failed to fetch data: ${response.code()}"
|
||||
_homeState.value = ResultState.Error(errorMessage)
|
||||
// Log.d(TAG, errorMessage) // Sudah di-log di atas
|
||||
}
|
||||
} catch (e: Exception){
|
||||
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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 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.textTheme
|
||||
import com.syaroful.agrilinkvocpro.core.utils.ResultState
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.DarkGrey
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.DarkGrey
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
|
||||
|
|
@ -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.viewModelScope
|
||||
|
|
@ -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.border
|
||||
|
|
@ -38,8 +38,8 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
import com.syaroful.agrilinkvocpro.core.components.textTheme
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.syaroful.agrilinkvocpro.ui.screen.profile
|
||||
package com.syaroful.agrilinkvocpro.presentation.screen.profile
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
|
@ -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.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.AppTextField
|
||||
import com.syaroful.agrilinkvocpro.core.components.textTheme
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.DarkGrey
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.DarkGrey
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
@Composable
|
||||
fun RegisterScreen(
|
||||
|
|
@ -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.background
|
||||
|
|
@ -20,8 +20,8 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
import com.syaroful.agrilinkvocpro.data.UserPreferences
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.DarkGreen
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.DarkGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.syaroful.agrilinkvocpro.ui.theme
|
||||
package com.syaroful.agrilinkvocpro.presentation.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.syaroful.agrilinkvocpro.ui.theme
|
||||
package com.syaroful.agrilinkvocpro.presentation.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.syaroful.agrilinkvocpro.ui.theme
|
||||
package com.syaroful.agrilinkvocpro.presentation.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ 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.ui.theme.AgrilinkVocproTheme
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
|
||||
|
||||
class PricePredictionActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ runtimeAndroid = "1.8.1"
|
|||
ycharts = "2.1.0"
|
||||
|
||||
[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" }
|
||||
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" }
|
||||
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCore" }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ 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.ui.theme.AgrilinkVocproTheme
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
|
||||
|
||||
class GrowthRecipeActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
|
|||
|
|
@ -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.diagnosisModule
|
||||
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.unloadKoinModules
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.R
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
|
||||
@Composable
|
||||
fun CustomCameraShutter(
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import com.syaroful.agrilinkvocpro.R
|
|||
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.presentation.camera.CameraViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user