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 aa1d148cf..fae82eb6b 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 @@ -7,6 +7,7 @@ package com.aurora.store.compose.ui.details import android.content.ActivityNotFoundException +import android.content.Context import android.content.Intent import androidx.compose.animation.AnimatedContent import androidx.compose.animation.fadeIn @@ -135,6 +136,8 @@ fun AppDetailsScreen( app = loadedApp, error = err.error, extra = err.extra, + isAnonymous = viewModel.authProvider.isAnonymous, + onBuy = { openPlayStore(context, loadedApp.packageName) }, onDismiss = viewModel::dismissInstallError ) } @@ -198,6 +201,21 @@ fun AppDetailsScreen( } } +/** + * Opens the given app's Play Store listing, preferring the Play Store app when available and + * falling back to whichever activity can handle the web listing (e.g. a browser). + */ +private fun openPlayStore(context: Context, packageName: String) { + val uri = "${Constants.SHARE_URL}$packageName".toUri() + val intent = Intent(Intent.ACTION_VIEW).apply { data = uri } + + if (intent.resolveActivity(context.packageManager) != null) { + context.startActivity(intent.apply { setPackage(Constants.PACKAGE_NAME_PLAY_STORE) }) + } else { + context.startActivity(intent) + } +} + private fun stateKey(state: AppState, app: App?): String = when { state is AppState.Error -> "error" state is AppState.Loading || app == null -> "loading" @@ -261,6 +279,11 @@ private fun ScreenContentApp( onForceRestart: () -> Unit = {} ) { val context = LocalContext.current + + // Anonymous accounts can't purchase, so a paid app can neither be installed nor manually + // downloaded (any version) by them. Free apps are always acquirable. + val canAcquire = app.isFree || !isAnonymous + var scaffoldDirective = calculatePaneScaffoldDirective(windowAdaptiveInfo) if (forceSinglePane) { @@ -317,7 +340,11 @@ private fun ScreenContentApp( @Composable fun SetupMenu() { - AppDetailsMenu(isFavorite = isFavorite, state = state) { menuItem -> + AppDetailsMenu( + isFavorite = isFavorite, + state = state, + canManualDownload = canAcquire + ) { menuItem -> when (menuItem) { MenuItem.FAVORITE -> onFavorite() @@ -333,20 +360,7 @@ private fun ScreenContentApp( ShortcutManagerUtil.requestPinShortcut(context, app.packageName) } - MenuItem.PLAY_STORE -> { - val uri = "${Constants.SHARE_URL}${app.packageName}".toUri() - val intent = Intent(Intent.ACTION_VIEW).apply { data = uri } - - if (intent.resolveActivity(context.packageManager) != null) { - context.startActivity( - intent.apply { - setPackage(Constants.PACKAGE_NAME_PLAY_STORE) - } - ) - } else { - context.startActivity(intent) - } - } + MenuItem.PLAY_STORE -> openPlayStore(context, app.packageName) } } } @@ -413,6 +427,8 @@ private fun ScreenContentApp( Actions( primaryActionDisplayName = primaryActionName, secondaryActionDisplayName = stringResource(R.string.title_manual_download), + isPrimaryActionEnabled = canAcquire, + isSecondaryActionEnabled = canAcquire, onPrimaryAction = ::onInstall, onSecondaryAction = { showExtraPane(ExtraScreen.ManualDownload) } ) diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/menu/AppDetailsMenu.kt b/app/src/main/java/com/aurora/store/compose/ui/details/menu/AppDetailsMenu.kt index 07f909a3d..afd70198a 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/menu/AppDetailsMenu.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/menu/AppDetailsMenu.kt @@ -42,6 +42,7 @@ fun AppDetailsMenu( state: AppState = AppState.Unavailable, isFavorite: Boolean = false, isExpanded: Boolean = false, + canManualDownload: Boolean = true, onMenuItemClicked: (menuItem: MenuItem) -> Unit = {} ) { val context = LocalContext.current @@ -80,7 +81,7 @@ fun AppDetailsMenu( DropdownMenuItem( text = { Text(text = stringResource(R.string.title_manual_download)) }, onClick = { onClick(MenuItem.MANUAL_DOWNLOAD) }, - enabled = !state.inProgress() + enabled = canManualDownload && !state.inProgress() ) DropdownMenuItem( text = { Text(text = stringResource(R.string.action_info)) }, diff --git a/app/src/main/java/com/aurora/store/compose/ui/sheets/InstallErrorSheet.kt b/app/src/main/java/com/aurora/store/compose/ui/sheets/InstallErrorSheet.kt index 34a4dd87f..c93ef203c 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/sheets/InstallErrorSheet.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/sheets/InstallErrorSheet.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -41,9 +42,20 @@ import com.aurora.store.R @OptIn(ExperimentalMaterial3Api::class) @Composable -fun InstallErrorSheet(app: App, error: String?, extra: String?, onDismiss: () -> Unit) { +fun InstallErrorSheet( + app: App, + error: String?, + extra: String?, + isAnonymous: Boolean = true, + onBuy: () -> Unit = {}, + onDismiss: () -> Unit +) { val context = LocalContext.current + // A paid app that failed to install for a signed-in (non-anonymous) account is almost + // always one that hasn't been purchased yet; offer a shortcut to buy it on the Play Store. + val canBuy = !app.isFree && !isAnonymous + ModalBottomSheet( onDismissRequest = onDismiss, sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) @@ -53,7 +65,7 @@ fun InstallErrorSheet(app: App, error: String?, extra: String?, onDismiss: () -> .fillMaxWidth() .verticalScroll(rememberScrollState()) ) { - Header(app = app) + Header(app = app, showBuy = canBuy, onBuy = onBuy) HorizontalDivider( modifier = Modifier.padding( @@ -101,7 +113,7 @@ fun InstallErrorSheet(app: App, error: String?, extra: String?, onDismiss: () -> } @Composable -private fun Header(app: App) { +private fun Header(app: App, showBuy: Boolean = false, onBuy: () -> Unit = {}) { Row( modifier = Modifier .fillMaxWidth() @@ -137,5 +149,14 @@ private fun Header(app: App) { overflow = TextOverflow.Ellipsis ) } + if (showBuy) { + Button(onClick = onBuy) { + Text( + text = stringResource(R.string.action_buy, app.price), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87f9d60cb..a3ffdaf7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,6 +52,7 @@ "Back" "Blacklist" "Add to blacklist" + "Buy @ %1$s" "Cancel" "Clear" "Close"