feat: refactor package
This commit is contained in:
parent
6b5010d37d
commit
b0a3130cb9
|
|
@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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
|
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?,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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()) }
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
@ -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.border
|
||||||
import androidx.compose.foundation.clickable
|
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 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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
@ -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."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -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 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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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(
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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.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?) {
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
|
|
@ -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?) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user