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
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.android.play.core.splitinstall.SplitInstallException
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener
import com.google.android.play.core.splitinstall.model.SplitInstallErrorCode
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
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 com.syaroful.agrilinkvocpro.core.utils.ResultState
import com.syaroful.agrilinkvocpro.data.UserPreferences
import com.syaroful.agrilinkvocpro.data.model.SensorDataResponse
import com.syaroful.agrilinkvocpro.data.repository.SensorDataRepository
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 javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val dynamicModuleRepository: DynamicModuleRepository
private const val TAG = "HomeViewModel"
class HomeViewModel(
private val sensorDataRepository: SensorDataRepository,
private val userPreferences: UserPreferences
) : ViewModel() {
private val _homeState = MutableStateFlow<ResultState<SensorDataResponse>>(ResultState.Idle)
val homeState: StateFlow<ResultState<SensorDataResponse>> = _homeState
var currentModuleToDownload = mutableStateOf<String?>(null)
private set
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 -> {}
}
}
init {
getGreenHouseData()
}
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) {
fun printUserToken(){
viewModelScope.launch {
downloadState.collect { status ->
when (status) {
is DownloadState.Idle -> {
showProgressDialog.value = false
progressMessage.value = ""
progressPercent.floatValue = 0f
}
val token = userPreferences.tokenFlow.first()
Log.d(TAG, "Token: $token")
}
}
is DownloadState.Starting -> {
showProgressDialog.value = true
progressMessage.value = "Starting download..."
progressPercent.floatValue = 0f
}
is DownloadState.Downloading -> {
showProgressDialog.value = true
progressMessage.value = "Downloading module..."
}
is DownloadState.Downloaded -> {
progressMessage.value = "Download completed"
progressPercent.floatValue = 1f
}
is DownloadState.Installed -> {
progressMessage.value = "Install completed"
progressPercent.floatValue = 1f
delayAndDismissDialog(onLaunch)
}
is DownloadState.Failed -> {
progressMessage.value = "Failed: ${status.message}"
showProgressDialog.value = true
progressPercent.floatValue = 0f
}
is DownloadState.DownloadingWithProgress -> {
showProgressDialog.value = true
progressPercent.floatValue = status.progress
}
fun getGreenHouseData(){
_homeState.value = ResultState.Loading
viewModelScope.launch {
val token = userPreferences.tokenFlow.first()
val authHeader = "Bearer $token"
try {
val response = sensorDataRepository.getLatestSensorData(authHeader)
if (response.isSuccessful) {
_homeState.value = ResultState.Success(response.body())
Log.d(TAG, "Success to fetch data: ${response.body()} \nCurl response: ${response.raw()}")
} else {
val errorBody = response.errorBody()?.string()
Log.d(TAG, "Failed to fetch data. Code: ${response.code()}, Error Body: $errorBody \nCurl response: ${response.raw()}")
val errorMessage = errorBody?.let {
org.json.JSONObject(it)
.optString("message", "Failed to fetch data: ${response.code()}")
} ?: "Failed to fetch data: ${response.code()}"
_homeState.value = ResultState.Error(errorMessage)
// Log.d(TAG, errorMessage) // Sudah di-log di atas
}
} catch (e: Exception){
val errorMessage = mapToUserFriendlyError(e)
_homeState.value = ResultState.Error(errorMessage)
Log.d(TAG, "Failed to fetch data: ${e.message}")
}
}
}
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."
}
}
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)
}
}