From df0103f8822f62d2522757547c1d227fdf9887f9 Mon Sep 17 00:00:00 2001 From: Zekan Qian Date: Mon, 29 Aug 2022 13:19:11 +0800 Subject: [PATCH] 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 --- .../spa/gallery/page/ArgumentPage.kt | 115 +++++++--- .../spa/framework/common/PageArguments.kt | 74 +++++++ .../spa/framework/common/SettingsEntry.kt | 205 ++++++++++++++++++ .../framework/common/SettingsPageProvider.kt | 2 +- 4 files changed, 365 insertions(+), 31 deletions(-) create mode 100644 packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt create mode 100644 packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt index bd366c3d8518c..92b64fb42abf0 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt @@ -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 { + val pageArgs = PageArguments(ArgumentPageProvider.arguments, arguments) + if (!pageArgs.isValid()) return emptyList() + + val owner = SettingsPageBuilder.create(name, pageArgs.normalize()).build() + val entryList = mutableListOf() + 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 +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt new file mode 100644 index 0000000000000..c0f585ffa595b --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/PageArguments.kt @@ -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, + 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() + 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 + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt new file mode 100644 index 0000000000000..6fcd555f0df69 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -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) + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt index 2ab0cb9c67b99..a57c66e14c3aa 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt @@ -36,5 +36,5 @@ interface SettingsPageProvider { @Composable fun Page(arguments: Bundle?) - // fun buildEntry( arguments: Bundle?) : List + fun buildEntry(arguments: Bundle?): List = emptyList() }