feat: implement history and save detection result to local database
This commit is contained in:
parent
5151379922
commit
287d5774e8
|
|
@ -11,6 +11,7 @@ import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.navigation.compose.rememberNavController
|
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.databaseModule
|
||||||
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.presentation.theme.AgrilinkVocproTheme
|
import com.syaroful.agrilinkvocpro.presentation.theme.AgrilinkVocproTheme
|
||||||
|
|
@ -21,7 +22,7 @@ class PlantDiseaseDetectionActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// load Module
|
// load Module
|
||||||
loadKoinModules(listOf(cameraModule, diagnosisModule))
|
loadKoinModules(listOf(cameraModule, diagnosisModule, databaseModule))
|
||||||
|
|
||||||
Log.d("KOIN_DEBUG", "✅ cameraModule loaded, yeay 🤗")
|
Log.d("KOIN_DEBUG", "✅ cameraModule loaded, yeay 🤗")
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ class PlantDiseaseDetectionActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
unloadKoinModules(listOf(cameraModule, diagnosisModule))
|
unloadKoinModules(listOf(cameraModule, diagnosisModule, databaseModule))
|
||||||
Log.d("KOIN_DEBUG", "🧹 cameraModule unloaded")
|
Log.d("KOIN_DEBUG", "🧹 cameraModule unloaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.network.
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.PlantDiagnosisRepository
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.PlantDiagnosisRepository
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.PlantDiagnosisRepositoryImpl
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.PlantDiagnosisRepositoryImpl
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.detail.PlantDiagnosisViewModel
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.detail.PlantDiagnosisViewModel
|
||||||
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history.HistoryViewModel
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
|
@ -43,5 +44,6 @@ val diagnosisModule = module {
|
||||||
get<Retrofit>().create(GeminiApiService::class.java)
|
get<Retrofit>().create(GeminiApiService::class.java)
|
||||||
}
|
}
|
||||||
single<PlantDiagnosisRepository> { PlantDiagnosisRepositoryImpl(get()) }
|
single<PlantDiagnosisRepository> { PlantDiagnosisRepositoryImpl(get()) }
|
||||||
viewModel { PlantDiagnosisViewModel(get()) }
|
viewModel { PlantDiagnosisViewModel(get(), get()) }
|
||||||
|
viewModel { HistoryViewModel(get()) }
|
||||||
}
|
}
|
||||||
|
|
@ -2,27 +2,48 @@ package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.navigation
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
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.navArgument
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera.CameraScreen
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera.CameraScreen
|
||||||
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.plant_disease_detection_feature.presentation.detail.DetailScreen
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.detail.DetailScreen
|
||||||
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history.DetailHistoryScreen
|
||||||
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history.HistoryScreen
|
||||||
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.history.HistoryViewModel
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NavGraph(navController: NavHostController) {
|
fun NavGraph(navController: NavHostController) {
|
||||||
val cameraViewModel: CameraViewModel = koinViewModel()
|
val cameraViewModel: CameraViewModel = koinViewModel()
|
||||||
|
val historyViewModel: HistoryViewModel = koinViewModel()
|
||||||
|
|
||||||
|
|
||||||
NavHost(navController, startDestination = "camera_screen") {
|
NavHost(navController, startDestination = "camera_screen") {
|
||||||
composable("camera_screen") {
|
composable("camera_screen") {
|
||||||
CameraScreen(navController, cameraViewModel)
|
CameraScreen(navController, cameraViewModel)
|
||||||
}
|
}
|
||||||
composable("detail_screen"){
|
composable("detail_screen") {
|
||||||
DetailScreen(
|
DetailScreen(
|
||||||
cameraViewModel = cameraViewModel,
|
cameraViewModel = cameraViewModel,
|
||||||
onBack = { navController.popBackStack() }
|
onBack = { navController.popBackStack() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable("history") {
|
||||||
|
HistoryScreen(navController = navController, viewModel = historyViewModel)
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
route = "detail-history/{id}",
|
||||||
|
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||||
|
) { backStackEntry ->
|
||||||
|
val id = backStackEntry.arguments?.getInt("id") ?: return@composable
|
||||||
|
|
||||||
|
DetailHistoryScreen(
|
||||||
|
navController = navController,
|
||||||
|
diagnosisId = id,
|
||||||
|
viewModel = historyViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
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.AppButton
|
||||||
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
|
||||||
|
|
@ -53,6 +54,7 @@ fun DetailScreen(
|
||||||
|
|
||||||
val bitmap by cameraViewModel.bitmap.collectAsState()
|
val bitmap by cameraViewModel.bitmap.collectAsState()
|
||||||
val diagnosisState by diagnosisViewModel.state.collectAsState()
|
val diagnosisState by diagnosisViewModel.state.collectAsState()
|
||||||
|
val saveLoading by diagnosisViewModel.saveLoadingState.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(bitmap) {
|
LaunchedEffect(bitmap) {
|
||||||
if (bitmap != null && diagnosisState == null) {
|
if (bitmap != null && diagnosisState == null) {
|
||||||
|
|
@ -101,6 +103,7 @@ fun DetailScreen(
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
bitmap = it.asImageBitmap(),
|
bitmap = it.asImageBitmap(),
|
||||||
contentDescription = "Gambar Tanaman",
|
contentDescription = "Gambar Tanaman",
|
||||||
contentScale = ContentScale.Crop, // Make image fill the Card
|
contentScale = ContentScale.Crop, // Make image fill the Card
|
||||||
|
|
@ -141,7 +144,6 @@ fun DetailScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (data?.diagnosis != null) {
|
} else if (data?.diagnosis != null) {
|
||||||
// Tampilkan detail diagnosis
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(
|
.background(
|
||||||
|
|
@ -213,6 +215,12 @@ fun DetailScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AppButton(
|
||||||
|
label = if (saveLoading) "Menyimpan..." else "Simpan Hasil Deteksi",
|
||||||
|
isEnable = !saveLoading
|
||||||
|
) {
|
||||||
|
diagnosisViewModel.saveResultToLocal(data, bitmap!!)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Text("Gagal parsing response")
|
Text("Gagal parsing response")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,26 @@ 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.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.data.local.entity.PlantDiagnosisEntity
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.PlantDiseaseDetectionResponse
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.model.PlantDiseaseDetectionResponse
|
||||||
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.PlantDiagnosisLocalRepository
|
||||||
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.PlantDiagnosisRepository
|
import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.PlantDiagnosisRepository
|
||||||
|
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.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class PlantDiagnosisViewModel(
|
class PlantDiagnosisViewModel(
|
||||||
private val repository: PlantDiagnosisRepository
|
private val repository: PlantDiagnosisRepository,
|
||||||
|
private val localRepository: PlantDiagnosisLocalRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _state = MutableStateFlow<Result<PlantDiseaseDetectionResponse>?>(null)
|
private val _state = MutableStateFlow<Result<PlantDiseaseDetectionResponse>?>(null)
|
||||||
val state: StateFlow<Result<PlantDiseaseDetectionResponse>?> = _state
|
val state: StateFlow<Result<PlantDiseaseDetectionResponse>?> = _state
|
||||||
|
|
||||||
|
private val _saveLoadingState = MutableStateFlow(false)
|
||||||
|
val saveLoadingState: StateFlow<Boolean> = _saveLoadingState
|
||||||
|
|
||||||
fun analyzePlant(bitmap: Bitmap, prompt: String) {
|
fun analyzePlant(bitmap: Bitmap, prompt: String) {
|
||||||
Log.d("PlantDiagnosisViewModel", "analyzePlant() called")
|
Log.d("PlantDiagnosisViewModel", "analyzePlant() called")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
@ -31,4 +39,23 @@ class PlantDiagnosisViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveResultToLocal(response: PlantDiseaseDetectionResponse, image: Bitmap) {
|
||||||
|
_saveLoadingState.value = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
delay(1000L)
|
||||||
|
val entity = PlantDiagnosisEntity(
|
||||||
|
diagnosis = response.diagnosis ?: "Unknown",
|
||||||
|
cause = response.cause ?: "-",
|
||||||
|
description = response.description ?: "-",
|
||||||
|
prevention = response.prevention?.joinToString(", ") ?: "-",
|
||||||
|
image = image.toByteArray(),
|
||||||
|
treatment = response.treatment?.joinToString(", ") {
|
||||||
|
"${it.method}: ${it.description}"
|
||||||
|
} ?: "-",
|
||||||
|
)
|
||||||
|
localRepository.saveDiagnosis(entity)
|
||||||
|
_saveLoadingState.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user