mirror of
https://github.com/whyorean/AuroraStore.git
synced 2025-12-23 22:27:55 -05:00
DO NOT MERGE: Switch to adaptive navigation3 statergy
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -190,6 +190,7 @@ dependencies {
|
||||
|
||||
implementation(libs.androidx.adaptive.core)
|
||||
implementation(libs.androidx.adaptive.navigation)
|
||||
implementation(libs.androidx.adaptive.navigation3)
|
||||
implementation(libs.androidx.adaptive.layout)
|
||||
implementation(libs.androidx.paging.compose)
|
||||
implementation(libs.androidx.navigation3.runtime)
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.composable.Header
|
||||
import com.aurora.store.compose.composable.TopAppBar
|
||||
import com.aurora.store.compose.composable.app.LargeAppListItem
|
||||
import com.aurora.store.compose.preview.AppPreviewProvider
|
||||
import com.aurora.store.compose.preview.coilPreviewProvider
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
import kotlin.random.Random
|
||||
|
||||
@Composable
|
||||
fun SuggestionsScreen(
|
||||
packageName: String,
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit,
|
||||
actions: @Composable (RowScope.() -> Unit) = {},
|
||||
viewModel: AppDetailsViewModel = hiltViewModel(key = packageName),
|
||||
) {
|
||||
val suggestions by viewModel.suggestions.collectAsStateWithLifecycle()
|
||||
|
||||
ScreenContent(
|
||||
suggestions = suggestions,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails,
|
||||
actions = actions
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenContent(
|
||||
suggestions: List<App> = emptyList(),
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit = {},
|
||||
actions: @Composable (RowScope.() -> Unit) = {}
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { TopAppBar(actions = actions) }
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(dimensionResource(R.dimen.margin_medium)),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_suggestions),
|
||||
contentDescription = null
|
||||
)
|
||||
Header(title = stringResource(R.string.pref_ui_similar_apps))
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = dimensionResource(R.dimen.padding_medium))
|
||||
) {
|
||||
items(items = suggestions, key = { item -> item.id }) { app ->
|
||||
LargeAppListItem(
|
||||
app = app,
|
||||
onClick = { onNavigateToAppDetails(app.packageName) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SuggestionsScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides coilPreviewProvider) {
|
||||
val apps = List(10) { app.copy(id = Random.nextInt()) }
|
||||
ScreenContent(suggestions = apps)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.ui.details.navigation
|
||||
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
|
||||
import androidx.compose.material3.adaptive.navigation3.SupportingPaneSceneStrategy
|
||||
import androidx.compose.material3.adaptive.navigation3.rememberSupportingPaneSceneStrategy
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import com.aurora.Constants.SHARE_URL
|
||||
import com.aurora.extensions.appInfo
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.extensions.isWindowCompact
|
||||
import com.aurora.extensions.requiresObbDir
|
||||
import com.aurora.extensions.share
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.compose.navigation.Screen
|
||||
import com.aurora.store.compose.ui.commons.PermissionRationaleScreen
|
||||
import com.aurora.store.compose.ui.details.AppDetailsScreen
|
||||
import com.aurora.store.compose.ui.details.ExodusScreen
|
||||
import com.aurora.store.compose.ui.details.ManualDownloadScreen
|
||||
import com.aurora.store.compose.ui.details.MoreScreen
|
||||
import com.aurora.store.compose.ui.details.PermissionScreen
|
||||
import com.aurora.store.compose.ui.details.ReviewScreen
|
||||
import com.aurora.store.compose.ui.details.ScreenshotScreen
|
||||
import com.aurora.store.compose.ui.details.SuggestionsScreen
|
||||
import com.aurora.store.compose.ui.details.menu.AppDetailsMenu
|
||||
import com.aurora.store.compose.ui.details.menu.MenuItem
|
||||
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
||||
import com.aurora.store.data.model.PermissionType
|
||||
import com.aurora.store.data.providers.PermissionProvider.Companion.isPermittedToInstall
|
||||
import com.aurora.store.util.ShortcutManagerUtil
|
||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||
|
||||
/**
|
||||
* Navigation display for AppDetails Screen
|
||||
*
|
||||
* When viewing details related to an app, a user can navigate to several screens among
|
||||
* which majority are related to the app, directly or indirectly. This navigation display
|
||||
* is supposed to manage those screens in its own separate backstack.
|
||||
*/
|
||||
@Composable
|
||||
fun NavDisplay(
|
||||
packageName: String,
|
||||
onNavigateUp: () -> Unit,
|
||||
onNavigateToAppDetails: (packageName: String) -> Unit,
|
||||
forceSinglePane: Boolean = false,
|
||||
viewModel: AppDetailsViewModel = hiltViewModel(key = packageName),
|
||||
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val app by viewModel.app.collectAsStateWithLifecycle()
|
||||
|
||||
// TODO: Make this adaptive so it doesn't looks bad; maybe use BoxWithConstraints
|
||||
var paneScaffoldDirective = calculatePaneScaffoldDirective(windowAdaptiveInfo)
|
||||
if (forceSinglePane) {
|
||||
paneScaffoldDirective = paneScaffoldDirective.copy(maxHorizontalPartitions = 1)
|
||||
}
|
||||
|
||||
val startDestinations = listOfNotNull<NavKey>(
|
||||
Screen.AppDetails(packageName),
|
||||
if (windowAdaptiveInfo.isWindowCompact) SupportingScreen.Suggestions else null
|
||||
)
|
||||
val backstack = rememberNavBackStack(*startDestinations.toTypedArray())
|
||||
val supportingPaneSceneStrategy = rememberSupportingPaneSceneStrategy<NavKey>(
|
||||
directive = paneScaffoldDirective
|
||||
)
|
||||
|
||||
fun onRequestNavigateUp() {
|
||||
if (!backstack.all { it in startDestinations }) {
|
||||
backstack.removeLastOrNull()
|
||||
} else {
|
||||
onNavigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle manual download on first time without permissions as app will change
|
||||
fun onInstall(requested: App = app!!) {
|
||||
if (isPermittedToInstall(context, requested)) {
|
||||
viewModel.enqueueDownload(requested)
|
||||
backstack.removeAll(setOf(ExtraScreen.ManualDownload, Screen.PermissionRationale))
|
||||
} else {
|
||||
val requiredPermissions = setOfNotNull(
|
||||
PermissionType.INSTALL_UNKNOWN_APPS,
|
||||
if (requested.fileList.requiresObbDir()) PermissionType.STORAGE_MANAGER else null,
|
||||
if (requested.fileList.requiresObbDir()) PermissionType.EXTERNAL_STORAGE else null
|
||||
)
|
||||
backstack.add(Screen.PermissionRationale(requiredPermissions = requiredPermissions))
|
||||
}
|
||||
}
|
||||
|
||||
NavDisplay(
|
||||
backStack = backstack,
|
||||
entryDecorators = listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator()
|
||||
),
|
||||
sceneStrategy = supportingPaneSceneStrategy,
|
||||
entryProvider = entryProvider {
|
||||
entry<Screen.AppDetails>(metadata = SupportingPaneSceneStrategy.mainPane()) {
|
||||
AppDetailsScreen(
|
||||
packageName = packageName,
|
||||
onNavigateUp = ::onRequestNavigateUp,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails,
|
||||
)
|
||||
}
|
||||
|
||||
entry<SupportingScreen.Suggestions>(
|
||||
metadata = when {
|
||||
windowAdaptiveInfo.isWindowCompact -> SupportingPaneSceneStrategy.extraPane()
|
||||
else -> SupportingPaneSceneStrategy.supportingPane()
|
||||
}
|
||||
) {
|
||||
SuggestionsScreen(
|
||||
packageName = packageName,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
}
|
||||
|
||||
entry<ExtraScreen.Review>(metadata = SupportingPaneSceneStrategy.extraPane()) {
|
||||
ReviewScreen(
|
||||
packageName = packageName,
|
||||
onNavigateUp = ::onRequestNavigateUp
|
||||
)
|
||||
}
|
||||
|
||||
entry<ExtraScreen.Exodus>(metadata = SupportingPaneSceneStrategy.extraPane()) {
|
||||
ExodusScreen(
|
||||
packageName = packageName,
|
||||
onNavigateUp = ::onRequestNavigateUp
|
||||
)
|
||||
}
|
||||
|
||||
entry<ExtraScreen.More>(metadata = SupportingPaneSceneStrategy.extraPane()) {
|
||||
MoreScreen(
|
||||
packageName = packageName,
|
||||
onNavigateUp = ::onRequestNavigateUp,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
}
|
||||
|
||||
entry<ExtraScreen.Permission>(metadata = SupportingPaneSceneStrategy.extraPane()) {
|
||||
PermissionScreen(
|
||||
packageName = packageName,
|
||||
onNavigateUp = ::onRequestNavigateUp
|
||||
)
|
||||
}
|
||||
|
||||
entry<ExtraScreen.Screenshot>(metadata = SupportingPaneSceneStrategy.extraPane()) {
|
||||
ScreenshotScreen(
|
||||
packageName = packageName,
|
||||
index = it.index,
|
||||
onNavigateUp = ::onRequestNavigateUp
|
||||
)
|
||||
}
|
||||
|
||||
entry<ExtraScreen.ManualDownload>(metadata = SupportingPaneSceneStrategy.extraPane()) {
|
||||
ManualDownloadScreen(
|
||||
packageName = packageName,
|
||||
onNavigateUp = ::onRequestNavigateUp,
|
||||
onRequestInstall = { requestedApp -> onInstall(requestedApp) }
|
||||
)
|
||||
}
|
||||
|
||||
// Independent screens but still navigated as extras as related to app //
|
||||
entry<Screen.DevProfile>(metadata = SupportingPaneSceneStrategy.extraPane()) {
|
||||
DevProfileScreen(
|
||||
publisherId = app!!.developerName,
|
||||
onNavigateUp = ::onRequestNavigateUp,
|
||||
onNavigateToAppDetails = onNavigateToAppDetails
|
||||
)
|
||||
}
|
||||
|
||||
entry<Screen.PermissionRationale>(metadata = SupportingPaneSceneStrategy.extraPane()) {
|
||||
PermissionRationaleScreen(
|
||||
onNavigateUp = ::onRequestNavigateUp,
|
||||
requiredPermissions = it.requiredPermissions,
|
||||
onPermissionCallback = { onInstall() }
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Supporting destinations for app detail's screen
|
||||
*/
|
||||
@Parcelize
|
||||
@Serializable
|
||||
sealed class SupportingScreen : NavKey, Parcelable {
|
||||
|
||||
@Serializable
|
||||
data object Suggestions : SupportingScreen()
|
||||
}
|
||||
@@ -9,6 +9,7 @@ activity = "1.11.0"
|
||||
agCoreservice = "13.3.1.300"
|
||||
androidGradlePlugin = "8.13.0"
|
||||
adaptive = "1.3.0-alpha02"
|
||||
adaptive-navigation3 = "1.3.0-alpha03"
|
||||
androidx-hilt = "1.3.0"
|
||||
androidx-junit = "1.3.0"
|
||||
browser = "1.9.0"
|
||||
@@ -59,6 +60,7 @@ androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core" }
|
||||
androidx-adaptive-core = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "adaptive" }
|
||||
androidx-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "adaptive" }
|
||||
androidx-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "adaptive" }
|
||||
androidx-adaptive-navigation3 = { module = "androidx.compose.material3.adaptive:adaptive-navigation3", version.ref = "adaptive-navigation3" }
|
||||
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
|
||||
androidx-hilt-viewmodel = { module = "androidx.hilt:hilt-lifecycle-viewmodel-compose", version.ref = "androidx-hilt" }
|
||||
androidx-junit = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-junit" }
|
||||
|
||||
Reference in New Issue
Block a user