diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/DownloadProgressDialog.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/DownloadProgressDialog.kt index bdc2597..470f186 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/DownloadProgressDialog.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/DownloadProgressDialog.kt @@ -35,7 +35,11 @@ fun DownloadProgressDialog( ) { Text(text = message) Spacer(modifier = Modifier.height(16.dp)) - LinearProgressIndicator(color = MainGreen, trackColor = MainGreen.copy(alpha = 0.1f)) + LinearProgressIndicator( + progress = {progress}, + color = MainGreen, + trackColor = MainGreen.copy(alpha = 0.1f), + ) } }, confirmButton = { diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/repository/DynamicModuleRepository.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/repository/DynamicModuleRepository.kt index 4158f3a..74142de 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/repository/DynamicModuleRepository.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/data/repository/DynamicModuleRepository.kt @@ -4,9 +4,8 @@ import com.google.android.gms.tasks.Task import com.google.android.play.core.splitinstall.SplitInstallManager import com.google.android.play.core.splitinstall.SplitInstallRequest import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener -import javax.inject.Inject -class DynamicModuleRepository @Inject constructor( +class DynamicModuleRepository ( private val splitInstallManager: SplitInstallManager ) { private var sessionId = 0 @@ -17,7 +16,7 @@ class DynamicModuleRepository @Inject constructor( fun startDownload( moduleName: String, - listener: SplitInstallStateUpdatedListener + listener: SplitInstallStateUpdatedListener, ): Task { val request = SplitInstallRequest.newBuilder() .addModule(moduleName) diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/di/DynamicFeatureModule.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/di/DynamicFeatureModule.kt index 1a793cb..fec6f9f 100644 --- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/di/DynamicFeatureModule.kt +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/di/DynamicFeatureModule.kt @@ -1,22 +1,15 @@ package com.syaroful.agrilinkvocpro.di -import android.content.Context import com.google.android.play.core.splitinstall.SplitInstallManager import com.google.android.play.core.splitinstall.SplitInstallManagerFactory -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton +import com.syaroful.agrilinkvocpro.data.repository.DynamicModuleRepository +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module -@Module -@InstallIn(SingletonComponent::class) -object DynamicFeatureModule { - - @Provides - @Singleton - fun provideSplitInstallManager(@ApplicationContext context: Context): SplitInstallManager { - return SplitInstallManagerFactory.create(context) +val dynamicFeatureModule = module { + single { + SplitInstallManagerFactory.create(androidContext()) } + + single { DynamicModuleRepository(get()) } } \ No newline at end of file diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/DynamicModuleViewModel.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/DynamicModuleViewModel.kt new file mode 100644 index 0000000..0196c7b --- /dev/null +++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/DynamicModuleViewModel.kt @@ -0,0 +1,192 @@ +package com.syaroful.agrilinkvocpro.ui.screen.home + +import androidx.compose.runtime.mutableStateOf +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 kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class DynamicModuleViewModel( + private val dynamicModuleRepository: DynamicModuleRepository +) : ViewModel() { + + var currentModuleToDownload = mutableStateOf(null) + private set + + private var onLaunchCallback: ((String) -> Unit)? = null + + + private val _showProgressDialog = MutableStateFlow(false) + val showProgressDialog: StateFlow = _showProgressDialog + + private val _progressMessage = MutableStateFlow("") + val progressMessage: StateFlow = _progressMessage + + private val _progressPercent = MutableStateFlow(0f) + val progressPercent: StateFlow = _progressPercent + + fun setDialogVisibility(show: Boolean) { + _showProgressDialog.value = show + } + + private val _downloadState = MutableStateFlow(DownloadState.Idle) + private val downloadState: StateFlow = _downloadState.asStateFlow() + + init { + observeDownloadStatus() + } + + 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 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) { + onLaunchCallback = onLaunch + _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) + } + + } + + + private fun isModuleDownloaded(moduleName: String): Boolean { + return dynamicModuleRepository.isModuleDownloaded(moduleName) + } + + private fun observeDownloadStatus() { + viewModelScope.launch { + downloadState.collect { status -> + when (status) { + is DownloadState.Idle -> { + _progressMessage.value = "" + _progressPercent.value = 0f + } + + is DownloadState.Starting -> { + _showProgressDialog.value = true + _progressMessage.value = "Starting download..." + _progressPercent.value = 0f + } + + is DownloadState.Downloading -> { + _showProgressDialog.value = true + _progressMessage.value = "Downloading module..." + } + + is DownloadState.Downloaded -> { + _progressMessage.value = "Download completed" + _showProgressDialog.value = true + _progressPercent.value = 1f + } + + is DownloadState.Installed -> { + _progressMessage.value = "Install completed" + _progressPercent.value = 1f + delayAndDismissDialog() + } + + is DownloadState.Failed -> { + _progressMessage.value = "Failed: ${status.message}" + _showProgressDialog.value = true + _progressPercent.value = 0f + } + + is DownloadState.DownloadingWithProgress -> { + _showProgressDialog.value = true + _progressPercent.value = status.progress + } + } + } + } + } + + private fun delayAndDismissDialog() { + viewModelScope.launch { + delay(1500) + _showProgressDialog.value = false + currentModuleToDownload.value?.let { + onLaunchCallback?.invoke(it) + } + currentModuleToDownload.value = null + } + } + + private fun startFeatureActivity(moduleName: String, onLaunch: (String) -> Unit) { + val className = when (moduleName) { + "control_feature" -> "com.syaroful.agrilinkvocpro.control_feature.ControlActuatorActivity" + "growth_recipe_feature" -> "com.syaroful.agrilinkvocpro.growth_recipe_feature.GrowthRecipeActivity" + "commodity_price_prediction_feature" -> "com.syaroful.agrilinkvocpro.commodity_price_prediction_feature.PricePredictionActivity" + "plant_disease_detection_feature" -> "com.syaroful.agrilinkvocpro.plant_disease_detection_feature.PlantDiseaseDetectionActivity" + else -> null + } + className?.let { onLaunch(it) } + } + + override fun onCleared() { + super.onCleared() + dynamicModuleRepository.unregisterListener(listener) + } +} \ No newline at end of file