compose: details: Encapsulate app-dependent navigation logic
Make it harder to accidentally navigate to an extra screen dependent upon an app object which is unavailable. Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -6,31 +6,23 @@
|
||||
package com.aurora.store.compose.navigation
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import com.aurora.store.R
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Destination (Screen) for navigation in compose
|
||||
* @param label Label of the screen
|
||||
* @param icon Optional icon for the screen; Must not be null if screen is a top-level destination
|
||||
* Destinations for navigation in compose
|
||||
*/
|
||||
@Parcelize
|
||||
@Serializable
|
||||
sealed class Screen(
|
||||
@StringRes val label: Int? = null,
|
||||
@DrawableRes val icon: Int? = null
|
||||
) : NavKey, Parcelable {
|
||||
sealed class Screen : NavKey, Parcelable {
|
||||
|
||||
companion object {
|
||||
const val PARCEL_KEY = "SCREEN"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object Blacklist : Screen(label = R.string.title_blacklist_manager)
|
||||
data object Blacklist : Screen()
|
||||
|
||||
@Serializable
|
||||
data class DevProfile(val developerId: String): Screen()
|
||||
@@ -40,40 +32,4 @@ sealed class Screen(
|
||||
|
||||
@Serializable
|
||||
data object Search : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data object DetailsMore : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data class DetailsScreenshot(val index: Int) : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data object DetailsExodus : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data object DetailsReview : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data object DetailsPermission : Screen()
|
||||
|
||||
/**
|
||||
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
|
||||
*/
|
||||
@Serializable
|
||||
data object DetailsManualDownload : Screen()
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||
import com.aurora.Constants.SHARE_URL
|
||||
import com.aurora.extensions.appInfo
|
||||
@@ -74,6 +75,7 @@ import com.aurora.store.compose.ui.details.components.AppRatingAndReviews
|
||||
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.details.navigation.ExtraScreen
|
||||
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.model.AppState
|
||||
@@ -212,7 +214,7 @@ private fun ScreenContentApp(
|
||||
scaffoldDirective = scaffoldDirective.copy(maxHorizontalPartitions = 1)
|
||||
}
|
||||
|
||||
val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator<Screen>(
|
||||
val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator<NavKey>(
|
||||
scaffoldDirective = scaffoldDirective,
|
||||
adaptStrategies = SupportingPaneScaffoldDefaults.adaptStrategies(
|
||||
supportingPaneAdaptStrategy = AdaptStrategy
|
||||
@@ -224,13 +226,13 @@ private fun ScreenContentApp(
|
||||
val shouldShowMenuOnMainPane = scaffoldNavigator
|
||||
.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Hidden
|
||||
|
||||
fun showMainPane() {
|
||||
fun onNavigateBack() {
|
||||
coroutineScope.launch {
|
||||
scaffoldNavigator.navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
fun showExtraPane(screen: Screen) {
|
||||
fun showExtraPane(screen: NavKey) {
|
||||
coroutineScope.launch {
|
||||
scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Extra, screen)
|
||||
}
|
||||
@@ -242,7 +244,7 @@ private fun ScreenContentApp(
|
||||
when (menuItem) {
|
||||
AppDetailsMenuItem.FAVORITE -> onFavorite()
|
||||
AppDetailsMenuItem.MANUAL_DOWNLOAD -> {
|
||||
showExtraPane(Screen.DetailsManualDownload)
|
||||
showExtraPane(ExtraScreen.ManualDownload)
|
||||
}
|
||||
AppDetailsMenuItem.SHARE -> context.share(app)
|
||||
AppDetailsMenuItem.APP_INFO -> context.appInfo(app.packageName)
|
||||
@@ -295,7 +297,7 @@ private fun ScreenContentApp(
|
||||
primaryActionDisplayName = primaryActionName,
|
||||
secondaryActionDisplayName = stringResource(R.string.title_manual_download),
|
||||
onPrimaryAction = onDownload,
|
||||
onSecondaryAction = { showExtraPane(Screen.DetailsManualDownload) }
|
||||
onSecondaryAction = { showExtraPane(ExtraScreen.ManualDownload) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -336,18 +338,18 @@ private fun ScreenContentApp(
|
||||
HeaderComposable(
|
||||
title = stringResource(R.string.details_more_about_app),
|
||||
subtitle = app.shortDescription,
|
||||
onClick = { showExtraPane(Screen.DetailsMore) }
|
||||
onClick = { showExtraPane(ExtraScreen.More) }
|
||||
)
|
||||
|
||||
AppScreenshots(
|
||||
screenshots = app.screenshots,
|
||||
onNavigateToScreenshot = { showExtraPane(Screen.DetailsScreenshot(it)) }
|
||||
onNavigateToScreenshot = { showExtraPane(ExtraScreen.Screenshot(it)) }
|
||||
)
|
||||
|
||||
AppRatingAndReviews(
|
||||
rating = app.rating,
|
||||
featuredReviews = featuredReviews,
|
||||
onNavigateToDetailsReview = { showExtraPane(Screen.DetailsReview) }
|
||||
onNavigateToDetailsReview = { showExtraPane(ExtraScreen.Review) }
|
||||
)
|
||||
|
||||
if (!isAnonymous && app.testingProgram?.isAvailable == true) {
|
||||
@@ -370,7 +372,7 @@ private fun ScreenContentApp(
|
||||
stringResource(R.string.details_no_permission)
|
||||
},
|
||||
onClick = if (app.permissions.isNotEmpty()) {
|
||||
{ showExtraPane(Screen.DetailsPermission) }
|
||||
{ showExtraPane(ExtraScreen.Permission) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -386,7 +388,7 @@ private fun ScreenContentApp(
|
||||
AppPrivacy(
|
||||
report = exodusReport,
|
||||
onNavigateToDetailsExodus = if (!exodusReport?.trackers.isNullOrEmpty()) {
|
||||
{ showExtraPane(Screen.DetailsExodus) }
|
||||
{ showExtraPane(ExtraScreen.Exodus) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -447,35 +449,41 @@ private fun ScreenContentApp(
|
||||
scaffoldNavigator.currentDestination?.contentKey?.let { screen ->
|
||||
AnimatedPane {
|
||||
when (screen) {
|
||||
is Screen.DetailsReview -> DetailsReviewScreen(
|
||||
is ExtraScreen.Review -> ReviewScreen(
|
||||
packageName = app.packageName,
|
||||
onNavigateUp = ::showMainPane
|
||||
onNavigateUp = ::onNavigateBack
|
||||
)
|
||||
is Screen.DetailsExodus -> DetailsExodusScreen(
|
||||
|
||||
is ExtraScreen.Exodus -> ExodusScreen(
|
||||
packageName = app.packageName,
|
||||
onNavigateUp = ::showMainPane
|
||||
onNavigateUp = ::onNavigateBack
|
||||
)
|
||||
is Screen.DetailsMore -> DetailsMoreScreen(
|
||||
|
||||
is ExtraScreen.More -> MoreScreen(
|
||||
packageName = app.packageName,
|
||||
onNavigateUp = ::showMainPane,
|
||||
onNavigateUp = ::onNavigateBack,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
is Screen.DetailsPermission -> DetailsPermissionScreen(
|
||||
|
||||
is ExtraScreen.Permission -> PermissionScreen(
|
||||
packageName = app.packageName,
|
||||
onNavigateUp = ::showMainPane
|
||||
onNavigateUp = ::onNavigateBack
|
||||
)
|
||||
is Screen.DetailsScreenshot -> DetailsScreenshotScreen(
|
||||
|
||||
is ExtraScreen.Screenshot -> ScreenshotScreen(
|
||||
packageName = app.packageName,
|
||||
index = screen.index,
|
||||
onNavigateUp = ::showMainPane
|
||||
onNavigateUp = ::onNavigateBack
|
||||
)
|
||||
is Screen.DetailsManualDownload -> DetailsManualDownloadScreen(
|
||||
|
||||
is ExtraScreen.ManualDownload -> ManualDownloadScreen(
|
||||
packageName = app.packageName,
|
||||
onNavigateUp = ::showMainPane
|
||||
onNavigateUp = ::onNavigateBack
|
||||
)
|
||||
|
||||
is Screen.DevProfile -> DevProfileScreen(
|
||||
publisherId = app.developerName,
|
||||
onNavigateUp = ::showMainPane,
|
||||
onNavigateUp = ::onNavigateBack,
|
||||
onNavigateToAppDetails = { onNavigateToAppDetails(it) }
|
||||
)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import com.aurora.store.viewmodel.details.DetailsExodusViewModel
|
||||
|
||||
@Composable
|
||||
fun DetailsExodusScreen(
|
||||
fun ExodusScreen(
|
||||
packageName: String,
|
||||
onNavigateUp: () -> Unit,
|
||||
appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName),
|
||||
@@ -110,7 +110,7 @@ private fun ScreenContent(
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DetailsExodusScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
private fun ExodusScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
ScreenContent(
|
||||
topAppBarTitle = app.displayName,
|
||||
version = app.versionName
|
||||
@@ -55,7 +55,7 @@ import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import kotlinx.coroutines.android.awaitFrame
|
||||
|
||||
@Composable
|
||||
fun DetailsManualDownloadScreen(
|
||||
fun ManualDownloadScreen(
|
||||
packageName: String,
|
||||
onNavigateUp: () -> Unit,
|
||||
viewModel: AppDetailsViewModel = hiltViewModel(key = packageName),
|
||||
@@ -182,7 +182,7 @@ private fun ScreenContent(
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DetailsManualDownloadScreenPreview(
|
||||
private fun ManualDownloadScreenPreview(
|
||||
@PreviewParameter(AppPreviewProvider::class) app: App
|
||||
) {
|
||||
ScreenContent(
|
||||
@@ -47,7 +47,7 @@ import com.aurora.store.viewmodel.details.DetailsMoreViewModel
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun DetailsMoreScreen(
|
||||
fun MoreScreen(
|
||||
packageName: String,
|
||||
onNavigateUp: () -> Unit,
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit,
|
||||
@@ -192,7 +192,7 @@ private fun AppInfoMore(app: App) {
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DetailsMoreScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
private fun MoreScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides coilPreviewProvider) {
|
||||
ScreenContent(app = app)
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import com.aurora.store.viewmodel.details.DetailsPermissionViewModel
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun DetailsPermissionScreen(
|
||||
fun PermissionScreen(
|
||||
packageName: String,
|
||||
onNavigateUp: () -> Unit,
|
||||
appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName),
|
||||
@@ -117,7 +117,7 @@ private fun ScreenContent(
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DetailsPermissionScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
private fun PermissionScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
ScreenContent(
|
||||
topAppBarTitle = app.displayName
|
||||
)
|
||||
@@ -54,7 +54,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlin.random.Random
|
||||
|
||||
@Composable
|
||||
fun DetailsReviewScreen(
|
||||
fun ReviewScreen(
|
||||
packageName: String,
|
||||
onNavigateUp: () -> Unit,
|
||||
appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName),
|
||||
@@ -182,7 +182,7 @@ private fun FilterHeader(onClick: (filter: Review.Filter) -> Unit) {
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DetailsReviewScreenPreview(@PreviewParameter(ReviewPreviewProvider::class) review: Review) {
|
||||
private fun ReviewScreenPreview(@PreviewParameter(ReviewPreviewProvider::class) review: Review) {
|
||||
val reviews = List(10) { review.copy(commentId = Random.nextInt().toString()) }
|
||||
val reviewsFlow = MutableStateFlow(PagingData.from(reviews)).collectAsLazyPagingItems()
|
||||
|
||||
@@ -30,7 +30,7 @@ import com.aurora.store.compose.composables.details.ScreenshotComposable
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
|
||||
@Composable
|
||||
fun DetailsScreenshotScreen(
|
||||
fun ScreenshotScreen(
|
||||
packageName: String,
|
||||
index: Int,
|
||||
onNavigateUp: () -> Unit,
|
||||
@@ -96,7 +96,7 @@ private fun ScreenContent(
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun DetailsScreenshotScreenPreview() {
|
||||
private fun ScreenshotScreenPreview() {
|
||||
ScreenContent(
|
||||
topAppBarTitle = stringResource(R.string.app_name)
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.ui.details.navigation
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Extra destinations for app detail's screen
|
||||
*
|
||||
* All of these destinations require and show information related to an app and thus aren't part of
|
||||
* the main navigation display class.
|
||||
*/
|
||||
@Parcelize
|
||||
@Serializable
|
||||
sealed class ExtraScreen : NavKey, Parcelable {
|
||||
|
||||
@Serializable
|
||||
data object More : ExtraScreen()
|
||||
|
||||
@Serializable
|
||||
data class Screenshot(val index: Int) : ExtraScreen()
|
||||
|
||||
@Serializable
|
||||
data object Exodus : ExtraScreen()
|
||||
|
||||
@Serializable
|
||||
data object Review : ExtraScreen()
|
||||
|
||||
@Serializable
|
||||
data object Permission : ExtraScreen()
|
||||
|
||||
@Serializable
|
||||
data object ManualDownload : ExtraScreen()
|
||||
}
|
||||
Reference in New Issue
Block a user