From 70d68dc8721bad72150a45e20ebbeaecd1ecf411 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Tue, 20 May 2025 22:52:17 +0800 Subject: [PATCH] compose: details: Move manual download to independent screen We want to allow users to select location to download files and select splits in future too. Signed-off-by: Aayush Gupta --- .../aurora/store/compose/navigation/Screen.kt | 6 + .../compose/ui/details/AppDetailsScreen.kt | 43 +--- .../ui/details/DetailsManualDownloadScreen.kt | 188 ++++++++++++++++++ .../ui/dialogs/ManualDownloadDialog.kt | 102 ---------- 4 files changed, 202 insertions(+), 137 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/compose/ui/details/DetailsManualDownloadScreen.kt delete mode 100644 app/src/main/java/com/aurora/store/compose/ui/dialogs/ManualDownloadDialog.kt diff --git a/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt b/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt index a709bbb93..7e75335b3 100644 --- a/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt +++ b/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt @@ -66,4 +66,10 @@ sealed class Screen( */ @Serializable data object DetailsPermission : Screen() + + /** + * Child screen of [AppDetails]; Avoid navigating to this screen directly. + */ + @Serializable + data object DetailsManualDownload : Screen() } 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 846c25af0..0d6c26484 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 @@ -28,10 +28,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -75,7 +72,6 @@ import com.aurora.store.compose.ui.details.components.AppScreenshots import com.aurora.store.compose.ui.details.components.AppTags import com.aurora.store.compose.ui.details.components.AppTesting import com.aurora.store.compose.ui.dev.DevProfileScreen -import com.aurora.store.compose.ui.dialogs.ManualDownloadDialog import com.aurora.store.data.installer.AppInstaller import com.aurora.store.data.model.Report import com.aurora.store.data.model.Scores @@ -107,33 +103,7 @@ fun AppDetailsScreen( val installProgress by viewModel.installProgress.collectAsStateWithLifecycle() val suggestions by viewModel.suggestions.collectAsStateWithLifecycle() - var shouldShowManualDownloadDialog by rememberSaveable { mutableStateOf(false) } - LaunchedEffect(key1 = Unit) { viewModel.fetchAppDetails(packageName) } - LaunchedEffect(key1 = Unit) { - viewModel.purchaseStatus.collect { success -> - if (shouldShowManualDownloadDialog) { - if (success) { - shouldShowManualDownloadDialog = false - context.toast(R.string.toast_manual_available) - } else { - context.toast(R.string.toast_manual_unavailable) - } - } else { - context.toast(R.string.download_failed) - } - } - } - - if (shouldShowManualDownloadDialog) { - ManualDownloadDialog( - currentVersionCode = app!!.versionCode, - onConfirm = { versionCode -> - viewModel.purchase(app!!.copy(versionCode = versionCode)) - }, - onDismiss = { shouldShowManualDownloadDialog = false } - ) - } with(app) { when { @@ -155,9 +125,8 @@ fun AppDetailsScreen( hasValidUpdate = viewModel.hasValidUpdate, onNavigateUp = onNavigateUp, onNavigateToAppDetails = onNavigateToAppDetails, - onDownload = { viewModel.download(this) }, + onDownload = { viewModel.purchase(this) }, onFavorite = { viewModel.toggleFavourite(this) }, - onManualDownload = { shouldShowManualDownloadDialog = true }, onCancelDownload = { viewModel.cancelDownload(this) }, onUninstall = { AppInstaller.uninstall(context, packageName) }, onOpen = { @@ -230,7 +199,6 @@ private fun ScreenContentApp( onNavigateUp: () -> Unit = {}, onNavigateToAppDetails: (packageName: String) -> Unit = {}, onDownload: () -> Unit = {}, - onManualDownload: () -> Unit = {}, onFavorite: () -> Unit = {}, onCancelDownload: () -> Unit = {}, onUninstall: () -> Unit = {}, @@ -260,7 +228,9 @@ private fun ScreenContentApp( AppDetailsMenu(isInstalled = app.isInstalled, isFavorite = isFavorite) { menuItem -> when (menuItem) { AppDetailsMenuItem.FAVORITE -> onFavorite() - AppDetailsMenuItem.MANUAL_DOWNLOAD -> onManualDownload() + AppDetailsMenuItem.MANUAL_DOWNLOAD -> { + showExtraPane(Screen.DetailsManualDownload) + } AppDetailsMenuItem.SHARE -> context.share(app) AppDetailsMenuItem.APP_INFO -> context.appInfo(app.packageName) AppDetailsMenuItem.PLAY_STORE -> context.browse("$SHARE_URL${app.packageName}") @@ -293,7 +263,7 @@ private fun ScreenContentApp( onNavigateToDetailsExodus = { showExtraPane(Screen.DetailsExodus) }, onNavigateToDetailsPermission = { showExtraPane(Screen.DetailsPermission) }, onDownload = onDownload, - onManualDownload = onManualDownload, + onManualDownload = { showExtraPane(Screen.DetailsManualDownload) }, onCancelDownload = onCancelDownload, onUninstall = onUninstall, onOpen = onOpen, @@ -328,6 +298,9 @@ private fun ScreenContentApp( index = screen.index, onNavigateUp = ::showMainPane ) + is Screen.DetailsManualDownload -> DetailsManualDownloadScreen( + onNavigateUp = ::showMainPane + ) is Screen.DevProfile -> DevProfileScreen( publisherId = app.developerName, onNavigateUp = ::showMainPane, diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/DetailsManualDownloadScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/DetailsManualDownloadScreen.kt new file mode 100644 index 000000000..144712653 --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/details/DetailsManualDownloadScreen.kt @@ -0,0 +1,188 @@ +/* + * SPDX-FileCopyrightText: 2025 The Calyx Institute + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.aurora.store.compose.ui.details + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.WindowAdaptiveInfo +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.core.text.isDigitsOnly +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.window.core.layout.WindowWidthSizeClass +import com.aurora.extensions.adaptiveNavigationIcon +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.composables.preview.AppPreviewProvider +import com.aurora.store.viewmodel.details.AppDetailsViewModel +import kotlinx.coroutines.android.awaitFrame + +@Composable +fun DetailsManualDownloadScreen( + onNavigateUp: () -> Unit, + viewModel: AppDetailsViewModel = hiltViewModel(), + windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() +) { + val context = LocalContext.current + + val app by viewModel.app.collectAsStateWithLifecycle() + val topAppBarTitle = when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) { + WindowWidthSizeClass.COMPACT -> 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) + } + } + } + + ScreenContent( + topAppBarTitle = topAppBarTitle, + currentVersionCode = app!!.versionCode, + onNavigateUp = onNavigateUp, + onDownload = { versionCode -> + viewModel.purchase(app!!.copy(versionCode = versionCode)) + } + ) +} + +@Composable +private fun ScreenContent( + topAppBarTitle: String? = null, + currentVersionCode: Long = 0L, + onNavigateUp: () -> Unit = {}, + onDownload: (versionCode: Long) -> Unit = {}, + windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() +) { + + val focusRequester = remember { FocusRequester() } + var versionCode by remember { + val initText = currentVersionCode.toString() + mutableStateOf(TextFieldValue(text = initText, selection = TextRange(initText.length))) + } + + LaunchedEffect(focusRequester) { + awaitFrame() + focusRequester.requestFocus() + } + + Scaffold( + topBar = { + TopAppBarComposable( + title = topAppBarTitle, + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, + onNavigateUp = onNavigateUp + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(dimensionResource(R.dimen.padding_medium)), + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) + ) { + InfoComposable( + icon = R.drawable.ic_download_manager, + title = AnnotatedString(text = stringResource(R.string.manual_download_hint)) + ) + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester), + value = versionCode, + onValueChange = { if (it.text.isDigitsOnly()) versionCode = it }, + shape = RoundedCornerShape(10.dp), + singleLine = true, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number + ) + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)) + ) { + FilledTonalButton( + modifier = Modifier.weight(1F), + onClick = onNavigateUp + ) { + Text( + text = stringResource(R.string.action_cancel), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + Button( + modifier = Modifier.weight(1F), + onClick = { onDownload(versionCode.text.toLong()) }) { + Text( + text = stringResource(R.string.action_install), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } +} + +@Preview +@Composable +private fun DetailsManualDownloadScreenPreview( + @PreviewParameter(AppPreviewProvider::class) app: App +) { + ScreenContent( + topAppBarTitle = app.displayName, + currentVersionCode = app.versionCode + ) +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/dialogs/ManualDownloadDialog.kt b/app/src/main/java/com/aurora/store/compose/ui/dialogs/ManualDownloadDialog.kt deleted file mode 100644 index c4d0e9703..000000000 --- a/app/src/main/java/com/aurora/store/compose/ui/dialogs/ManualDownloadDialog.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The Calyx Institute - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package com.aurora.store.compose.ui.dialogs - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.unit.dp -import androidx.core.text.isDigitsOnly -import com.aurora.gplayapi.data.models.App -import com.aurora.store.R -import com.aurora.store.compose.composables.preview.AppPreviewProvider -import kotlinx.coroutines.android.awaitFrame - -/** - * Dialog for entering the versionCode manually for an App to download - * @param currentVersionCode Current versionCode of the app - * @param onConfirm Callback when the versionCode has been entered - * @param onDismiss Callback when the dialog has been dismissed - */ -@Composable -fun ManualDownloadDialog( - currentVersionCode: Long, - onConfirm: (versionCode: Long) -> Unit = {}, - onDismiss: () -> Unit = {} -) { - val focusRequester = remember { FocusRequester() } - var versionCode by remember { - val initText = currentVersionCode.toString() - mutableStateOf(TextFieldValue(text = initText, selection = TextRange(initText.length))) - } - - LaunchedEffect(focusRequester) { - awaitFrame() - focusRequester.requestFocus() - } - - AlertDialog( - title = { Text(text = stringResource(R.string.title_manual_download)) }, - text = { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - Text(text = stringResource(R.string.manual_download_hint)) - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester), - value = versionCode, - onValueChange = { if (it.text.isDigitsOnly()) versionCode = it }, - shape = RoundedCornerShape(10.dp), - singleLine = true, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Number - ) - ) - } - }, - onDismissRequest = onDismiss, - confirmButton = { - TextButton (onClick = { onConfirm(versionCode.text.toLong()) }) { - Text(text = stringResource(android.R.string.ok)) - } - }, - dismissButton = { - TextButton(onClick = onDismiss) { - Text(text = stringResource(android.R.string.cancel)) - } - } - ) -} - -@Preview -@Composable -private fun ManualDownloadDialogPreview(@PreviewParameter(AppPreviewProvider::class) app: App) { - ManualDownloadDialog(currentVersionCode = app.versionCode) -}