Merge "Defines SettingsEntry and use it in ArgumentPage"

This commit is contained in:
Chaohui Wang
2022-09-01 11:18:32 +00:00
committed by Android (Google) Code Review
4 changed files with 365 additions and 31 deletions

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -36,5 +36,5 @@ interface SettingsPageProvider {
@Composable
fun Page(arguments: Bundle?)
// fun buildEntry( arguments: Bundle?) : List<entry>
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
}