Defines SettingsEntry and use it in ArgumentPage
Add buildEntry API in SPP. Implement buildEntry API in ArgumentPage. Add helper class SettingsEntryBuilder to build SettingsEntry. Add helper class to control the arguments of Settings pages. Bug: 244122804 Test: manual - build Spa gallery Change-Id: I6158d525c8aa92381d049d95c587ddd8764eda93
This commit is contained in:
@@ -21,6 +21,10 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import com.android.settingslib.spa.framework.common.PageArguments
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntry
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageBuilder
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||
import com.android.settingslib.spa.framework.compose.navigator
|
||||
import com.android.settingslib.spa.framework.compose.toState
|
||||
@@ -41,41 +45,72 @@ object ArgumentPageProvider : SettingsPageProvider {
|
||||
navArgument(INT_PARAM_NAME) { type = NavType.IntType },
|
||||
)
|
||||
|
||||
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
|
||||
val pageArgs = PageArguments(ArgumentPageProvider.arguments, arguments)
|
||||
if (!pageArgs.isValid()) return emptyList()
|
||||
|
||||
val owner = SettingsPageBuilder.create(name, pageArgs.normalize()).build()
|
||||
val entryList = mutableListOf<SettingsEntry>()
|
||||
val stringParamEntry = SettingsEntryBuilder.create("string_param", owner)
|
||||
stringParamEntry.setUiLayoutFn {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "String param value"
|
||||
override val summary = pageArgs.getStringArg(STRING_PARAM_NAME)!!.toState()
|
||||
})
|
||||
}
|
||||
entryList.add(stringParamEntry.build())
|
||||
|
||||
val intParamEntry = SettingsEntryBuilder.create("int_param", owner)
|
||||
intParamEntry.setUiLayoutFn {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Int param value"
|
||||
override val summary = pageArgs.getIntArg(INT_PARAM_NAME)!!.toString().toState()
|
||||
})
|
||||
}
|
||||
entryList.add(intParamEntry.build())
|
||||
|
||||
val entryFoo = buildInjectEntry(pageArgs.buildNextArg("foo"))
|
||||
val entryBar = buildInjectEntry(pageArgs.buildNextArg("bar"))
|
||||
if (entryFoo != null) entryList.add(entryFoo.setLink(fromPage = owner).build())
|
||||
if (entryBar != null) entryList.add(entryBar.setLink(fromPage = owner).build())
|
||||
|
||||
return entryList
|
||||
}
|
||||
|
||||
private fun buildInjectEntry(arguments: Bundle?): SettingsEntryBuilder? {
|
||||
val pageArgs = PageArguments(ArgumentPageProvider.arguments, arguments)
|
||||
if (!pageArgs.isValid()) return null
|
||||
|
||||
val seBuilder = SettingsEntryBuilder.createLinkTo("injection", name, pageArgs.normalize())
|
||||
seBuilder.setIsAllowSearch(false)
|
||||
|
||||
seBuilder.setUiLayoutFn {
|
||||
val summaryArray = listOf(
|
||||
"$STRING_PARAM_NAME=" + pageArgs.getStringArg(STRING_PARAM_NAME)!!,
|
||||
"$INT_PARAM_NAME=" + pageArgs.getIntArg(INT_PARAM_NAME)!!
|
||||
)
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = TITLE
|
||||
override val summary = summaryArray.joinToString(", ").toState()
|
||||
override val onClick = navigator(name + pageArgs.navLink())
|
||||
})
|
||||
}
|
||||
|
||||
return seBuilder
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Page(arguments: Bundle?) {
|
||||
ArgumentPage(
|
||||
stringParam = arguments!!.getString(STRING_PARAM_NAME, "default"),
|
||||
intParam = arguments.getInt(INT_PARAM_NAME),
|
||||
)
|
||||
RegularScaffold(title = TITLE) {
|
||||
for (entry in buildEntry(arguments)) {
|
||||
entry.uiLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EntryItem(stringParam: String, intParam: Int) {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = TITLE
|
||||
override val summary =
|
||||
"$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam".toState()
|
||||
override val onClick = navigator("$name/$stringParam/$intParam")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ArgumentPage(stringParam: String, intParam: Int) {
|
||||
RegularScaffold(title = TITLE) {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "String param value"
|
||||
override val summary = stringParam.toState()
|
||||
})
|
||||
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Int param value"
|
||||
override val summary = intParam.toString().toState()
|
||||
})
|
||||
|
||||
ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = intParam + 1)
|
||||
|
||||
ArgumentPageProvider.EntryItem(stringParam = "bar", intParam = intParam + 1)
|
||||
buildInjectEntry(buildArgument(stringParam, intParam))?.build()?.uiLayout?.let { it() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +118,26 @@ fun ArgumentPage(stringParam: String, intParam: Int) {
|
||||
@Composable
|
||||
private fun ArgumentPagePreview() {
|
||||
SettingsTheme {
|
||||
ArgumentPage(stringParam = "foo", intParam = 0)
|
||||
ArgumentPageProvider.Page(buildArgument(stringParam = "foo", intParam = 0))
|
||||
}
|
||||
}
|
||||
|
||||
private fun PageArguments.isValid(): Boolean {
|
||||
val stringParam = getStringArg(STRING_PARAM_NAME)
|
||||
return (stringParam != null && listOf("foo", "bar").contains(stringParam))
|
||||
}
|
||||
|
||||
private fun PageArguments.buildNextArg(stringParam: String): Bundle {
|
||||
val intParam = getIntArg(INT_PARAM_NAME)
|
||||
return if (intParam == null)
|
||||
buildArgument(stringParam)
|
||||
else
|
||||
buildArgument(stringParam, intParam + 1)
|
||||
}
|
||||
|
||||
private fun buildArgument(stringParam: String, intParam: Int? = null): Bundle {
|
||||
val args = Bundle()
|
||||
args.putString(STRING_PARAM_NAME, stringParam)
|
||||
if (intParam != null) args.putInt(INT_PARAM_NAME, intParam)
|
||||
return args
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.spa.framework.common
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import androidx.navigation.NavType
|
||||
|
||||
class PageArguments(
|
||||
private val navArgsDef: List<NamedNavArgument>,
|
||||
private val rawArgs: Bundle?
|
||||
) {
|
||||
fun normalize(): Bundle {
|
||||
if (rawArgs == null) return Bundle.EMPTY
|
||||
val normArgs = Bundle()
|
||||
for (navArg in navArgsDef) {
|
||||
when (navArg.argument.type) {
|
||||
NavType.StringType -> {
|
||||
val value = rawArgs.getString(navArg.name)
|
||||
if (value != null) normArgs.putString(navArg.name, value)
|
||||
}
|
||||
NavType.IntType -> {
|
||||
if (rawArgs.containsKey(navArg.name))
|
||||
normArgs.putInt(navArg.name, rawArgs.getInt(navArg.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
return normArgs
|
||||
}
|
||||
|
||||
fun navLink(): String{
|
||||
if (rawArgs == null) return ""
|
||||
val argsArray = mutableListOf<String>()
|
||||
for (navArg in navArgsDef) {
|
||||
when (navArg.argument.type) {
|
||||
NavType.StringType -> {
|
||||
argsArray.add(rawArgs.getString(navArg.name, ""))
|
||||
}
|
||||
NavType.IntType -> {
|
||||
argsArray.add(rawArgs.getInt(navArg.name).toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
return argsArray.joinToString("") {arg -> "/$arg" }
|
||||
}
|
||||
|
||||
fun getStringArg(key: String): String? {
|
||||
if (rawArgs != null && rawArgs.containsKey(key)) {
|
||||
return rawArgs.getString(key)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getIntArg(key: String): Int? {
|
||||
if (rawArgs != null && rawArgs.containsKey(key)) {
|
||||
return rawArgs.getInt(key)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.spa.framework.common
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
/**
|
||||
* Defines data of one Settings entry for Settings search.
|
||||
*/
|
||||
data class SearchData(val keyword: String = "")
|
||||
|
||||
/**
|
||||
* Defines data of one Settings entry for UI rendering.
|
||||
*/
|
||||
data class UiData(val title: String = "")
|
||||
|
||||
/**
|
||||
* Defines data to identify a Settings page.
|
||||
*/
|
||||
data class SettingsPage(val name: String = "", val args: Bundle = Bundle.EMPTY)
|
||||
|
||||
/**
|
||||
* Defines data of a Settings entry.
|
||||
*/
|
||||
data class SettingsEntry(
|
||||
val name: String,
|
||||
val owner: SettingsPage,
|
||||
|
||||
// Generates the unique id of this entry
|
||||
val uniqueId: String,
|
||||
|
||||
// Defines linking of Settings entries
|
||||
val fromPage: SettingsPage? = null,
|
||||
val toPage: SettingsPage? = null,
|
||||
|
||||
/**
|
||||
* ========================================
|
||||
* Defines entry attributes here.
|
||||
* ========================================
|
||||
*/
|
||||
val isAllowSearch: Boolean,
|
||||
|
||||
/**
|
||||
* ========================================
|
||||
* Defines entry APIs to get data here.
|
||||
* ========================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* API to get Search related data for this entry.
|
||||
* Returns null if this entry is not available for the search at the moment.
|
||||
*/
|
||||
val searchData: () -> SearchData? = { null },
|
||||
|
||||
/**
|
||||
* API to get UI related data for this entry.
|
||||
* Returns null if the entry is not render-able.
|
||||
*/
|
||||
val uiData: () -> UiData? = { null },
|
||||
|
||||
/**
|
||||
* API to Render UI of this entry directly. For now, we use it in the internal injection, to
|
||||
* support the case that the injection page owner wants to maintain both data and UI of the
|
||||
* injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and
|
||||
* use each entries' UI rendering function in the page instead.
|
||||
*/
|
||||
val uiLayout: (@Composable () -> Unit) = {},
|
||||
)
|
||||
|
||||
/**
|
||||
* The helper to build a Settings Page instance.
|
||||
*/
|
||||
class SettingsPageBuilder(private val name: String) {
|
||||
private val arguments = Bundle()
|
||||
|
||||
fun pushArgs(args: Bundle?): SettingsPageBuilder {
|
||||
if (args != null) arguments.putAll(args)
|
||||
return this
|
||||
}
|
||||
|
||||
fun pushArg(argName: String, argValue: String?): SettingsPageBuilder {
|
||||
if (argValue != null) this.arguments.putString(argName, argValue)
|
||||
return this
|
||||
}
|
||||
|
||||
fun pushArg(argName: String, argValue: Int?): SettingsPageBuilder {
|
||||
if (argValue != null) this.arguments.putInt(argName, argValue)
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): SettingsPage {
|
||||
return SettingsPage(name, arguments)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(name: String, args: Bundle? = null): SettingsPageBuilder {
|
||||
return SettingsPageBuilder(name).apply {
|
||||
pushArgs(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The helper to build a Settings Entry instance.
|
||||
*/
|
||||
class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) {
|
||||
private var uniqueId: String? = null
|
||||
private var fromPage: SettingsPage? = null
|
||||
private var toPage: SettingsPage? = null
|
||||
private var isAllowSearch: Boolean? = null
|
||||
|
||||
private var searchDataFn: () -> SearchData? = { null }
|
||||
private var uiLayoutFn: (@Composable () -> Unit) = {}
|
||||
|
||||
fun build(): SettingsEntry {
|
||||
return SettingsEntry(
|
||||
name = name,
|
||||
owner = owner,
|
||||
uniqueId = computeUniqueId(),
|
||||
|
||||
// linking data
|
||||
fromPage = fromPage,
|
||||
toPage = toPage,
|
||||
|
||||
// attributes
|
||||
isAllowSearch = computeSearchable(),
|
||||
|
||||
// functions
|
||||
searchData = searchDataFn,
|
||||
uiLayout = uiLayoutFn,
|
||||
)
|
||||
}
|
||||
|
||||
fun setLink(
|
||||
fromPage: SettingsPage? = null,
|
||||
toPage: SettingsPage? = null
|
||||
): SettingsEntryBuilder {
|
||||
if (fromPage != null) this.fromPage = fromPage
|
||||
if (toPage != null) this.toPage = toPage
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder {
|
||||
this.isAllowSearch = isAllowSearch
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSearchDataFn(fn: () -> SearchData?): SettingsEntryBuilder {
|
||||
this.searchDataFn = fn
|
||||
return this
|
||||
}
|
||||
|
||||
fun setUiLayoutFn(fn: @Composable () -> Unit): SettingsEntryBuilder {
|
||||
this.uiLayoutFn = fn
|
||||
return this
|
||||
}
|
||||
|
||||
private fun computeUniqueId(): String = uniqueId ?: (name + owner.toString())
|
||||
|
||||
private fun computeSearchable(): Boolean = isAllowSearch ?: false
|
||||
|
||||
companion object {
|
||||
fun create(
|
||||
entryName: String,
|
||||
ownerPageName: String,
|
||||
ownerPageArgs: Bundle? = null
|
||||
): SettingsEntryBuilder {
|
||||
val owner = SettingsPageBuilder.create(ownerPageName, ownerPageArgs)
|
||||
return SettingsEntryBuilder(entryName, owner.build())
|
||||
}
|
||||
|
||||
fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
|
||||
return SettingsEntryBuilder(entryName, owner)
|
||||
}
|
||||
|
||||
fun createLinkTo(
|
||||
entryName: String,
|
||||
ownerPageName: String,
|
||||
ownerPageArgs: Bundle? = null
|
||||
): SettingsEntryBuilder {
|
||||
val owner = SettingsPageBuilder.create(ownerPageName, ownerPageArgs)
|
||||
return SettingsEntryBuilder(entryName, owner.build()).setLink(toPage = owner.build())
|
||||
}
|
||||
|
||||
fun createLinkTo(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
|
||||
return SettingsEntryBuilder(entryName, owner).setLink(toPage = owner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,5 +36,5 @@ interface SettingsPageProvider {
|
||||
@Composable
|
||||
fun Page(arguments: Bundle?)
|
||||
|
||||
// fun buildEntry( arguments: Bundle?) : List<entry>
|
||||
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user