compose: details: Simplify and fix showing app states
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -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()) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user