feat: implement sensor data fetching in HomeViewModel

This commit is contained in:
Cutiful 2025-06-12 08:42:02 +07:00
parent c2fbd6fa25
commit 6111391bed

View File

@ -1,184 +1,79 @@
package com.syaroful.agrilinkvocpro.ui.screen.home package com.syaroful.agrilinkvocpro.ui.screen.home
import androidx.compose.runtime.mutableFloatStateOf import android.util.Log
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.android.play.core.splitinstall.SplitInstallException import com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener import com.syaroful.agrilinkvocpro.data.UserPreferences
import com.google.android.play.core.splitinstall.model.SplitInstallErrorCode import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus import com.syaroful.agrilinkvocpro.data.repository.SensorDataRepository
import com.syaroful.agrilinkvocpro.core.utils.DownloadState
import com.syaroful.agrilinkvocpro.data.repository.DynamicModuleRepository
import dagger.hilt.android.lifecycle.HiltViewModel
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.asStateFlow import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor( private const val TAG = "HomeViewModel"
private val dynamicModuleRepository: DynamicModuleRepository
class HomeViewModel(
private val sensorDataRepository: SensorDataRepository,
private val userPreferences: UserPreferences
) : ViewModel() { ) : ViewModel() {
private val _homeState = MutableStateFlow<ResultState<SensorDataResponse>>(ResultState.Idle)
val homeState: StateFlow<ResultState<SensorDataResponse>> = _homeState
var currentModuleToDownload = mutableStateOf<String?>(null) init {
private set getGreenHouseData()
var showProgressDialog = mutableStateOf(false)
private set
var progressMessage = mutableStateOf("")
private set
var progressPercent = mutableFloatStateOf(0f)
private set
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.Idle)
private val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()
private val listener = SplitInstallStateUpdatedListener { state ->
if (state.sessionId() == dynamicModuleRepository.getSessionId()) {
when (state.status()) {
SplitInstallSessionStatus.DOWNLOADING -> {
val progress = if (state.totalBytesToDownload() > 0)
state.bytesDownloaded().toFloat() / state.totalBytesToDownload()
else 0f
_downloadState.value = DownloadState.DownloadingWithProgress(progress)
}
SplitInstallSessionStatus.DOWNLOADED -> {
_downloadState.value = DownloadState.Downloaded
}
SplitInstallSessionStatus.INSTALLED -> {
_downloadState.value = DownloadState.Installed
}
SplitInstallSessionStatus.FAILED -> {
_downloadState.value = DownloadState.Failed("Installation Failed")
}
SplitInstallSessionStatus.CANCELED -> {
_downloadState.value = DownloadState.Failed("Installation Canceled")
}
else -> {}
}
}
} }
fun printUserToken(){
fun onFeatureClicked(moduleName: String, onLaunch: (String) -> Unit) {
if (isModuleDownloaded(moduleName)) {
startFeatureActivity(moduleName, onLaunch)
} else {
currentModuleToDownload.value = moduleName
}
}
fun confirmDownload(onLaunch: (String) -> Unit) {
currentModuleToDownload.value?.let {
downloadDynamicModule(it, onLaunch)
}
currentModuleToDownload.value = null
}
private fun downloadDynamicModule(moduleName: String, onLaunch: (String) -> Unit) {
_downloadState.value = DownloadState.Starting
dynamicModuleRepository.startDownload(moduleName, listener)
.addOnFailureListener { exception ->
val errorCode = (exception as? SplitInstallException)?.errorCode ?: -1
val errorMessage = when (errorCode) {
SplitInstallErrorCode.NETWORK_ERROR -> "No Internet Connection"
SplitInstallErrorCode.MODULE_UNAVAILABLE -> "Module Unavailable"
SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> "Active Session Limit Exceeded"
SplitInstallErrorCode.INSUFFICIENT_STORAGE -> "Insufficient Storage"
SplitInstallErrorCode.PLAY_STORE_NOT_FOUND -> "Play Store Not Found"
else -> "Unknown Error: $errorCode"
}
_downloadState.value = DownloadState.Failed(errorMessage)
}
observeDownloadStatus(onLaunch)
}
private fun isModuleDownloaded(moduleName: String): Boolean {
return dynamicModuleRepository.isModuleDownloaded(moduleName)
}
private fun observeDownloadStatus(onLaunch: (String) -> Unit) {
viewModelScope.launch { viewModelScope.launch {
downloadState.collect { status -> val token = userPreferences.tokenFlow.first()
when (status) { Log.d(TAG, "Token: $token")
is DownloadState.Idle -> { }
showProgressDialog.value = false }
progressMessage.value = ""
progressPercent.floatValue = 0f
}
is DownloadState.Starting -> { fun getGreenHouseData(){
showProgressDialog.value = true _homeState.value = ResultState.Loading
progressMessage.value = "Starting download..." viewModelScope.launch {
progressPercent.floatValue = 0f val token = userPreferences.tokenFlow.first()
} val authHeader = "Bearer $token"
try {
is DownloadState.Downloading -> { val response = sensorDataRepository.getLatestSensorData(authHeader)
showProgressDialog.value = true if (response.isSuccessful) {
progressMessage.value = "Downloading module..." _homeState.value = ResultState.Success(response.body())
} Log.d(TAG, "Success to fetch data: ${response.body()} \nCurl response: ${response.raw()}")
} else {
is DownloadState.Downloaded -> { val errorBody = response.errorBody()?.string()
progressMessage.value = "Download completed" Log.d(TAG, "Failed to fetch data. Code: ${response.code()}, Error Body: $errorBody \nCurl response: ${response.raw()}")
progressPercent.floatValue = 1f val errorMessage = errorBody?.let {
} org.json.JSONObject(it)
.optString("message", "Failed to fetch data: ${response.code()}")
is DownloadState.Installed -> { } ?: "Failed to fetch data: ${response.code()}"
progressMessage.value = "Install completed" _homeState.value = ResultState.Error(errorMessage)
progressPercent.floatValue = 1f // Log.d(TAG, errorMessage) // Sudah di-log di atas
delayAndDismissDialog(onLaunch) }
} } catch (e: Exception){
val errorMessage = mapToUserFriendlyError(e)
is DownloadState.Failed -> { _homeState.value = ResultState.Error(errorMessage)
progressMessage.value = "Failed: ${status.message}" Log.d(TAG, "Failed to fetch data: ${e.message}")
showProgressDialog.value = true }
progressPercent.floatValue = 0f }
} }
private fun mapToUserFriendlyError(e: Exception): String {
is DownloadState.DownloadingWithProgress -> { return when (e) {
showProgressDialog.value = true is java.net.UnknownHostException -> "Tidak dapat terhubung ke server. Periksa koneksi internet Anda."
progressPercent.floatValue = status.progress 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."
} }
} }
private fun delayAndDismissDialog(onLaunch: (String) -> Unit) {
viewModelScope.launch {
delay(1500)
showProgressDialog.value = false
currentModuleToDownload.value?.let {
startFeatureActivity(it, onLaunch)
}
currentModuleToDownload.value = null
}
}
private fun startFeatureActivity(moduleName: String, onLaunch: (String) -> Unit) {
val className = when (moduleName) {
"control_feature" -> "com.syaroful.agrilinkvocpro.control_feature.ControlActuatorActivity"
"recipe_feature" -> "com.syaroful.agrilinkvocpro.recipe_feature.RecipeActivity"
"price_prediction_feature" -> "com.syaroful.agrilinkvocpro.price_prediction_feature.PricePredictionActivity"
"plant_disease_detection_feature" -> "com.syaroful.agrilinkvocpro.diseasedetection_feature.PlantDiseaseDetectionActivity"
else -> null
}
className?.let { onLaunch(it) }
}
override fun onCleared() {
super.onCleared()
dynamicModuleRepository.unregisterListener(listener)
}
} }