Merge "Improve runtime parameter support. 1. Define runtime parameter as prefixed by "rt_" 2. Move entry highlight parameter into intent as Extra, instead of a runtime parameter."
This commit is contained in:
@@ -18,14 +18,11 @@ package com.android.settingslib.spa.gallery.home
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntry
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
import com.android.settingslib.spa.framework.util.getRuntimeArguments
|
||||
import com.android.settingslib.spa.framework.util.mergeArguments
|
||||
import com.android.settingslib.spa.gallery.R
|
||||
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
|
||||
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
|
||||
@@ -61,20 +58,12 @@ object HomePageProvider : SettingsPageProvider {
|
||||
|
||||
@Composable
|
||||
override fun Page(arguments: Bundle?) {
|
||||
val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
|
||||
HomeScaffold(title = stringResource(R.string.app_name)) {
|
||||
for (entry in buildEntry(arguments)) {
|
||||
if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
|
||||
entry.UiLayout(
|
||||
mergeArguments(
|
||||
listOf(
|
||||
globalRuntimeArgs,
|
||||
ArgumentPageModel.buildArgument(intParam = 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
|
||||
} else {
|
||||
entry.UiLayout(globalRuntimeArgs)
|
||||
entry.UiLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,12 @@ package com.android.settingslib.spa.gallery.page
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntry
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
|
||||
import com.android.settingslib.spa.framework.common.SettingsPage
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
import com.android.settingslib.spa.framework.util.getRuntimeArguments
|
||||
import com.android.settingslib.spa.framework.util.mergeArguments
|
||||
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
|
||||
import com.android.settingslib.spa.gallery.createSettingsPage
|
||||
import com.android.settingslib.spa.widget.preference.Preference
|
||||
@@ -101,20 +98,12 @@ object ArgumentPageProvider : SettingsPageProvider {
|
||||
|
||||
@Composable
|
||||
override fun Page(arguments: Bundle?) {
|
||||
val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
|
||||
RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) {
|
||||
for (entry in buildEntry(arguments)) {
|
||||
if (entry.toPage != null) {
|
||||
entry.UiLayout(
|
||||
mergeArguments(
|
||||
listOf(
|
||||
globalRuntimeArgs,
|
||||
ArgumentPageModel.buildNextArgument(arguments)
|
||||
)
|
||||
)
|
||||
)
|
||||
entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
|
||||
} else {
|
||||
entry.UiLayout(globalRuntimeArgs)
|
||||
entry.UiLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ private const val PAGE_TITLE = "Sample page with arguments"
|
||||
private const val STRING_PARAM_TITLE = "String param value"
|
||||
private const val INT_PARAM_TITLE = "Int param value"
|
||||
private const val STRING_PARAM_NAME = "stringParam"
|
||||
private const val INT_PARAM_NAME = "intParam"
|
||||
private const val INT_PARAM_NAME = "rt_intParam"
|
||||
private val ARGUMENT_PAGE_KEYWORDS = listOf("argument keyword1", "argument keyword2")
|
||||
|
||||
class ArgumentPageModel : PageModel() {
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.android.settingslib.spa.framework.common.SettingsEntry
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
import com.android.settingslib.spa.framework.util.getRuntimeArguments
|
||||
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
|
||||
import com.android.settingslib.spa.gallery.createSettingsPage
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
|
||||
@@ -178,10 +177,9 @@ object PreferencePageProvider : SettingsPageProvider {
|
||||
|
||||
@Composable
|
||||
override fun Page(arguments: Bundle?) {
|
||||
val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
|
||||
RegularScaffold(title = PAGE_TITLE) {
|
||||
for (entry in buildEntry(arguments)) {
|
||||
entry.UiLayout(globalRuntimeArgs)
|
||||
entry.UiLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.android.settingslib.spa.R
|
||||
import com.android.settingslib.spa.framework.common.SpaEnvironment
|
||||
import com.android.settingslib.spa.framework.compose.LocalNavController
|
||||
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
|
||||
import com.android.settingslib.spa.framework.compose.localNavController
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
import com.android.settingslib.spa.framework.util.navRoute
|
||||
@@ -70,31 +70,29 @@ open class BrowseActivity(spaEnvironment: SpaEnvironment) : ComponentActivity()
|
||||
composable(NULL_PAGE_NAME) {}
|
||||
for (page in sppRepository.getAllProviders()) {
|
||||
composable(
|
||||
route = page.name + page.parameter.navRoute() +
|
||||
"?$HIGHLIGHT_ENTRY_PARAM_NAME={$HIGHLIGHT_ENTRY_PARAM_NAME}",
|
||||
arguments = page.parameter + listOf(
|
||||
// add optional parameters
|
||||
navArgument(HIGHLIGHT_ENTRY_PARAM_NAME) { defaultValue = "null" }
|
||||
),
|
||||
) { navBackStackEntry ->
|
||||
page.Page(navBackStackEntry.arguments)
|
||||
route = page.name + page.parameter.navRoute(),
|
||||
arguments = page.parameter,
|
||||
) { navBackStackEntry -> page.Page(navBackStackEntry.arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
InitialDestinationNavigator()
|
||||
}
|
||||
|
||||
InitialDestinationNavigator(navController)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InitialDestinationNavigator(navController: NavHostController) {
|
||||
private fun InitialDestinationNavigator() {
|
||||
val destinationNavigated = rememberSaveable { mutableStateOf(false) }
|
||||
if (destinationNavigated.value) return
|
||||
destinationNavigated.value = true
|
||||
val controller = LocalNavController.current as NavControllerWrapperImpl
|
||||
LaunchedEffect(Unit) {
|
||||
val destination =
|
||||
intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPage()
|
||||
val highlightEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
|
||||
if (destination.isNotEmpty()) {
|
||||
controller.highlightId = highlightEntryId
|
||||
val navController = controller.navController
|
||||
navController.navigate(destination) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
inclusive = true
|
||||
@@ -106,6 +104,6 @@ open class BrowseActivity(spaEnvironment: SpaEnvironment) : ComponentActivity()
|
||||
|
||||
companion object {
|
||||
const val KEY_DESTINATION = "spaActivityDestination"
|
||||
const val HIGHLIGHT_ENTRY_PARAM_NAME = "highlightEntry"
|
||||
const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.android.settingslib.spa.R
|
||||
import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
|
||||
import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntry
|
||||
import com.android.settingslib.spa.framework.common.SettingsPage
|
||||
import com.android.settingslib.spa.framework.common.SpaEnvironment
|
||||
@@ -190,7 +191,7 @@ open class DebugActivity(private val spaEnvironment: SpaEnvironment) : Component
|
||||
RegularScaffold(title = "Entry - ${entry.displayTitle()}") {
|
||||
Preference(model = object : PreferenceModel {
|
||||
override val title = "open entry"
|
||||
override val enabled = (!entry.hasRuntimeParam()).toState()
|
||||
override val enabled = (!entry.containerPage().hasRuntimeParam()).toState()
|
||||
override val onClick = openEntry(entry)
|
||||
})
|
||||
Text(text = entryContent)
|
||||
@@ -225,11 +226,12 @@ open class DebugActivity(private val spaEnvironment: SpaEnvironment) : Component
|
||||
|
||||
@Composable
|
||||
private fun openEntry(entry: SettingsEntry): (() -> Unit)? {
|
||||
if (entry.hasRuntimeParam()) return null
|
||||
if (entry.containerPage().hasRuntimeParam()) return null
|
||||
val context = LocalContext.current
|
||||
val route = entry.buildRoute()
|
||||
val route = entry.containerPage().buildRoute()
|
||||
val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
|
||||
putExtra(KEY_DESTINATION, route)
|
||||
putExtra(KEY_HIGHLIGHT_ENTRY, entry.id)
|
||||
}
|
||||
return {
|
||||
Log.d("DEBUG ACTIVITY", "Open entry: $route")
|
||||
|
||||
@@ -208,7 +208,7 @@ open class EntryProvider(spaEnvironment: SpaEnvironment) : ContentProvider() {
|
||||
cursor.newRow()
|
||||
.add(ColumnEnum.ENTRY_ID.id, entry.id)
|
||||
.add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
|
||||
.add(ColumnEnum.ENTRY_ROUTE.id, entry.buildRoute())
|
||||
.add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
|
||||
.add(ColumnEnum.ENTRY_TITLE.id, searchData?.title ?: "")
|
||||
.add(
|
||||
ColumnEnum.ENTRY_SEARCH_KEYWORD.id,
|
||||
|
||||
@@ -23,7 +23,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.android.settingslib.spa.framework.BrowseActivity.Companion.HIGHLIGHT_ENTRY_PARAM_NAME
|
||||
import com.android.settingslib.spa.framework.compose.LocalNavController
|
||||
|
||||
const val INJECT_ENTRY_NAME = "INJECT"
|
||||
const val ROOT_ENTRY_NAME = "ROOT"
|
||||
@@ -90,20 +90,12 @@ data class SettingsEntry(
|
||||
return "${owner.displayName}:$displayName"
|
||||
}
|
||||
|
||||
private fun containerPage(): SettingsPage {
|
||||
fun containerPage(): SettingsPage {
|
||||
// The Container page of the entry, which is the from-page or
|
||||
// the owner-page if from-page is unset.
|
||||
return fromPage ?: owner
|
||||
}
|
||||
|
||||
fun buildRoute(): String {
|
||||
return containerPage().buildRoute(id)
|
||||
}
|
||||
|
||||
fun hasRuntimeParam(): Boolean {
|
||||
return containerPage().hasRuntimeParam()
|
||||
}
|
||||
|
||||
private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
|
||||
val arguments = Bundle()
|
||||
if (owner.arguments != null) arguments.putAll(owner.arguments)
|
||||
@@ -119,8 +111,9 @@ data class SettingsEntry(
|
||||
@Composable
|
||||
fun UiLayout(runtimeArguments: Bundle? = null) {
|
||||
val context = LocalContext.current
|
||||
val controller = LocalNavController.current
|
||||
val highlight = rememberSaveable {
|
||||
mutableStateOf(runtimeArguments?.getString(HIGHLIGHT_ENTRY_PARAM_NAME) == id)
|
||||
mutableStateOf(controller.highlightEntryId == id)
|
||||
}
|
||||
if (highlight.value) {
|
||||
highlight.value = false
|
||||
|
||||
@@ -18,9 +18,9 @@ package com.android.settingslib.spa.framework.common
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import androidx.navigation.NavType
|
||||
import com.android.settingslib.spa.framework.BrowseActivity
|
||||
import com.android.settingslib.spa.framework.util.isRuntimeParam
|
||||
import com.android.settingslib.spa.framework.util.navLink
|
||||
import com.android.settingslib.spa.framework.util.normalize
|
||||
|
||||
/**
|
||||
* Defines data to identify a Settings page.
|
||||
@@ -74,57 +74,25 @@ data class SettingsPage(
|
||||
}
|
||||
|
||||
fun formatArguments(): String {
|
||||
val normalizedArguments = parameter.normalize(arguments)
|
||||
if (normalizedArguments == null || normalizedArguments.isEmpty) return "[No arguments]"
|
||||
return normalizedArguments.toString().removeRange(0, 6)
|
||||
val normArguments = parameter.normalize(arguments)
|
||||
if (normArguments == null || normArguments.isEmpty) return "[No arguments]"
|
||||
return normArguments.toString().removeRange(0, 6)
|
||||
}
|
||||
|
||||
fun formatDisplayTitle(): String {
|
||||
return "$displayName ${formatArguments()}"
|
||||
}
|
||||
|
||||
fun buildRoute(highlightEntryId: String? = null): String {
|
||||
val highlightParam =
|
||||
if (highlightEntryId == null)
|
||||
""
|
||||
else
|
||||
"?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryId"
|
||||
return name + parameter.navLink(arguments) + highlightParam
|
||||
fun buildRoute(): String {
|
||||
return name + parameter.navLink(arguments)
|
||||
}
|
||||
|
||||
fun hasRuntimeParam(): Boolean {
|
||||
return parameter.hasRuntimeParam(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<NamedNavArgument>.normalize(arguments: Bundle? = null): Bundle? {
|
||||
if (this.isEmpty()) return null
|
||||
val normArgs = Bundle()
|
||||
for (navArg in this) {
|
||||
when (navArg.argument.type) {
|
||||
NavType.StringType -> {
|
||||
val value = arguments?.getString(navArg.name)
|
||||
if (value != null)
|
||||
normArgs.putString(navArg.name, value)
|
||||
else
|
||||
normArgs.putString("unset_" + navArg.name, null)
|
||||
}
|
||||
NavType.IntType -> {
|
||||
if (arguments != null && arguments.containsKey(navArg.name))
|
||||
normArgs.putInt(navArg.name, arguments.getInt(navArg.name))
|
||||
else
|
||||
normArgs.putString("unset_" + navArg.name, null)
|
||||
}
|
||||
for (navArg in parameter) {
|
||||
if (navArg.isRuntimeParam()) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return normArgs
|
||||
}
|
||||
|
||||
private fun List<NamedNavArgument>.hasRuntimeParam(arguments: Bundle? = null): Boolean {
|
||||
for (navArg in this) {
|
||||
if (arguments == null || !arguments.containsKey(navArg.name)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun String.toHashId(): String {
|
||||
|
||||
@@ -27,6 +27,9 @@ import androidx.navigation.NavHostController
|
||||
interface NavControllerWrapper {
|
||||
fun navigate(route: String)
|
||||
fun navigateBack()
|
||||
|
||||
val highlightEntryId: String?
|
||||
get() = null
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -56,9 +59,11 @@ fun navigator(route: String?): () -> Unit {
|
||||
}
|
||||
|
||||
internal class NavControllerWrapperImpl(
|
||||
private val navController: NavHostController,
|
||||
val navController: NavHostController,
|
||||
private val onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||
) : NavControllerWrapper {
|
||||
var highlightId: String? = null
|
||||
|
||||
override fun navigate(route: String) {
|
||||
navController.navigate(route)
|
||||
}
|
||||
@@ -66,4 +71,7 @@ internal class NavControllerWrapperImpl(
|
||||
override fun navigateBack() {
|
||||
onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
|
||||
override val highlightEntryId: String?
|
||||
get() = highlightId
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ package com.android.settingslib.spa.framework.util
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import androidx.navigation.NavType
|
||||
import com.android.settingslib.spa.framework.BrowseActivity
|
||||
|
||||
const val RUNTIME_PARAM_PREFIX = "rt_"
|
||||
const val UNSET_PARAM_PREFIX = "unset_"
|
||||
const val UNSET_PARAM_VALUE = "[unset]"
|
||||
|
||||
fun List<NamedNavArgument>.navRoute(): String {
|
||||
return this.joinToString("") { argument -> "/{${argument.name}}" }
|
||||
@@ -29,7 +32,7 @@ fun List<NamedNavArgument>.navLink(arguments: Bundle? = null): String {
|
||||
val argsArray = mutableListOf<String>()
|
||||
for (navArg in this) {
|
||||
if (arguments == null || !arguments.containsKey(navArg.name)) {
|
||||
argsArray.add("[rt]")
|
||||
argsArray.add(UNSET_PARAM_VALUE)
|
||||
continue
|
||||
}
|
||||
when (navArg.argument.type) {
|
||||
@@ -44,6 +47,35 @@ fun List<NamedNavArgument>.navLink(arguments: Bundle? = null): String {
|
||||
return argsArray.joinToString("") { arg -> "/$arg" }
|
||||
}
|
||||
|
||||
fun List<NamedNavArgument>.normalize(arguments: Bundle? = null): Bundle? {
|
||||
if (this.isEmpty()) return null
|
||||
val normArgs = Bundle()
|
||||
for (navArg in this) {
|
||||
// Erase value of runtime parameters.
|
||||
if (navArg.isRuntimeParam()) {
|
||||
normArgs.putString(navArg.name, null)
|
||||
continue
|
||||
}
|
||||
|
||||
when (navArg.argument.type) {
|
||||
NavType.StringType -> {
|
||||
val value = arguments?.getString(navArg.name)
|
||||
if (value != null)
|
||||
normArgs.putString(navArg.name, value)
|
||||
else
|
||||
normArgs.putString(UNSET_PARAM_PREFIX + navArg.name, null)
|
||||
}
|
||||
NavType.IntType -> {
|
||||
if (arguments != null && arguments.containsKey(navArg.name))
|
||||
normArgs.putInt(navArg.name, arguments.getInt(navArg.name))
|
||||
else
|
||||
normArgs.putString(UNSET_PARAM_PREFIX + navArg.name, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
return normArgs
|
||||
}
|
||||
|
||||
fun List<NamedNavArgument>.getStringArg(name: String, arguments: Bundle? = null): String? {
|
||||
if (this.containsStringArg(name) && arguments != null) {
|
||||
return arguments.getString(name)
|
||||
@@ -72,20 +104,6 @@ fun List<NamedNavArgument>.containsIntArg(name: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
fun getRuntimeArguments(arguments: Bundle? = null): Bundle {
|
||||
val res = Bundle()
|
||||
val highlightEntry = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME)
|
||||
if (highlightEntry != null) {
|
||||
res.putString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME, highlightEntry)
|
||||
}
|
||||
// Append more general runtime arguments here
|
||||
return res
|
||||
}
|
||||
|
||||
fun mergeArguments(argsList: List<Bundle?>): Bundle {
|
||||
val res = Bundle()
|
||||
for (args in argsList) {
|
||||
if (args != null) res.putAll(args)
|
||||
}
|
||||
return res
|
||||
fun NamedNavArgument.isRuntimeParam(): Boolean {
|
||||
return this.name.startsWith(RUNTIME_PARAM_PREFIX)
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
|
||||
private const val ENTRY_NAME = "AllowControl"
|
||||
private const val PERMISSION = "permission"
|
||||
private const val PACKAGE_NAME = "packageName"
|
||||
private const val USER_ID = "userId"
|
||||
private const val PACKAGE_NAME = "rt_packageName"
|
||||
private const val USER_ID = "rt_userId"
|
||||
private const val PAGE_NAME = "TogglePermissionAppInfoPage"
|
||||
private val PAGE_PARAMETER = listOf(
|
||||
navArgument(PERMISSION) { type = NavType.StringType },
|
||||
|
||||
Reference in New Issue
Block a user