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