diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt index 72fe0ae0a..8767b4896 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt @@ -135,7 +135,7 @@ fun AppDetailsScreen( exodusReport = exodusReport, onNavigateUp = onNavigateUp, onNavigateToAppDetails = onNavigateToAppDetails, - onDownload = { viewModel.purchase(app!!) }, + onDownload = { viewModel.enqueueDownload(app!!) }, onFavorite = { viewModel.toggleFavourite(app!!) }, onCancelDownload = { viewModel.cancelDownload(app!!) }, onUninstall = { AppInstaller.uninstall(context, packageName) }, @@ -274,14 +274,14 @@ private fun ScreenContentApp( @Composable fun SetupActions() { when (state) { + is AppState.Queued, is AppState.Purchasing, is AppState.Downloading -> { Actions( primaryActionDisplayName = stringResource(R.string.action_open), secondaryActionDisplayName = stringResource(R.string.action_cancel), isPrimaryActionEnabled = false, - onSecondaryAction = onCancelDownload, - isSecondaryActionEnabled = state !is AppState.Purchasing + onSecondaryAction = onCancelDownload ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/ManualDownloadScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/ManualDownloadScreen.kt index 4f30dafdb..21e8e1783 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/ManualDownloadScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/ManualDownloadScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.FilledTonalButton @@ -33,7 +34,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -49,12 +50,12 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.aurora.extensions.adaptiveNavigationIcon import com.aurora.extensions.isWindowCompact -import com.aurora.extensions.toast import com.aurora.gplayapi.data.models.App import com.aurora.store.R import com.aurora.store.compose.composables.InfoComposable import com.aurora.store.compose.composables.TopAppBarComposable import com.aurora.store.compose.preview.AppPreviewProvider +import com.aurora.store.data.model.AppState import com.aurora.store.viewmodel.details.AppDetailsViewModel import kotlinx.coroutines.android.awaitFrame import kotlinx.coroutines.launch @@ -66,26 +67,21 @@ fun ManualDownloadScreen( viewModel: AppDetailsViewModel = hiltViewModel(key = packageName), windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() ) { - val context = LocalContext.current - val app by viewModel.app.collectAsStateWithLifecycle() + val state by viewModel.state.collectAsStateWithLifecycle() val topAppBarTitle = when { windowAdaptiveInfo.isWindowCompact -> app!!.displayName else -> stringResource(R.string.title_manual_download) } - LaunchedEffect(key1 = Unit) { - viewModel.purchaseStatus.collect { success -> - if (success) { - context.toast(R.string.toast_manual_available) - onNavigateUp() - } else { - context.toast(R.string.toast_manual_unavailable) - } + LaunchedEffect(key1 = state) { + if (state is AppState.Downloading) { + onNavigateUp() } } ScreenContent( + state = state, topAppBarTitle = topAppBarTitle, currentVersionCode = app!!.versionCode, onNavigateUp = onNavigateUp, @@ -98,13 +94,14 @@ fun ManualDownloadScreen( } ) ) - viewModel.purchase(requestedApp) + viewModel.enqueueDownload(requestedApp) } ) } @Composable private fun ScreenContent( + state: AppState = AppState.Unavailable, topAppBarTitle: String? = null, currentVersionCode: Long = 0L, onNavigateUp: () -> Unit = {}, @@ -116,6 +113,7 @@ private fun ScreenContent( val snackBarHostState = remember { SnackbarHostState() } val errorMessage = stringResource(R.string.manual_download_version_error) + val focusManager = LocalFocusManager.current val focusRequester = remember { FocusRequester() } var versionCode by remember { val initText = currentVersionCode.toString() @@ -157,6 +155,7 @@ private fun ScreenContent( modifier = Modifier .fillMaxWidth() .focusRequester(focusRequester), + enabled = !state.inProgress(), value = versionCode, onValueChange = { if (it.text.isDigitsOnly()) { @@ -167,9 +166,8 @@ private fun ScreenContent( }, shape = RoundedCornerShape(10.dp), singleLine = true, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Number - ) + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }) ) } @@ -182,7 +180,7 @@ private fun ScreenContent( onClick = onNavigateUp ) { Text( - text = stringResource(R.string.action_cancel), + text = stringResource(R.string.action_close), maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -190,7 +188,12 @@ private fun ScreenContent( Button( modifier = Modifier.weight(1F), - onClick = { onDownload(versionCode.text.toLong()) }) { + enabled = !state.inProgress(), + onClick = { + onDownload(versionCode.text.toLong()) + focusManager.clearFocus() + } + ) { Text( text = stringResource(R.string.action_install), maxLines = 1, @@ -204,9 +207,7 @@ private fun ScreenContent( @Preview @Composable -private fun ManualDownloadScreenPreview( - @PreviewParameter(AppPreviewProvider::class) app: App -) { +private fun ManualDownloadScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) { ScreenContent( topAppBarTitle = app.displayName, currentVersionCode = app.versionCode diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/components/Details.kt b/app/src/main/java/com/aurora/store/compose/ui/details/components/Details.kt index 8a35abb99..83bd6acaa 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/components/Details.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/components/Details.kt @@ -97,9 +97,8 @@ fun Details( ) } - AppState.Purchasing::class -> { - stringResource(R.string.preparing_to_install) - } + AppState.Queued::class -> stringResource(R.string.status_queued) + AppState.Purchasing::class -> stringResource(R.string.preparing_to_install) else -> { stringResource(R.string.version, versionName, versionCode) diff --git a/app/src/main/java/com/aurora/store/data/model/DownloadStatus.kt b/app/src/main/java/com/aurora/store/data/model/DownloadStatus.kt index 4e1a8596a..da3dada10 100644 --- a/app/src/main/java/com/aurora/store/data/model/DownloadStatus.kt +++ b/app/src/main/java/com/aurora/store/data/model/DownloadStatus.kt @@ -10,10 +10,11 @@ enum class DownloadStatus(@StringRes val localized: Int) { COMPLETED(R.string.status_completed), QUEUED(R.string.status_queued), UNAVAILABLE(R.string.status_unavailable), - VERIFYING(R.string.status_verifying); + VERIFYING(R.string.status_verifying), + PURCHASING(R.string.preparing_to_install); companion object { val finished = listOf(FAILED, CANCELLED, COMPLETED) - val running = listOf(QUEUED, DOWNLOADING) + val running = listOf(QUEUED, PURCHASING, DOWNLOADING) } } diff --git a/app/src/main/java/com/aurora/store/data/model/State.kt b/app/src/main/java/com/aurora/store/data/model/State.kt index 2dee8e587..ba0c68546 100644 --- a/app/src/main/java/com/aurora/store/data/model/State.kt +++ b/app/src/main/java/com/aurora/store/data/model/State.kt @@ -55,6 +55,7 @@ sealed class AppState { val timeRemaining: Long ) : AppState() + data object Queued : AppState() data object Purchasing : AppState() data class Installing(val progress: Float) : AppState() data class Error(val message: String?) : AppState() @@ -68,7 +69,7 @@ sealed class AppState { * Whether there is some sort of ongoing process related to the app */ fun inProgress(): Boolean { - return this is Downloading || this is Installing || this is Purchasing + return this is Downloading || this is Installing || this is Purchasing || this is Queued } /** diff --git a/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt b/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt index 4630cad3a..4f2ee4092 100644 --- a/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt +++ b/app/src/main/java/com/aurora/store/data/work/DownloadWorker.kt @@ -111,6 +111,7 @@ class DownloadWorker @AssistedInject constructor( setForeground(getForegroundInfo()) // Try to purchase the app if file list is empty + notifyStatus(DownloadStatus.PURCHASING) download.fileList = download.fileList.ifEmpty { purchase(download.packageName, download.versionCode, download.offerType) } diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt index eda88ad9e..b5e05d61d 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt @@ -17,7 +17,6 @@ import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Review import com.aurora.gplayapi.data.models.details.TestingProgramStatus import com.aurora.gplayapi.helpers.AppDetailsHelper -import com.aurora.gplayapi.helpers.PurchaseHelper import com.aurora.gplayapi.helpers.ReviewsHelper import com.aurora.gplayapi.helpers.web.WebDataSafetyHelper import com.aurora.gplayapi.network.IHttpClient @@ -26,6 +25,7 @@ import com.aurora.store.BuildConfig import com.aurora.store.data.event.InstallerEvent import com.aurora.store.data.helper.DownloadHelper import com.aurora.store.data.model.AppState +import com.aurora.store.data.model.DownloadStatus import com.aurora.store.data.model.ExodusReport import com.aurora.store.data.model.PlexusReport import com.aurora.store.data.model.Report @@ -40,11 +40,8 @@ import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter @@ -62,7 +59,6 @@ import com.aurora.gplayapi.data.models.datasafety.Report as DataSafetyReport class AppDetailsViewModel @Inject constructor( val authProvider: AuthProvider, @ApplicationContext private val context: Context, - private val purchaseHelper: PurchaseHelper, private val appDetailsHelper: AppDetailsHelper, private val reviewsHelper: ReviewsHelper, private val webDataSafetyHelper: WebDataSafetyHelper, @@ -104,9 +100,6 @@ class AppDetailsViewModel @Inject constructor( private val _favourite = MutableStateFlow(false) val favourite = _favourite.asStateFlow() - private val _purchaseStatus = MutableSharedFlow() - val purchaseStatus = _purchaseStatus.asSharedFlow() - private val download = combine(app, downloadHelper.downloadsList) { a, list -> if (a?.packageName.isNullOrBlank()) return@combine null list.find { d -> d.packageName == a.packageName } @@ -219,20 +212,9 @@ class AppDetailsViewModel @Inject constructor( } } - fun purchase(app: App) { + fun enqueueDownload(app: App) { viewModelScope.launch(Dispatchers.IO) { - try { - _state.value = AppState.Purchasing - val files = purchaseHelper.purchase(app.packageName, app.versionCode, app.offerType) - _purchaseStatus.emit(files.isNotEmpty()) - if (files.isNotEmpty()) { - downloadHelper.enqueueApp(app.copy(fileList = files.toMutableList())) - } - } catch (exception: Exception) { - _state.value = defaultAppState - _purchaseStatus.emit(false) - Log.e(TAG, "Failed to purchase the app", exception) - } + downloadHelper.enqueueApp(app) } } @@ -271,13 +253,15 @@ class AppDetailsViewModel @Inject constructor( }.launchIn(viewModelScope) download.filterNotNull().onEach { - _state.value = when { - it.isRunning -> AppState.Downloading( + _state.value = when (it.status) { + DownloadStatus.DOWNLOADING -> AppState.Downloading( it.progress.toFloat(), it.speed, it.timeRemaining ) + DownloadStatus.QUEUED -> AppState.Queued + DownloadStatus.PURCHASING -> AppState.Purchasing else -> defaultAppState } }.launchIn(viewModelScope)