compose: details: Simplify and fix showing app states

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-05-22 23:57:49 +08:00
parent 4c479e060b
commit cb216f6c01
11 changed files with 166 additions and 138 deletions

View File

@@ -73,9 +73,9 @@ import com.aurora.store.compose.ui.details.components.AppTags
import com.aurora.store.compose.ui.details.components.AppTesting import com.aurora.store.compose.ui.details.components.AppTesting
import com.aurora.store.compose.ui.dev.DevProfileScreen import com.aurora.store.compose.ui.dev.DevProfileScreen
import com.aurora.store.data.installer.AppInstaller import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.AppState
import com.aurora.store.data.model.Report import com.aurora.store.data.model.Report
import com.aurora.store.data.model.Scores import com.aurora.store.data.model.Scores
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.PackageUtil import com.aurora.store.util.PackageUtil
import com.aurora.store.util.PackageUtil.PACKAGE_NAME_GMS import com.aurora.store.util.PackageUtil.PACKAGE_NAME_GMS
import com.aurora.store.util.ShortcutManagerUtil import com.aurora.store.util.ShortcutManagerUtil
@@ -94,13 +94,12 @@ fun AppDetailsScreen(
val context = LocalContext.current val context = LocalContext.current
val app by viewModel.app.collectAsStateWithLifecycle() val app by viewModel.app.collectAsStateWithLifecycle()
val state by viewModel.state.collectAsStateWithLifecycle()
val featuredReviews by viewModel.featuredReviews.collectAsStateWithLifecycle() val featuredReviews by viewModel.featuredReviews.collectAsStateWithLifecycle()
val favorite by viewModel.favourite.collectAsStateWithLifecycle() val favorite by viewModel.favourite.collectAsStateWithLifecycle()
val exodusReport by viewModel.exodusReport.collectAsStateWithLifecycle() val exodusReport by viewModel.exodusReport.collectAsStateWithLifecycle()
val dataSafetyReport by viewModel.dataSafetyReport.collectAsStateWithLifecycle() val dataSafetyReport by viewModel.dataSafetyReport.collectAsStateWithLifecycle()
val plexusScores by viewModel.plexusScores.collectAsStateWithLifecycle() val plexusScores by viewModel.plexusScores.collectAsStateWithLifecycle()
val download by viewModel.download.collectAsStateWithLifecycle()
val installProgress by viewModel.installProgress.collectAsStateWithLifecycle()
val suggestions by viewModel.suggestions.collectAsStateWithLifecycle() val suggestions by viewModel.suggestions.collectAsStateWithLifecycle()
LaunchedEffect(key1 = Unit) { viewModel.fetchAppDetails(packageName) } LaunchedEffect(key1 = Unit) { viewModel.fetchAppDetails(packageName) }
@@ -117,12 +116,10 @@ fun AppDetailsScreen(
suggestions = suggestions, suggestions = suggestions,
isFavorite = favorite, isFavorite = favorite,
isAnonymous = viewModel.authProvider.isAnonymous, isAnonymous = viewModel.authProvider.isAnonymous,
download = download, state = state,
installProgress = installProgress,
plexusScores = plexusScores, plexusScores = plexusScores,
dataSafetyReport = dataSafetyReport, dataSafetyReport = dataSafetyReport,
exodusReport = exodusReport, exodusReport = exodusReport,
hasValidUpdate = viewModel.hasValidUpdate,
onNavigateUp = onNavigateUp, onNavigateUp = onNavigateUp,
onNavigateToAppDetails = onNavigateToAppDetails, onNavigateToAppDetails = onNavigateToAppDetails,
onDownload = { viewModel.purchase(this) }, onDownload = { viewModel.purchase(this) },
@@ -190,12 +187,10 @@ private fun ScreenContentApp(
suggestions: List<App> = emptyList(), suggestions: List<App> = emptyList(),
isFavorite: Boolean = false, isFavorite: Boolean = false,
isAnonymous: Boolean = true, isAnonymous: Boolean = true,
download: Download? = null, state: AppState = AppState.Unavailable,
installProgress: Float? = null,
plexusScores: Scores? = null, plexusScores: Scores? = null,
dataSafetyReport: DataSafetyReport? = null, dataSafetyReport: DataSafetyReport? = null,
exodusReport: Report? = null, exodusReport: Report? = null,
hasValidUpdate: Boolean = false,
onNavigateUp: () -> Unit = {}, onNavigateUp: () -> Unit = {},
onNavigateToAppDetails: (packageName: String) -> Unit = {}, onNavigateToAppDetails: (packageName: String) -> Unit = {},
onDownload: () -> Unit = {}, onDownload: () -> Unit = {},
@@ -248,13 +243,11 @@ private fun ScreenContentApp(
ScreenContentAppMainPane( ScreenContentAppMainPane(
app = app, app = app,
featuredReviews = featuredReviews, featuredReviews = featuredReviews,
download = download, state = state,
installProgress = installProgress,
isAnonymous = isAnonymous, isAnonymous = isAnonymous,
plexusScores = plexusScores, plexusScores = plexusScores,
dataSafetyReport = dataSafetyReport, dataSafetyReport = dataSafetyReport,
exodusReport = exodusReport, exodusReport = exodusReport,
hasValidUpdate = hasValidUpdate,
onNavigateUp = onNavigateUp, onNavigateUp = onNavigateUp,
onNavigateToScreen = { screen -> showExtraPane(screen) }, onNavigateToScreen = { screen -> showExtraPane(screen) },
onDownload = onDownload, onDownload = onDownload,
@@ -316,13 +309,11 @@ private fun ScreenContentApp(
private fun ScreenContentAppMainPane( private fun ScreenContentAppMainPane(
app: App, app: App,
featuredReviews: List<Review> = emptyList(), featuredReviews: List<Review> = emptyList(),
download: Download?, state: AppState = AppState.Unavailable,
installProgress: Float?,
isAnonymous: Boolean, isAnonymous: Boolean,
plexusScores: Scores?, plexusScores: Scores?,
dataSafetyReport: DataSafetyReport?, dataSafetyReport: DataSafetyReport?,
exodusReport: Report?, exodusReport: Report?,
hasValidUpdate: Boolean,
onNavigateUp: () -> Unit, onNavigateUp: () -> Unit,
onNavigateToScreen: (screen: Screen) -> Unit, onNavigateToScreen: (screen: Screen) -> Unit,
onDownload: () -> Unit, onDownload: () -> Unit,
@@ -332,12 +323,11 @@ private fun ScreenContentAppMainPane(
onTestingSubscriptionChange: (subscribe: Boolean) -> Unit, onTestingSubscriptionChange: (subscribe: Boolean) -> Unit,
menuActions: @Composable (RowScope.() -> Unit) = {} menuActions: @Composable (RowScope.() -> Unit) = {}
) { ) {
val context = LocalContext.current
@Composable @Composable
fun SetupAppActions() { fun SetupAppActions() {
when { when (state) {
download?.isRunning == true -> { is AppState.Downloading -> {
AppActions( AppActions(
primaryActionDisplayName = stringResource(R.string.action_open), primaryActionDisplayName = stringResource(R.string.action_open),
secondaryActionDisplayName = stringResource(R.string.action_cancel), secondaryActionDisplayName = stringResource(R.string.action_cancel),
@@ -346,7 +336,7 @@ private fun ScreenContentAppMainPane(
) )
} }
app.isInstalled && hasValidUpdate -> { is AppState.Updatable -> {
AppActions( AppActions(
primaryActionDisplayName = stringResource(R.string.action_update), primaryActionDisplayName = stringResource(R.string.action_update),
secondaryActionDisplayName = stringResource(R.string.action_uninstall), secondaryActionDisplayName = stringResource(R.string.action_uninstall),
@@ -355,7 +345,7 @@ private fun ScreenContentAppMainPane(
) )
} }
app.isInstalled -> { is AppState.Installed -> {
AppActions( AppActions(
primaryActionDisplayName = stringResource(R.string.action_open), primaryActionDisplayName = stringResource(R.string.action_open),
secondaryActionDisplayName = stringResource(R.string.action_uninstall), secondaryActionDisplayName = stringResource(R.string.action_uninstall),
@@ -365,7 +355,7 @@ private fun ScreenContentAppMainPane(
} }
else -> { else -> {
val primaryActionName = if (PackageUtil.isArchived(context, app.packageName)) { val primaryActionName = if (state is AppState.Archived) {
stringResource(R.string.action_unarchive) stringResource(R.string.action_unarchive)
} else { } else {
if (app.isFree) stringResource(R.string.action_install) else app.price if (app.isFree) stringResource(R.string.action_install) else app.price
@@ -394,20 +384,12 @@ private fun ScreenContentAppMainPane(
.padding(dimensionResource(R.dimen.padding_medium)), .padding(dimensionResource(R.dimen.padding_medium)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
) { ) {
val isDownloading = download != null && download.isRunning
val isInstalling = installProgress != null
val progress = when {
isDownloading -> download?.progress?.toFloat() ?: 0F
isInstalling -> installProgress ?: 0F
else -> 0F
}
AppDetails( AppDetails(
app = app, app = app,
inProgress = isDownloading || isInstalling, inProgress = state.inProgress(),
progress = progress, progress = state.progress(),
onNavigateToDetailsDevProfile = { onNavigateToScreen(Screen.DevProfile(it)) }, onNavigateToDetailsDevProfile = { onNavigateToScreen(Screen.DevProfile(it)) },
hasValidUpdate = hasValidUpdate isUpdatable = state is AppState.Updatable
) )
SetupAppActions() SetupAppActions()
@@ -533,7 +515,6 @@ private fun AppDetailsScreenPreview(@PreviewParameter(AppPreviewProvider::class)
ScreenContentApp( ScreenContentApp(
app = app, app = app,
isAnonymous = false, isAnonymous = false,
hasValidUpdate = false,
suggestions = List(10) { app.copy(id = Random.nextInt()) } suggestions = List(10) { app.copy(id = Random.nextInt()) }
) )
} }

View File

@@ -40,7 +40,7 @@ import com.aurora.store.util.PackageUtil
* @param progress Ongoing progress percentage out of 100, for e.g. 50.0 * @param progress Ongoing progress percentage out of 100, for e.g. 50.0
* @param inProgress Whether there is some ongoing progress related to the app * @param inProgress Whether there is some ongoing progress related to the app
* @param onNavigateToDetailsDevProfile Callback when the developer name is tapped * @param onNavigateToDetailsDevProfile Callback when the developer name is tapped
* @param hasValidUpdate Whether the app has a valid update available * @param isUpdatable Whether the app has a valid update available
*/ */
@Composable @Composable
fun AppDetails( fun AppDetails(
@@ -48,7 +48,7 @@ fun AppDetails(
progress: Float = 0F, progress: Float = 0F,
inProgress: Boolean = false, inProgress: Boolean = false,
onNavigateToDetailsDevProfile: (developerName: String) -> Unit = {}, onNavigateToDetailsDevProfile: (developerName: String) -> Unit = {},
hasValidUpdate: Boolean = false, isUpdatable: Boolean = false,
) { ) {
val context = LocalContext.current val context = LocalContext.current
@@ -76,7 +76,7 @@ fun AppDetails(
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
) )
Text( Text(
text = if (!hasValidUpdate) { text = if (!isUpdatable) {
stringResource(R.string.version, app.versionName, app.versionCode) stringResource(R.string.version, app.versionName, app.versionCode)
} else { } else {
stringResource( stringResource(

View File

@@ -32,15 +32,18 @@ sealed class AuthEvent : Event() {
data class GoogleLogin(val success: Boolean, val email: String, val token: String) : AuthEvent() data class GoogleLogin(val success: Boolean, val email: String, val token: String) : AuthEvent()
} }
sealed class InstallerEvent : Event() { open class InstallerEvent(open val packageName: String) : Event() {
lateinit var extra: String data class Installed(override val packageName: String) : InstallerEvent(packageName)
lateinit var error: String data class Uninstalled(override val packageName: String) : InstallerEvent(packageName)
var progress: Int = -1 data class Installing(
override val packageName: String,
val progress: Float = 0.0F
) : InstallerEvent(packageName)
data class Installed(val packageName: String) : InstallerEvent() data class Failed(
data class Uninstalled(val packageName: String) : InstallerEvent() override val packageName: String,
data class Installing(val packageName: String) : InstallerEvent() val error: String? = null,
data class Cancelled(val packageName: String) : InstallerEvent() val extra: String? = null,
data class Failed(val packageName: String) : InstallerEvent() ) : InstallerEvent(packageName)
} }

View File

@@ -112,11 +112,12 @@ class RootInstaller @Inject constructor(
if (packageName == download?.packageName) onInstallationSuccess() if (packageName == download?.packageName) onInstallationSuccess()
} else { } else {
removeFromInstallQueue(packageName) removeFromInstallQueue(packageName)
val event = InstallerEvent.Failed(packageName).apply { AuroraApp.events.send(
this.extra = context.getString(R.string.installer_status_failure) InstallerEvent.Failed(
this.error = parseError(shellResult) packageName = packageName,
} error = parseError(shellResult)
AuroraApp.events.send(event) )
)
} }
} else { } else {
removeFromInstallQueue(packageName) removeFromInstallQueue(packageName)

View File

@@ -230,11 +230,7 @@ class ServiceInstaller @Inject constructor(
try { try {
when (returnCode) { when (returnCode) {
PackageInstaller.STATUS_SUCCESS -> { PackageInstaller.STATUS_SUCCESS -> {
AuroraApp.events.send( AuroraApp.events.send(InstallerEvent.Uninstalled(packageName = packageName))
InstallerEvent.Uninstalled(packageName).apply {
this.extra = context.getString(R.string.action_uninstall_success)
}
)
} }
else -> postError(packageName, getErrorString(context, returnCode), extra) else -> postError(packageName, getErrorString(context, returnCode), extra)
@@ -253,11 +249,7 @@ class ServiceInstaller @Inject constructor(
try { try {
when (returnCode) { when (returnCode) {
PackageInstaller.STATUS_SUCCESS -> { PackageInstaller.STATUS_SUCCESS -> {
AuroraApp.events.send( AuroraApp.events.send(InstallerEvent.Installed(packageName = packageName))
InstallerEvent.Installed(packageName).apply {
this.extra = context.getString(R.string.installer_status_success)
}
)
// Installation is not yet finished if this is a shared library // Installation is not yet finished if this is a shared library
if (packageName == download?.packageName) onInstallationSuccess() if (packageName == download?.packageName) onInstallationSuccess()
} }

View File

@@ -86,9 +86,10 @@ class SessionInstaller @Inject constructor(
if (packageName != null && progress > 0.0) { if (packageName != null && progress > 0.0) {
AuroraApp.events.send( AuroraApp.events.send(
InstallerEvent.Installing(packageName).apply { InstallerEvent.Installing(
this.progress = (progress * 100).toInt() packageName = packageName,
} progress = progress
)
) )
} }
} }

View File

@@ -91,14 +91,14 @@ abstract class InstallerBase(private val context: Context) : IInstaller {
} }
open fun postError(packageName: String, error: String?, extra: String?) { open fun postError(packageName: String, error: String?, extra: String?) {
Log.e(TAG, "Service Error :$error") Log.e(TAG, "Installer Error :$error")
AuroraApp.events.send(
val event = InstallerEvent.Failed(packageName).apply { InstallerEvent.Failed(
this.error = error ?: "" packageName = packageName,
this.extra = extra ?: "" error = error,
} extra = extra
)
AuroraApp.events.send(event) )
} }
fun getFiles( fun getFiles(

View File

@@ -44,3 +44,38 @@ sealed class AuthState {
data class PendingAccountManager(val email: String, val token: String) : AuthState() data class PendingAccountManager(val email: String, val token: String) : AuthState()
data class Failed(val status: String) : AuthState() data class Failed(val status: String) : AuthState()
} }
/**
* Possible states of an app to show appropriate actions on UI
*/
sealed class AppState {
data class Downloading(
val progress: Float,
val speed: Long,
val timeRemaining: Long
) : AppState()
data class Installing(val progress: Float) : AppState()
data object Installed : AppState()
data object Archived : AppState()
data object Updatable : AppState()
data object Unavailable : AppState()
/**
* Whether there is some sort of ongoing process related to the app
*/
fun inProgress(): Boolean {
return this is Downloading || this is Installing
}
/**
* Progress of the process related to the app; 0 otherwise
*/
fun progress(): Float {
return when (this) {
is Downloading -> progress
is Installing -> progress
else -> 0F
}
}
}

View File

@@ -132,26 +132,17 @@ abstract class BaseInstallerStatusReceiver : BroadcastReceiver() {
promptUser(context, intent) promptUser(context, intent)
} }
open fun postStatus(status: Int, packageName: String?, extra: String?, context: Context) { open fun postStatus(status: Int, packageName: String, extra: String?, context: Context) {
val event = when (status) { val event = when (status) {
PackageInstaller.STATUS_SUCCESS -> { PackageInstaller.STATUS_SUCCESS -> InstallerEvent.Installed(
InstallerEvent.Installed(packageName!!).apply { packageName = packageName
this.extra = context.getString(R.string.installer_status_success) )
}
}
PackageInstaller.STATUS_FAILURE_ABORTED -> { else -> InstallerEvent.Failed(
InstallerEvent.Cancelled(packageName!!).apply { packageName = packageName,
this.extra = InstallerBase.getErrorString(context, status) error = InstallerBase.getErrorString(context, status),
} extra = extra
} )
else -> {
InstallerEvent.Failed(packageName!!).apply {
this.error = InstallerBase.getErrorString(context, status)
this.extra = extra ?: ""
}
}
} }
AuroraApp.events.send(event) AuroraApp.events.send(event)

View File

@@ -222,11 +222,14 @@ class DownloadWorker @AssistedInject constructor(
else -> { else -> {
notifyStatus(DownloadStatus.FAILED) notifyStatus(DownloadStatus.FAILED)
AuroraApp.events.send(InstallerEvent.Failed(download.packageName).apply { AuroraApp.events.send(
extra = InstallerEvent.Failed(
exception.message ?: context.getString(R.string.download_failed) packageName = download.packageName,
error = exception.stackTraceToString() error = exception.stackTraceToString(),
}) extra = exception.message
?: context.getString(R.string.download_failed)
)
)
} }
} }
} }

View File

@@ -7,7 +7,6 @@
package com.aurora.store.viewmodel.details package com.aurora.store.viewmodel.details
import android.content.Context import android.content.Context
import android.content.pm.PackageInstaller
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -20,8 +19,11 @@ import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.gplayapi.helpers.ReviewsHelper import com.aurora.gplayapi.helpers.ReviewsHelper
import com.aurora.gplayapi.helpers.web.WebDataSafetyHelper import com.aurora.gplayapi.helpers.web.WebDataSafetyHelper
import com.aurora.gplayapi.network.IHttpClient import com.aurora.gplayapi.network.IHttpClient
import com.aurora.store.AuroraApp
import com.aurora.store.BuildConfig import com.aurora.store.BuildConfig
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.helper.DownloadHelper import com.aurora.store.data.helper.DownloadHelper
import com.aurora.store.data.model.AppState
import com.aurora.store.data.model.ExodusReport import com.aurora.store.data.model.ExodusReport
import com.aurora.store.data.model.PlexusReport import com.aurora.store.data.model.PlexusReport
import com.aurora.store.data.model.Report import com.aurora.store.data.model.Report
@@ -37,14 +39,16 @@ import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@@ -71,6 +75,9 @@ class AppDetailsViewModel @Inject constructor(
private val _app = MutableStateFlow<App?>(App("")) private val _app = MutableStateFlow<App?>(App(""))
val app = _app.asStateFlow() val app = _app.asStateFlow()
private val _state = MutableStateFlow(defaultAppState)
val state = _state.asStateFlow()
private val _suggestions = MutableStateFlow<List<App>>(emptyList()) private val _suggestions = MutableStateFlow<List<App>>(emptyList())
val suggestions = _suggestions.asStateFlow() val suggestions = _suggestions.asStateFlow()
@@ -98,42 +105,23 @@ class AppDetailsViewModel @Inject constructor(
private val _purchaseStatus = MutableSharedFlow<Boolean>() private val _purchaseStatus = MutableSharedFlow<Boolean>()
val purchaseStatus = _purchaseStatus.asSharedFlow() val purchaseStatus = _purchaseStatus.asSharedFlow()
val download = combine(app, downloadHelper.downloadsList) { a, list -> private val download = combine(app, downloadHelper.downloadsList) { a, list ->
if (a?.packageName.isNullOrBlank()) return@combine null if (a?.packageName.isNullOrBlank()) return@combine null
list.find { d -> d.packageName == a?.packageName } list.find { d -> d.packageName == a?.packageName }
}.stateIn(viewModelScope, SharingStarted.Eagerly, null) }.stateIn(viewModelScope, SharingStarted.Eagerly, null)
// TODO: Save sessionId to downloads to monitor packageInstall progress private val isInstalled: Boolean
val installProgress = callbackFlow<Float?> { get() = PackageUtil.isInstalled(context, app.value!!.packageName)
val packageInstaller = context.packageManager.packageInstaller
val callback = object : PackageInstaller.SessionCallback() {
override fun onActiveChanged(p0: Int, p1: Boolean) {}
override fun onBadgingChanged(p0: Int) {}
override fun onCreated(p0: Int) {
// trySend(0F)
}
override fun onFinished(p0: Int, p1: Boolean) {
// trySend(null)
}
override fun onProgressChanged(p0: Int, p1: Float) {
// trySend(p1)
}
}
packageInstaller.registerSessionCallback(callback) private val isUpdatable: Boolean
awaitClose { packageInstaller.unregisterSessionCallback(callback) } get() = PackageUtil.isUpdatable(context, app.value!!.packageName, app.value!!.versionCode)
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
private val isArchived: Boolean
get() = PackageUtil.isArchived(context, app.value!!.packageName)
private val isExtendedUpdateEnabled: Boolean private val isExtendedUpdateEnabled: Boolean
get() = Preferences.getBoolean(context, PREFERENCE_UPDATES_EXTENDED) get() = Preferences.getBoolean(context, PREFERENCE_UPDATES_EXTENDED)
private val isUpdatable: Boolean
get() = PackageUtil.isUpdatable(
context,
app.value!!.packageName,
app.value!!.versionCode
)
private val hasValidCerts: Boolean private val hasValidCerts: Boolean
get() = app.value!!.certificateSetList.any { get() = app.value!!.certificateSetList.any {
it.certificateSet in CertUtil.getEncodedCertificateHashes( it.certificateSet in CertUtil.getEncodedCertificateHashes(
@@ -142,9 +130,19 @@ class AppDetailsViewModel @Inject constructor(
) )
} }
val hasValidUpdate: Boolean private val hasValidUpdate: Boolean
get() = (isUpdatable && hasValidCerts) || (isUpdatable && isExtendedUpdateEnabled) get() = (isUpdatable && hasValidCerts) || (isUpdatable && isExtendedUpdateEnabled)
private val defaultAppState: AppState
get() = when {
isInstalled -> if (hasValidUpdate) AppState.Updatable else AppState.Installed
else -> if (isArchived) AppState.Archived else AppState.Unavailable
}
init {
observeAppState()
}
fun fetchAppDetails(packageName: String) { fun fetchAppDetails(packageName: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
@@ -170,18 +168,6 @@ class AppDetailsViewModel @Inject constructor(
} }
} }
private fun fetchFeaturedReviews(packageName: String) {
viewModelScope.launch(Dispatchers.IO) {
try {
_featuredReviews.value = reviewsHelper.getReviewSummary(packageName)
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch featured app reviews", exception)
_featuredReviews.value = emptyList()
}
}
}
fun updateTestingProgramStatus(packageName: String, subscribe: Boolean) { fun updateTestingProgramStatus(packageName: String, subscribe: Boolean) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
@@ -232,7 +218,7 @@ class AppDetailsViewModel @Inject constructor(
if (files.isNotEmpty()) download(app.copy(fileList = files.toMutableList())) if (files.isNotEmpty()) download(app.copy(fileList = files.toMutableList()))
} catch (exception: Exception) { } catch (exception: Exception) {
_purchaseStatus.emit(false) _purchaseStatus.emit(false)
Log.e(TAG, "Failed to purchase the app ", exception) Log.e(TAG, "Failed to purchase the app", exception)
} }
} }
} }
@@ -261,6 +247,41 @@ class AppDetailsViewModel @Inject constructor(
} }
} }
private fun observeAppState() {
AuroraApp.events.installerEvent
.filter { it.packageName == app.value?.packageName }
.onEach { event ->
_state.value = when {
event is InstallerEvent.Installing -> AppState.Installing(event.progress)
else -> defaultAppState
}
}.launchIn(viewModelScope)
download.filterNotNull().onEach {
_state.value = when {
it.isRunning -> AppState.Downloading(
it.progress.toFloat(),
it.speed,
it.timeRemaining
)
else -> defaultAppState
}
}.launchIn(viewModelScope)
}
private fun fetchFeaturedReviews(packageName: String) {
viewModelScope.launch(Dispatchers.IO) {
try {
_featuredReviews.value = reviewsHelper.getReviewSummary(packageName)
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch featured app reviews", exception)
_featuredReviews.value = emptyList()
}
}
}
private fun fetchFavourite(packageName: String) { private fun fetchFavourite(packageName: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
_favourite.value = favouriteDao.isFavourite(packageName) _favourite.value = favouriteDao.isFavourite(packageName)