feat: add download confirmation and progress dialogs
This commit is contained in:
parent
8e5e73bdf0
commit
40011a18d4
|
|
@ -35,7 +35,11 @@ fun DownloadProgressDialog(
|
||||||
) {
|
) {
|
||||||
Text(text = message)
|
Text(text = message)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
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 = {
|
confirmButton = {
|
||||||
|
|
|
||||||
|
|
@ -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.SplitInstallManager
|
||||||
import com.google.android.play.core.splitinstall.SplitInstallRequest
|
import com.google.android.play.core.splitinstall.SplitInstallRequest
|
||||||
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener
|
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class DynamicModuleRepository @Inject constructor(
|
class DynamicModuleRepository (
|
||||||
private val splitInstallManager: SplitInstallManager
|
private val splitInstallManager: SplitInstallManager
|
||||||
) {
|
) {
|
||||||
private var sessionId = 0
|
private var sessionId = 0
|
||||||
|
|
@ -17,7 +16,7 @@ class DynamicModuleRepository @Inject constructor(
|
||||||
|
|
||||||
fun startDownload(
|
fun startDownload(
|
||||||
moduleName: String,
|
moduleName: String,
|
||||||
listener: SplitInstallStateUpdatedListener
|
listener: SplitInstallStateUpdatedListener,
|
||||||
): Task<Int> {
|
): Task<Int> {
|
||||||
val request = SplitInstallRequest.newBuilder()
|
val request = SplitInstallRequest.newBuilder()
|
||||||
.addModule(moduleName)
|
.addModule(moduleName)
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,15 @@
|
||||||
package com.syaroful.agrilinkvocpro.di
|
package com.syaroful.agrilinkvocpro.di
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.google.android.play.core.splitinstall.SplitInstallManager
|
import com.google.android.play.core.splitinstall.SplitInstallManager
|
||||||
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory
|
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory
|
||||||
import dagger.Module
|
import com.syaroful.agrilinkvocpro.data.repository.DynamicModuleRepository
|
||||||
import dagger.Provides
|
import org.koin.android.ext.koin.androidContext
|
||||||
import dagger.hilt.InstallIn
|
import org.koin.dsl.module
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
val dynamicFeatureModule = module {
|
||||||
@InstallIn(SingletonComponent::class)
|
single<SplitInstallManager> {
|
||||||
object DynamicFeatureModule {
|
SplitInstallManagerFactory.create(androidContext())
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideSplitInstallManager(@ApplicationContext context: Context): SplitInstallManager {
|
|
||||||
return SplitInstallManagerFactory.create(context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
single { DynamicModuleRepository(get()) }
|
||||||
}
|
}
|
||||||
|
|
@ -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<String?>(null)
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var onLaunchCallback: ((String) -> Unit)? = null
|
||||||
|
|
||||||
|
|
||||||
|
private val _showProgressDialog = MutableStateFlow(false)
|
||||||
|
val showProgressDialog: StateFlow<Boolean> = _showProgressDialog
|
||||||
|
|
||||||
|
private val _progressMessage = MutableStateFlow("")
|
||||||
|
val progressMessage: StateFlow<String> = _progressMessage
|
||||||
|
|
||||||
|
private val _progressPercent = MutableStateFlow(0f)
|
||||||
|
val progressPercent: StateFlow<Float> = _progressPercent
|
||||||
|
|
||||||
|
fun setDialogVisibility(show: Boolean) {
|
||||||
|
_showProgressDialog.value = show
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.Idle)
|
||||||
|
private val downloadState: StateFlow<DownloadState> = _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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user