Compare commits
No commits in common. "30c751be7a508fdd16ebab3bb0acde41b9824fe2" and "155dea7be7e6c96a548e504eae848a8a0973fc91" have entirely different histories.
30c751be7a
...
155dea7be7
|
|
@ -105,7 +105,4 @@ dependencies {
|
||||||
// placeholder or shimmer loading
|
// placeholder or shimmer loading
|
||||||
implementation(libs.accompanist.placeholder.material)
|
implementation(libs.accompanist.placeholder.material)
|
||||||
|
|
||||||
// Lottie Animation
|
|
||||||
implementation(libs.lottie.compose)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package com.syaroful.agrilinkvocpro.core.components
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import com.airbnb.lottie.compose.LottieAnimation
|
|
||||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
|
||||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
|
||||||
import com.syaroful.agrilinkvocpro.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Loader() {
|
|
||||||
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.loading))
|
|
||||||
LottieAnimation(
|
|
||||||
composition,
|
|
||||||
modifier = Modifier.fillMaxWidth(0.5f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -18,8 +18,6 @@ fun getValuesForSensorNpk(sensor: String, data: List<NpkWithHour>): List<Double>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun getValuesForSensorDht(sensor: String, data: List<DhtWithHour>): List<Double> {
|
fun getValuesForSensorDht(sensor: String, data: List<DhtWithHour>): List<Double> {
|
||||||
return when (sensor) {
|
return when (sensor) {
|
||||||
"Kelembaban Udara" -> data.mapNotNull { it.viciHumidity?.toDouble() }
|
"Kelembaban Udara" -> data.mapNotNull { it.viciHumidity?.toDouble() }
|
||||||
|
|
|
||||||
BIN
agrilinkvocpro/app/src/main/res/drawable/mascot_greet.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
agrilinkvocpro/app/src/main/res/drawable/mascot_lough.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
agrilinkvocpro/app/src/main/res/drawable/maskot_joy.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
|
@ -6,6 +6,9 @@ converterGson = "2.9.0"
|
||||||
datastorePreferences = "1.1.7"
|
datastorePreferences = "1.1.7"
|
||||||
featureDelivery = "2.1.0"
|
featureDelivery = "2.1.0"
|
||||||
firebaseBom = "33.13.0"
|
firebaseBom = "33.13.0"
|
||||||
|
hiltAndroid = "2.56.2"
|
||||||
|
hiltAndroidCompiler = "2.56.2"
|
||||||
|
hiltNavigationCompose = "1.2.0"
|
||||||
javaJwt = "4.4.0"
|
javaJwt = "4.4.0"
|
||||||
koinAndroid = "3.5.3"
|
koinAndroid = "3.5.3"
|
||||||
koinAndroidxCompose = "3.5.3"
|
koinAndroidxCompose = "3.5.3"
|
||||||
|
|
@ -14,13 +17,17 @@ coreKtx = "1.16.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
|
kotlinxSerializationJson = "1.6.3"
|
||||||
kotlinxSerializationJsonVersion = "1.6.0"
|
kotlinxSerializationJsonVersion = "1.6.0"
|
||||||
|
ktorClientCio = "2.3.3"
|
||||||
|
ktorClientContentNegotiation = "2.3.3"
|
||||||
|
ktorClientCore = "2.3.3"
|
||||||
|
ktorSerializationKotlinxJson = "2.3.3"
|
||||||
lifecycleRuntimeKtx = "2.9.0"
|
lifecycleRuntimeKtx = "2.9.0"
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
composeBom = "2025.05.00"
|
composeBom = "2025.05.00"
|
||||||
lifecycleViewmodelCompose = "2.9.0"
|
lifecycleViewmodelCompose = "2.9.0"
|
||||||
loggingInterceptor = "4.11.0"
|
loggingInterceptor = "4.11.0"
|
||||||
lottieCompose = "6.6.6"
|
|
||||||
navigationCompose = "2.9.0"
|
navigationCompose = "2.9.0"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
retrofit = "2.9.0"
|
retrofit = "2.9.0"
|
||||||
|
|
@ -28,6 +35,7 @@ retrofit2KotlinxSerializationConverter = "0.8.0"
|
||||||
roomRuntime = "2.7.1"
|
roomRuntime = "2.7.1"
|
||||||
runtime = "1.8.2"
|
runtime = "1.8.2"
|
||||||
runtimeAndroid = "1.8.1"
|
runtimeAndroid = "1.8.1"
|
||||||
|
ycharts = "2.1.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
accompanist-placeholder-material = { module = "com.google.accompanist:accompanist-placeholder-material", version.ref = "accompanistSwiperefresh" }
|
accompanist-placeholder-material = { module = "com.google.accompanist:accompanist-placeholder-material", version.ref = "accompanistSwiperefresh" }
|
||||||
|
|
@ -38,6 +46,7 @@ androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", versi
|
||||||
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" }
|
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||||
|
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
|
||||||
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
||||||
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
|
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
|
||||||
|
|
@ -66,12 +75,17 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
|
||||||
androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" }
|
androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" }
|
||||||
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koinAndroid" }
|
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koinAndroid" }
|
||||||
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koinAndroidxCompose" }
|
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koinAndroidxCompose" }
|
||||||
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||||
kotlinx-serialization-json-v160 = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJsonVersion" }
|
kotlinx-serialization-json-v160 = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJsonVersion" }
|
||||||
|
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktorClientCio" }
|
||||||
|
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorClientContentNegotiation" }
|
||||||
|
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCore" }
|
||||||
|
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorSerializationKotlinxJson" }
|
||||||
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" }
|
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" }
|
||||||
lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottieCompose" }
|
|
||||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" }
|
retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" }
|
||||||
|
ycharts = { module = "co.yml:ycharts", version.ref = "ycharts" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.di.appModule
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.di.appModule
|
||||||
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.di.clusterNetworkModule
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.di.networkModule
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.di.networkModule
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.di.viewModelModule
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.di.viewModelModule
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.naviagtion.SetupNavigation
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.naviagtion.SetupNavigation
|
||||||
|
|
@ -16,17 +17,18 @@ class GrowthRecipeActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
loadKoinModules(listOf(appModule, networkModule, viewModelModule))
|
loadKoinModules(listOf(appModule, networkModule, clusterNetworkModule, viewModelModule))
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
AgrilinkVocproTheme {
|
AgrilinkVocproTheme {
|
||||||
SetupNavigation()
|
SetupNavigation()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
unloadKoinModules(listOf(appModule, networkModule, viewModelModule))
|
unloadKoinModules(listOf(appModule, networkModule, clusterNetworkModule, viewModelModule))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package com.syaroful.agrilinkvocpro.growth_recipe_feature.core.utils
|
|
||||||
|
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.data.model.NpkWithDay
|
|
||||||
|
|
||||||
fun getValuesForSensorNpk(sensor: String, data: List<NpkWithDay>): List<Double> {
|
|
||||||
return when (sensor) {
|
|
||||||
"Nitrogen" -> data.mapNotNull { it.soilNitrogen?.toDouble() }
|
|
||||||
"Pospor" -> data.mapNotNull { it.soilPhosphorus?.toDouble() }
|
|
||||||
"Kalium" -> data.mapNotNull { it.soilPotassium?.toDouble() }
|
|
||||||
"Suhu Tanah" -> data.mapNotNull { it.soilTemperature?.toDouble() }
|
|
||||||
"PH Tanah" -> data.mapNotNull { it.soilPh?.toDouble() }
|
|
||||||
"Kelembapan" -> data.mapNotNull { it.soilHumidity?.toDouble() }
|
|
||||||
"Konduktivitas" -> data.mapNotNull { it.soilConductivity?.toDouble() }
|
|
||||||
else -> emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.growth_recipe_feature.di
|
||||||
|
|
||||||
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.data.network.ClusteringService
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
val clusterNetworkModule = module {
|
||||||
|
single {
|
||||||
|
OkHttpClient.Builder()
|
||||||
|
.connectTimeout(5, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
single {
|
||||||
|
Retrofit.Builder()
|
||||||
|
.baseUrl("http://labai.polinema.ac.id:5050/")
|
||||||
|
.client(get())
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
single<ClusteringService> { get<Retrofit>().create(ClusteringService::class.java) }
|
||||||
|
}
|
||||||
|
|
@ -1,57 +1,26 @@
|
||||||
package com.syaroful.agrilinkvocpro.growth_recipe_feature.di
|
package com.syaroful.agrilinkvocpro.growth_recipe_feature.di
|
||||||
|
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.data.network.ClusteringService
|
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.data.network.GrowthRecipeService
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.data.network.GrowthRecipeService
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.koin.core.qualifier.named
|
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
val CLUSTER_CLIENT = named("CLUSTER_CLIENT")
|
|
||||||
val CLUSTER_RETROFIT = named("CLUSTER_RETROFIT")
|
|
||||||
|
|
||||||
val RECIPE_CLIENT = named("RECIPE_CLIENT")
|
|
||||||
val RECIPE_RETROFIT = named("RECIPE_RETROFIT")
|
|
||||||
|
|
||||||
val networkModule = module {
|
val networkModule = module {
|
||||||
|
single {
|
||||||
// Cluster client and retrofit
|
|
||||||
single(CLUSTER_CLIENT) {
|
|
||||||
OkHttpClient.Builder()
|
|
||||||
.connectTimeout(5, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
single(CLUSTER_RETROFIT) {
|
|
||||||
Retrofit.Builder()
|
|
||||||
.baseUrl("http://labai.polinema.ac.id:5050/")
|
|
||||||
.client(get(CLUSTER_CLIENT))
|
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
single<ClusteringService> {
|
|
||||||
get<Retrofit>(CLUSTER_RETROFIT).create(ClusteringService::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Growth Recipe client and retrofit
|
|
||||||
single(RECIPE_CLIENT) {
|
|
||||||
OkHttpClient.Builder()
|
OkHttpClient.Builder()
|
||||||
.connectTimeout(2, TimeUnit.SECONDS)
|
.connectTimeout(2, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
single(RECIPE_RETROFIT) {
|
single {
|
||||||
Retrofit.Builder()
|
Retrofit.Builder()
|
||||||
.baseUrl("http://labai.polinema.ac.id:3042/")
|
.baseUrl("http://labai.polinema.ac.id:3042/")
|
||||||
.client(get(RECIPE_CLIENT))
|
.client(get())
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
single<GrowthRecipeService> {
|
single<GrowthRecipeService> { get<Retrofit>().create(GrowthRecipeService::class.java) }
|
||||||
get<Retrofit>(RECIPE_RETROFIT).create(GrowthRecipeService::class.java)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
package com.syaroful.agrilinkvocpro.growth_recipe_feature.di
|
|
||||||
|
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.presentation.recipe.GrowthRecipeViewModel
|
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.presentation.recomendation.ClusterViewModel
|
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val viewModelModule = module {
|
|
||||||
viewModel { GrowthRecipeViewModel(get(), get()) }
|
|
||||||
viewModel { ClusterViewModel(get()) }
|
|
||||||
}
|
|
||||||
|
|
@ -137,6 +137,7 @@ fun GrowthRecipeScreen(
|
||||||
}
|
}
|
||||||
when (graphicState) {
|
when (graphicState) {
|
||||||
is ResultState.Loading -> {
|
is ResultState.Loading -> {
|
||||||
|
isRefreshing.value = true
|
||||||
Row {
|
Row {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
private const val TAG = "GrowthRecipeViewModel"
|
private const val TAG = "GrowthRecipeViewModel"
|
||||||
|
|
||||||
class GrowthRecipeViewModel(
|
class GrowthRecipeViewModel(
|
||||||
private val userPreferences: UserPreferences,
|
private val userPreferences: UserPreferences,
|
||||||
private val growthRecipeRepository: GraphicDataRepository
|
private val growthRecipeRepository: GraphicDataRepository
|
||||||
|
|
@ -26,8 +25,7 @@ class GrowthRecipeViewModel(
|
||||||
|
|
||||||
private val _getGraphicState =
|
private val _getGraphicState =
|
||||||
MutableStateFlow<ResultState<NpkGraphicDayResponse>>(ResultState.Idle)
|
MutableStateFlow<ResultState<NpkGraphicDayResponse>>(ResultState.Idle)
|
||||||
val getGraphicState: StateFlow<ResultState<NpkGraphicDayResponse>> =
|
val getGraphicState: StateFlow<ResultState<NpkGraphicDayResponse>> = _getGraphicState.asStateFlow()
|
||||||
_getGraphicState.asStateFlow()
|
|
||||||
|
|
||||||
private val today = Date()
|
private val today = Date()
|
||||||
private val tenDaysAgo = Date(today.time - 9 * 24 * 60 * 60 * 1000)
|
private val tenDaysAgo = Date(today.time - 9 * 24 * 60 * 60 * 1000)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
|
@ -33,10 +34,10 @@ import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
|
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
|
||||||
import com.syaroful.agrilinkvocpro.core.components.Loader
|
|
||||||
import com.syaroful.agrilinkvocpro.core.utils.ResultState
|
import com.syaroful.agrilinkvocpro.core.utils.ResultState
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.R
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.R
|
||||||
import com.syaroful.agrilinkvocpro.growth_recipe_feature.core.component.ListItemClustering
|
import com.syaroful.agrilinkvocpro.growth_recipe_feature.core.component.ListItemClustering
|
||||||
|
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
|
@ -97,7 +98,9 @@ fun ClusterScreen(
|
||||||
) {
|
) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is ResultState.Loading -> {
|
is ResultState.Loading -> {
|
||||||
Loader()
|
CircularProgressIndicator(
|
||||||
|
color = MainGreen
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is ResultState.Error -> {
|
is ResultState.Error -> {
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 262 KiB |
|
|
@ -1,53 +0,0 @@
|
||||||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.component
|
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.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.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.R
|
|
||||||
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DiseaseDetectionBanner() {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(color = MainGreen, shape = RoundedCornerShape(8.dp))
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.fillMaxWidth(0.6f)) {
|
|
||||||
Text(
|
|
||||||
"Lindungi Tanamanmu dari Hama dan Penyakit",
|
|
||||||
style = MaterialTheme.typography.titleMedium.copy(color = Color.White)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
"Deteksi, lihat hasil dan lakukan perawatan",
|
|
||||||
style = MaterialTheme.typography.bodySmall.copy(Color.White.copy(alpha = 0.5f))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Image(
|
|
||||||
modifier = Modifier.fillMaxWidth(0.8f),
|
|
||||||
painter = painterResource(id = R.drawable.plant_in_pot),
|
|
||||||
contentDescription = "Plant Image",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention
|
|
||||||
|
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.PlantDiseaseDetectionResponse
|
|
||||||
|
|
||||||
sealed class DiagnosisResult {
|
|
||||||
data class Success(val data: PlantDiseaseDetectionResponse) : DiagnosisResult()
|
|
||||||
data class Error(val errorMessage: String) : DiagnosisResult()
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model
|
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
|
||||||
data class ErrorResponse(
|
|
||||||
val error: String? = null
|
|
||||||
)
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository
|
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository
|
||||||
|
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.DiagnosisResult
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.PlantDiseaseDetectionResponse
|
||||||
|
|
||||||
interface PlantDiagnosisRepository {
|
interface PlantDiagnosisRepository {
|
||||||
suspend fun detectDisease(base64Image: String, prompt: String): DiagnosisResult
|
suspend fun detectDisease(base64Image: String, prompt: String): PlantDiseaseDetectionResponse
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository
|
package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository
|
||||||
|
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.DiagnosisResult
|
import android.util.Log
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.ErrorResponse
|
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.GeminiContent
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.GeminiContent
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.GeminiPart
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.GeminiPart
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.GeminiRequest
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.GeminiRequest
|
||||||
|
|
@ -11,44 +10,32 @@ import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.network.
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
class PlantDiagnosisRepositoryImpl(private val api: GeminiApiService) : PlantDiagnosisRepository {
|
class PlantDiagnosisRepositoryImpl(private val api: GeminiApiService) : PlantDiagnosisRepository {
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
|
||||||
override suspend fun detectDisease(
|
override suspend fun detectDisease(
|
||||||
base64Image: String,
|
base64Image: String,
|
||||||
prompt: String
|
prompt: String
|
||||||
): DiagnosisResult {
|
): PlantDiseaseDetectionResponse {
|
||||||
return try {
|
val request = GeminiRequest(
|
||||||
val request = GeminiRequest(
|
contents = listOf(
|
||||||
contents = listOf(
|
GeminiContent(
|
||||||
GeminiContent(
|
parts = listOf(
|
||||||
parts = listOf(
|
GeminiPart(text = prompt),
|
||||||
GeminiPart(text = prompt),
|
GeminiPart(inline_data = InlineData("image/jpeg", base64Image))
|
||||||
GeminiPart(inline_data = InlineData("image/jpeg", base64Image))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val response = api.detectDisease(
|
)
|
||||||
apiKey = "AIzaSyDYZHlMfOdFAcTFptWHqhPwIO734VdNUWE",
|
|
||||||
request = request
|
|
||||||
)
|
|
||||||
|
|
||||||
val rawJson = response.candidates.first().content.parts.first().text
|
val response = api.detectDisease(
|
||||||
val cleanedJson = rawJson
|
apiKey = "AIzaSyDYZHlMfOdFAcTFptWHqhPwIO734VdNUWE", // Ideally use BuildConfig or secure storage
|
||||||
.replace("```json", "")
|
request = request
|
||||||
.replace("```", "")
|
)
|
||||||
.trim()
|
Log.d("PlantDiagnosisRepository", "Response: $response")
|
||||||
try {
|
|
||||||
val parsedSuccess =
|
val rawJson = response.candidates.first().content.parts.first().text
|
||||||
json.decodeFromString<PlantDiseaseDetectionResponse>(cleanedJson)
|
val cleanedJson = rawJson
|
||||||
DiagnosisResult.Success(parsedSuccess)
|
.replace("```json", "")
|
||||||
} catch (e: Exception) {
|
.replace("```", "")
|
||||||
val parsedError = json.decodeFromString<ErrorResponse>(cleanedJson)
|
.trim()
|
||||||
DiagnosisResult.Error(parsedError.error.toString())
|
return Json.decodeFromString(cleanedJson)
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
DiagnosisResult.Error("Terjadi kesalahan: ${e.localizedMessage ?: "tidak diketahui"}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
|
@ -40,9 +41,9 @@ import androidx.compose.ui.unit.dp
|
||||||
import com.syaroful.agrilinkvocpro.R
|
import com.syaroful.agrilinkvocpro.R
|
||||||
import com.syaroful.agrilinkvocpro.core.components.AppButton
|
import com.syaroful.agrilinkvocpro.core.components.AppButton
|
||||||
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
|
import com.syaroful.agrilinkvocpro.core.components.DefaultErrorComponent
|
||||||
import com.syaroful.agrilinkvocpro.core.components.Loader
|
|
||||||
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.presentation.theme.MainGreen
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -118,7 +119,7 @@ fun DetailScreen(
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
) {
|
) {
|
||||||
Loader()
|
CircularProgressIndicator(color = MainGreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,11 +221,7 @@ fun DetailScreen(
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(56.dp))
|
Spacer(modifier = Modifier.height(56.dp))
|
||||||
} else {
|
} else {
|
||||||
DefaultErrorComponent(
|
Text("Gagal parsing response")
|
||||||
painter = painterResource(id = R.drawable.mascot_surprised),
|
|
||||||
label = "Keknya bukan gambar daun",
|
|
||||||
message = "Gambar tidak menunjukkan daun tanaman, mohon unggah gambar daun yang jelas"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import android.graphics.Bitmap
|
||||||
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.plant_disease_detection_feature.core.extention.DiagnosisResult
|
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toBase64
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toBase64
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toByteArray
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toByteArray
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity.PlantDiagnosisEntity
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity.PlantDiagnosisEntity
|
||||||
|
|
@ -31,18 +30,9 @@ class PlantDiagnosisViewModel(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val base64 = bitmap.toBase64()
|
val base64 = bitmap.toBase64()
|
||||||
when (val result =
|
val result = repository.detectDisease(prompt = prompt, base64Image = base64)
|
||||||
repository.detectDisease(prompt = prompt, base64Image = base64)) {
|
_state.value = Result.success(result)
|
||||||
is DiagnosisResult.Success -> {
|
Log.d("PlantDiagnosisViewModel", "Diagnosis result: $result")
|
||||||
_state.value = Result.success(result.data)
|
|
||||||
Log.d("PlantDiagnosisViewModel", "Diagnosis result: ${result.data}")
|
|
||||||
}
|
|
||||||
|
|
||||||
is DiagnosisResult.Error -> {
|
|
||||||
_state.value = Result.failure(Exception(result.errorMessage))
|
|
||||||
Log.e("PlantDiagnosisViewModel", "Diagnosis error: ${result.errorMessage}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_state.value = Result.failure(e)
|
_state.value = Result.failure(e)
|
||||||
Log.e("PlantDiagnosisViewModel", "Error during diagnosis", e)
|
Log.e("PlantDiagnosisViewModel", "Error during diagnosis", e)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
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
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
@ -39,7 +40,6 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
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.plant_disease_detection_feature.core.component.DiseaseDetectionBanner
|
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toBitmap
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toBitmap
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toFormattedDate
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.toFormattedDate
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity.PlantDiagnosisEntity
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.local.entity.PlantDiagnosisEntity
|
||||||
|
|
@ -185,4 +185,34 @@ private fun DiagnosisListItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DiseaseDetectionBanner() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(color = MainGreen, shape = RoundedCornerShape(8.dp))
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth(0.6f)) {
|
||||||
|
Text(
|
||||||
|
"Lindungi Tanamanmu dari Hama dan Penyakit",
|
||||||
|
style = MaterialTheme.typography.titleMedium.copy(color = Color.White)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Deteksi, lihat hasil dan lakukan perawatan",
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(Color.White.copy(alpha = 0.5f))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.fillMaxWidth(0.8f),
|
||||||
|
painter = painterResource(id = R.drawable.plant_in_pot),
|
||||||
|
contentDescription = "Plant Image",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||