Support StaticData / SearchData in SettingsEntry.
Add StaticData / SearchData API in SettingsEntry. Introduce SppEnum & EntryEnum, which includes name (for logging) and displayName. Refactor ArgumentPage & PreferencePage to use above APIs. Bug: 244122804 Test: manual - build Spa gallery Change-Id: I172ee08ee7b0e1e09337f289406f57b2caf9937e
This commit is contained in:
@@ -18,4 +18,8 @@ package com.android.settingslib.spa.gallery
|
||||
|
||||
import com.android.settingslib.spa.framework.DebugActivity
|
||||
|
||||
class GalleryDebugActivity : DebugActivity(SpaEnvironment.EntryRepository, MainActivity::class.java)
|
||||
class GalleryDebugActivity : DebugActivity(
|
||||
SpaEnvironment.EntryRepository,
|
||||
MainActivity::class.java,
|
||||
"com.android.spa.gallery.provider",
|
||||
)
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settingslib.spa.gallery
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
|
||||
import com.android.settingslib.spa.framework.common.SettingsPage
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
|
||||
@@ -34,6 +36,32 @@ import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferenceP
|
||||
import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
|
||||
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
|
||||
|
||||
/**
|
||||
* Enum to define all SPP name here.
|
||||
* Since the SPP name would be used in log, DO NOT change it once it is set. One can still change
|
||||
* the display name for better readability if necessary.
|
||||
*/
|
||||
enum class SettingsPageProviderEnum(val displayName: String) {
|
||||
HOME("home"),
|
||||
PREFERENCE("preference"),
|
||||
ARGUMENT("argument"),
|
||||
|
||||
// Add your SPPs
|
||||
}
|
||||
|
||||
fun createSettingsPage(
|
||||
SppName: SettingsPageProviderEnum,
|
||||
parameter: List<NamedNavArgument> = emptyList(),
|
||||
arguments: Bundle? = null
|
||||
): SettingsPage {
|
||||
return SettingsPage(
|
||||
name = SppName.name,
|
||||
displayName = SppName.displayName,
|
||||
parameter = parameter,
|
||||
arguments = arguments,
|
||||
)
|
||||
}
|
||||
|
||||
object SpaEnvironment {
|
||||
val PageProviderRepository: SettingsPageProviderRepository by
|
||||
lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||
@@ -55,7 +83,7 @@ object SpaEnvironment {
|
||||
ActionButtonPageProvider,
|
||||
),
|
||||
rootPages = listOf(
|
||||
SettingsPage.create(HomePageProvider.name)
|
||||
createSettingsPage(SettingsPageProviderEnum.HOME)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ import androidx.compose.runtime.Composable
|
||||
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.SettingsPage
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
import com.android.settingslib.spa.gallery.R
|
||||
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
|
||||
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
|
||||
import com.android.settingslib.spa.gallery.page.ArgumentPageModel
|
||||
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
|
||||
@@ -38,20 +38,19 @@ import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
|
||||
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
|
||||
|
||||
object HomePageProvider : SettingsPageProvider {
|
||||
override val name = "Home"
|
||||
override val name = SettingsPageProviderEnum.HOME.name
|
||||
|
||||
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
|
||||
val owner = SettingsPage.create(name)
|
||||
return listOf(
|
||||
PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
|
||||
ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
|
||||
SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
|
||||
SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
|
||||
SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
|
||||
FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
|
||||
IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
|
||||
CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
|
||||
ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
|
||||
PreferenceMainPageProvider.buildInjectEntry().build(),
|
||||
ArgumentPageProvider.buildInjectEntry("foo")!!.build(),
|
||||
SliderPageProvider.buildInjectEntry().build(),
|
||||
SpinnerPageProvider.buildInjectEntry().build(),
|
||||
SettingsPagerPageProvider.buildInjectEntry().build(),
|
||||
FooterPageProvider.buildInjectEntry().build(),
|
||||
IllustrationPageProvider.buildInjectEntry().build(),
|
||||
CategoryPageProvider.buildInjectEntry().build(),
|
||||
ActionButtonPageProvider.buildInjectEntry().build(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,7 +58,7 @@ object HomePageProvider : SettingsPageProvider {
|
||||
override fun Page(arguments: Bundle?) {
|
||||
HomeScaffold(title = stringResource(R.string.app_name)) {
|
||||
for (entry in buildEntry(arguments)) {
|
||||
if (entry.name.startsWith(ArgumentPageModel.name)) {
|
||||
if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
|
||||
entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
|
||||
} else {
|
||||
entry.UiLayout()
|
||||
|
||||
@@ -24,23 +24,38 @@ 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.gallery.SettingsPageProviderEnum
|
||||
import com.android.settingslib.spa.gallery.createSettingsPage
|
||||
import com.android.settingslib.spa.widget.preference.Preference
|
||||
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
|
||||
|
||||
object ArgumentPageProvider : SettingsPageProvider {
|
||||
override val name = ArgumentPageModel.name
|
||||
// Defines all entry name in this page.
|
||||
// Note that entry name would be used in log. DO NOT change it once it is set.
|
||||
// One can still change the display name for better readability if necessary.
|
||||
private enum class EntryEnum(val displayName: String) {
|
||||
STRING_PARAM("string_param"),
|
||||
INT_PARAM("int_param"),
|
||||
}
|
||||
|
||||
private fun createEntry(owner: SettingsPage, entry: EntryEnum): SettingsEntryBuilder {
|
||||
return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
|
||||
}
|
||||
|
||||
override val name = SettingsPageProviderEnum.ARGUMENT.name
|
||||
|
||||
override val parameter = ArgumentPageModel.parameter
|
||||
|
||||
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
|
||||
if (!ArgumentPageModel.isValidArgument(arguments)) return emptyList()
|
||||
|
||||
val owner = SettingsPage.create(name, parameter, arguments)
|
||||
val owner = createSettingsPage(SettingsPageProviderEnum.ARGUMENT, parameter, arguments)
|
||||
val entryList = mutableListOf<SettingsEntry>()
|
||||
entryList.add(
|
||||
SettingsEntryBuilder.create("string_param", owner)
|
||||
createEntry(owner, EntryEnum.STRING_PARAM)
|
||||
// Set attributes
|
||||
.setIsAllowSearch(true)
|
||||
.setSearchDataFn { ArgumentPageModel.genStringParamSearchData() }
|
||||
.setUiLayoutFn {
|
||||
// Set ui rendering
|
||||
Preference(ArgumentPageModel.create(it).genStringParamPreferenceModel())
|
||||
@@ -48,9 +63,10 @@ object ArgumentPageProvider : SettingsPageProvider {
|
||||
)
|
||||
|
||||
entryList.add(
|
||||
SettingsEntryBuilder.create("int_param", owner)
|
||||
createEntry(owner, EntryEnum.INT_PARAM)
|
||||
// Set attributes
|
||||
.setIsAllowSearch(true)
|
||||
.setSearchDataFn { ArgumentPageModel.genIntParamSearchData() }
|
||||
.setUiLayoutFn {
|
||||
// Set ui rendering
|
||||
Preference(ArgumentPageModel.create(it).genIntParamPreferenceModel())
|
||||
@@ -68,11 +84,12 @@ object ArgumentPageProvider : SettingsPageProvider {
|
||||
if (!ArgumentPageModel.isValidArgument(arguments)) return null
|
||||
|
||||
return SettingsEntryBuilder.createInject(
|
||||
entryName = "${name}_$stringParam",
|
||||
owner = SettingsPage.create(name, parameter, arguments)
|
||||
owner = createSettingsPage(SettingsPageProviderEnum.ARGUMENT, parameter, arguments),
|
||||
displayName = "${name}_$stringParam",
|
||||
)
|
||||
// Set attributes
|
||||
.setIsAllowSearch(false)
|
||||
.setSearchDataFn { ArgumentPageModel.genInjectSearchData() }
|
||||
.setUiLayoutFn {
|
||||
// Set ui rendering
|
||||
Preference(ArgumentPageModel.create(it).genInjectPreferenceModel())
|
||||
@@ -83,7 +100,7 @@ object ArgumentPageProvider : SettingsPageProvider {
|
||||
override fun Page(arguments: Bundle?) {
|
||||
RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) {
|
||||
for (entry in buildEntry(arguments)) {
|
||||
if (entry.name.startsWith(name)) {
|
||||
if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
|
||||
entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
|
||||
} else {
|
||||
entry.UiLayout()
|
||||
|
||||
@@ -23,22 +23,28 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.navArgument
|
||||
import com.android.settingslib.spa.framework.BrowseActivity
|
||||
import com.android.settingslib.spa.framework.common.EntrySearchData
|
||||
import com.android.settingslib.spa.framework.common.PageModel
|
||||
import com.android.settingslib.spa.framework.compose.navigator
|
||||
import com.android.settingslib.spa.framework.compose.stateOf
|
||||
import com.android.settingslib.spa.framework.util.getIntArg
|
||||
import com.android.settingslib.spa.framework.util.getStringArg
|
||||
import com.android.settingslib.spa.framework.util.navLink
|
||||
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
|
||||
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
||||
|
||||
private const val TITLE = "Sample page with arguments"
|
||||
// Defines all the resources for this page.
|
||||
// In real Settings App, resources data is defined in xml, rather than SPP.
|
||||
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 val ARGUMENT_PAGE_KEYWORDS = listOf("argument keyword1", "argument keyword2")
|
||||
|
||||
class ArgumentPageModel : PageModel() {
|
||||
|
||||
companion object {
|
||||
const val name = "Argument"
|
||||
val parameter = listOf(
|
||||
navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
|
||||
navArgument(INT_PARAM_NAME) { type = NavType.IntType },
|
||||
@@ -62,6 +68,18 @@ class ArgumentPageModel : PageModel() {
|
||||
return (stringParam != null && listOf("foo", "bar").contains(stringParam))
|
||||
}
|
||||
|
||||
fun genStringParamSearchData(): EntrySearchData {
|
||||
return EntrySearchData(title = STRING_PARAM_TITLE)
|
||||
}
|
||||
|
||||
fun genIntParamSearchData(): EntrySearchData {
|
||||
return EntrySearchData(title = INT_PARAM_TITLE)
|
||||
}
|
||||
|
||||
fun genInjectSearchData(): EntrySearchData {
|
||||
return EntrySearchData(title = PAGE_TITLE, keyword = ARGUMENT_PAGE_KEYWORDS)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun create(arguments: Bundle?): ArgumentPageModel {
|
||||
val pageModel: ArgumentPageModel = viewModel(key = arguments.toString())
|
||||
@@ -70,7 +88,7 @@ class ArgumentPageModel : PageModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private val title = TITLE
|
||||
private val title = PAGE_TITLE
|
||||
private var arguments: Bundle? = null
|
||||
private var stringParam: String? = null
|
||||
private var intParam: Int? = null
|
||||
@@ -92,7 +110,7 @@ class ArgumentPageModel : PageModel() {
|
||||
@Composable
|
||||
fun genStringParamPreferenceModel(): PreferenceModel {
|
||||
return object : PreferenceModel {
|
||||
override val title = "String param value"
|
||||
override val title = STRING_PARAM_TITLE
|
||||
override val summary = stateOf(stringParam!!)
|
||||
}
|
||||
}
|
||||
@@ -100,7 +118,7 @@ class ArgumentPageModel : PageModel() {
|
||||
@Composable
|
||||
fun genIntParamPreferenceModel(): PreferenceModel {
|
||||
return object : PreferenceModel {
|
||||
override val title = "Int param value"
|
||||
override val title = INT_PARAM_TITLE
|
||||
override val summary = stateOf(intParam!!.toString())
|
||||
}
|
||||
}
|
||||
@@ -114,7 +132,8 @@ class ArgumentPageModel : PageModel() {
|
||||
return object : PreferenceModel {
|
||||
override val title = genPageTitle()
|
||||
override val summary = stateOf(summaryArray.joinToString(", "))
|
||||
override val onClick = navigator(name + parameter.navLink(arguments))
|
||||
override val onClick = navigator(
|
||||
SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,132 +18,166 @@ package com.android.settingslib.spa.gallery.preference
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Autorenew
|
||||
import androidx.compose.material.icons.outlined.DisabledByDefault
|
||||
import androidx.compose.material.icons.outlined.TouchApp
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.android.settingslib.spa.framework.common.EntrySearchData
|
||||
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.compose.navigator
|
||||
import com.android.settingslib.spa.framework.compose.toState
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
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
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_TITLE
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.MANUAL_UPDATE_PREFERENCE_TITLE
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.PAGE_TITLE
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
|
||||
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.logMsg
|
||||
import com.android.settingslib.spa.widget.preference.Preference
|
||||
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
||||
import com.android.settingslib.spa.widget.preference.SimplePreferenceMarco
|
||||
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
|
||||
import com.android.settingslib.spa.widget.ui.SettingsIcon
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
private const val TITLE = "Sample Preference"
|
||||
|
||||
object PreferencePageProvider : SettingsPageProvider {
|
||||
override val name = "Preference"
|
||||
// Defines all entry name in this page.
|
||||
// Note that entry name would be used in log. DO NOT change it once it is set.
|
||||
// One can still change the display name for better readability if necessary.
|
||||
enum class EntryEnum(val displayName: String) {
|
||||
SIMPLE_PREFERENCE("preference"),
|
||||
SUMMARY_PREFERENCE("preference_with_summary"),
|
||||
DISABLED_PREFERENCE("preference_disable"),
|
||||
ASYNC_SUMMARY_PREFERENCE("preference_with_async_summary"),
|
||||
MANUAL_UPDATE_PREFERENCE("preference_actionable"),
|
||||
AUTO_UPDATE_PREFERENCE("preference_auto_update"),
|
||||
}
|
||||
|
||||
override val name = SettingsPageProviderEnum.PREFERENCE.name
|
||||
private val owner = createSettingsPage(SettingsPageProviderEnum.PREFERENCE)
|
||||
|
||||
private fun createEntry(entry: EntryEnum): SettingsEntryBuilder {
|
||||
return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
|
||||
}
|
||||
|
||||
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
|
||||
val owner = SettingsPage.create(name)
|
||||
val entryList = mutableListOf<SettingsEntry>()
|
||||
entryList.add(
|
||||
SettingsEntryBuilder.create("Preference", owner)
|
||||
createEntry(EntryEnum.SIMPLE_PREFERENCE)
|
||||
.setIsAllowSearch(true)
|
||||
.setUiLayoutFn {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Preference"
|
||||
})
|
||||
}.build()
|
||||
.setMarco {
|
||||
logMsg("create marco for ${EntryEnum.SIMPLE_PREFERENCE}")
|
||||
SimplePreferenceMarco(title = SIMPLE_PREFERENCE_TITLE)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
entryList.add(
|
||||
SettingsEntryBuilder.create("Preference with summary", owner)
|
||||
createEntry(EntryEnum.SUMMARY_PREFERENCE)
|
||||
.setIsAllowSearch(true)
|
||||
.setUiLayoutFn {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Preference"
|
||||
override val summary = "With summary".toState()
|
||||
})
|
||||
}.build()
|
||||
.setMarco {
|
||||
logMsg("create marco for ${EntryEnum.SUMMARY_PREFERENCE}")
|
||||
SimplePreferenceMarco(
|
||||
title = SIMPLE_PREFERENCE_TITLE,
|
||||
summary = SIMPLE_PREFERENCE_SUMMARY,
|
||||
searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
|
||||
)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
entryList.add(
|
||||
SettingsEntryBuilder.create("Preference with async summary", owner)
|
||||
createEntry(EntryEnum.DISABLED_PREFERENCE)
|
||||
.setIsAllowSearch(true)
|
||||
.setMarco {
|
||||
logMsg("create marco for ${EntryEnum.DISABLED_PREFERENCE}")
|
||||
SimplePreferenceMarco(
|
||||
title = DISABLE_PREFERENCE_TITLE,
|
||||
summary = DISABLE_PREFERENCE_SUMMARY,
|
||||
disabled = true,
|
||||
icon = Icons.Outlined.DisabledByDefault,
|
||||
)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
entryList.add(
|
||||
createEntry(EntryEnum.ASYNC_SUMMARY_PREFERENCE)
|
||||
.setIsAllowSearch(true)
|
||||
.setSearchDataFn {
|
||||
EntrySearchData(title = ASYNC_PREFERENCE_TITLE)
|
||||
}
|
||||
.setUiLayoutFn {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Preference"
|
||||
override val summary = produceState(initialValue = " ") {
|
||||
delay(1000L)
|
||||
value = "Async summary"
|
||||
val model = PreferencePageModel.create()
|
||||
val asyncSummary = remember { model.getAsyncSummary() }
|
||||
Preference(
|
||||
object : PreferenceModel {
|
||||
override val title = ASYNC_PREFERENCE_TITLE
|
||||
override val summary = asyncSummary
|
||||
}
|
||||
})
|
||||
)
|
||||
}.build()
|
||||
)
|
||||
entryList.add(
|
||||
SettingsEntryBuilder.create("Click me", owner)
|
||||
createEntry(EntryEnum.MANUAL_UPDATE_PREFERENCE)
|
||||
.setIsAllowSearch(true)
|
||||
.setUiLayoutFn {
|
||||
var count by rememberSaveable { mutableStateOf(0) }
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Click me"
|
||||
override val summary = derivedStateOf { count.toString() }
|
||||
override val onClick: (() -> Unit) = { count++ }
|
||||
override val icon = @Composable {
|
||||
SettingsIcon(imageVector = Icons.Outlined.TouchApp)
|
||||
val model = PreferencePageModel.create()
|
||||
val manualUpdaterSummary = remember { model.getManualUpdaterSummary() }
|
||||
Preference(
|
||||
object : PreferenceModel {
|
||||
override val title = MANUAL_UPDATE_PREFERENCE_TITLE
|
||||
override val summary = manualUpdaterSummary
|
||||
override val onClick = { model.manualUpdaterOnClick() }
|
||||
override val icon = @Composable {
|
||||
SettingsIcon(imageVector = Icons.Outlined.TouchApp)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}.build()
|
||||
)
|
||||
entryList.add(
|
||||
SettingsEntryBuilder.create("Ticker", owner)
|
||||
createEntry(EntryEnum.AUTO_UPDATE_PREFERENCE)
|
||||
.setIsAllowSearch(true)
|
||||
.setUiLayoutFn {
|
||||
var ticks by rememberSaveable { mutableStateOf(0) }
|
||||
LaunchedEffect(ticks) {
|
||||
delay(1000L)
|
||||
ticks++
|
||||
}
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Ticker"
|
||||
override val summary = derivedStateOf { ticks.toString() }
|
||||
})
|
||||
}.build()
|
||||
)
|
||||
entryList.add(
|
||||
SettingsEntryBuilder.create("Disabled", owner)
|
||||
.setIsAllowSearch(true)
|
||||
.setUiLayoutFn {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "Disabled"
|
||||
override val summary = "Disabled".toState()
|
||||
override val enabled = false.toState()
|
||||
override val icon = @Composable {
|
||||
SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
|
||||
val model = PreferencePageModel.create()
|
||||
val autoUpdaterSummary = remember { model.getAutoUpdaterSummary() }
|
||||
Preference(
|
||||
object : PreferenceModel {
|
||||
override val title = AUTO_UPDATE_PREFERENCE_TITLE
|
||||
override val summary = autoUpdaterSummary.observeAsState(" ")
|
||||
override val icon = @Composable {
|
||||
SettingsIcon(imageVector = Icons.Outlined.Autorenew)
|
||||
}
|
||||
}
|
||||
})
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
|
||||
return entryList
|
||||
}
|
||||
|
||||
fun buildInjectEntry(): SettingsEntryBuilder {
|
||||
return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
|
||||
return SettingsEntryBuilder.createInject(owner = owner)
|
||||
.setIsAllowSearch(true)
|
||||
.setUiLayoutFn {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = TITLE
|
||||
override val onClick = navigator(name)
|
||||
})
|
||||
.setMarco {
|
||||
logMsg("create marco for INJECT entry")
|
||||
SimplePreferenceMarco(
|
||||
title = PAGE_TITLE,
|
||||
clickRoute = SettingsPageProviderEnum.PREFERENCE.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Page(arguments: Bundle?) {
|
||||
RegularScaffold(title = TITLE) {
|
||||
RegularScaffold(title = PAGE_TITLE) {
|
||||
for (entry in buildEntry(arguments)) {
|
||||
entry.UiLayout()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.gallery.preference
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.android.settingslib.spa.framework.common.PageModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PreferencePageModel : PageModel() {
|
||||
companion object {
|
||||
// Defines all the resources for this page.
|
||||
// In real Settings App, resources data is defined in xml, rather than SPP.
|
||||
const val PAGE_TITLE = "Sample Preference"
|
||||
const val SIMPLE_PREFERENCE_TITLE = "Preference"
|
||||
const val SIMPLE_PREFERENCE_SUMMARY = "Simple summary"
|
||||
const val DISABLE_PREFERENCE_TITLE = "Disabled"
|
||||
const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
|
||||
const val ASYNC_PREFERENCE_TITLE = "Async Preference"
|
||||
private const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
|
||||
const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
|
||||
const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
|
||||
val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
|
||||
|
||||
@Composable
|
||||
fun create(): PreferencePageModel {
|
||||
val pageModel: PreferencePageModel = viewModel()
|
||||
pageModel.initOnce()
|
||||
return pageModel
|
||||
}
|
||||
|
||||
fun logMsg(message: String) {
|
||||
Log.d("PreferencePageModel", message)
|
||||
}
|
||||
}
|
||||
|
||||
private val asyncSummary = mutableStateOf(" ")
|
||||
|
||||
private val manualUpdater = mutableStateOf(0)
|
||||
|
||||
private val autoUpdater = object : MutableLiveData<String>(" ") {
|
||||
private var tick = 0
|
||||
private var updateJob: Job? = null
|
||||
override fun onActive() {
|
||||
logMsg("autoUpdater.active")
|
||||
updateJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
while (true) {
|
||||
delay(1000L)
|
||||
tick++
|
||||
logMsg("autoUpdater.value $tick")
|
||||
postValue(tick.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
logMsg("autoUpdater.inactive")
|
||||
updateJob?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun initialize(arguments: Bundle?) {
|
||||
logMsg("init with args " + arguments.toString())
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
delay(2000L)
|
||||
asyncSummary.value = ASYNC_PREFERENCE_SUMMARY
|
||||
}
|
||||
}
|
||||
|
||||
fun getAsyncSummary(): State<String> {
|
||||
logMsg("getAsyncSummary")
|
||||
return asyncSummary
|
||||
}
|
||||
|
||||
fun getManualUpdaterSummary(): State<String> {
|
||||
logMsg("getManualUpdaterSummary")
|
||||
return derivedStateOf { manualUpdater.value.toString() }
|
||||
}
|
||||
|
||||
fun manualUpdaterOnClick() {
|
||||
logMsg("manualUpdaterOnClick")
|
||||
manualUpdater.value = manualUpdater.value + 1
|
||||
}
|
||||
|
||||
fun getAutoUpdaterSummary(): LiveData<String> {
|
||||
logMsg("getAutoUpdaterSummary")
|
||||
return autoUpdater
|
||||
}
|
||||
}
|
||||
@@ -31,12 +31,22 @@ 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.ROOT_PAGE_NAME
|
||||
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
|
||||
import com.android.settingslib.spa.framework.compose.localNavController
|
||||
import com.android.settingslib.spa.framework.theme.SettingsTheme
|
||||
import com.android.settingslib.spa.framework.util.navRoute
|
||||
|
||||
const val NULL_PAGE_NAME = "NULL"
|
||||
|
||||
/**
|
||||
* The Activity to render ALL SPA pages, and handles jumps between SPA pages.
|
||||
* One can open any SPA page by:
|
||||
* $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination <SpaPageRoute>
|
||||
* For gallery, BrowseActivityComponent = com.android.settingslib.spa.gallery/.MainActivity
|
||||
* Some examples:
|
||||
* $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination HOME
|
||||
* $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination ARGUMENT/bar/5
|
||||
*/
|
||||
open class BrowseActivity(
|
||||
private val sppRepository: SettingsPageProviderRepository,
|
||||
) : ComponentActivity() {
|
||||
@@ -55,8 +65,8 @@ open class BrowseActivity(
|
||||
private fun MainContent() {
|
||||
val navController = rememberNavController()
|
||||
CompositionLocalProvider(navController.localNavController()) {
|
||||
NavHost(navController, ROOT_PAGE_NAME) {
|
||||
composable(ROOT_PAGE_NAME) {}
|
||||
NavHost(navController, NULL_PAGE_NAME) {
|
||||
composable(NULL_PAGE_NAME) {}
|
||||
for (page in sppRepository.getAllProviders()) {
|
||||
composable(
|
||||
route = page.name + page.parameter.navRoute() +
|
||||
@@ -82,7 +92,7 @@ open class BrowseActivity(
|
||||
destinationNavigated.value = true
|
||||
LaunchedEffect(Unit) {
|
||||
val destination =
|
||||
intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPageName()
|
||||
intent?.getStringExtra(KEY_DESTINATION) ?: sppRepository.getDefaultStartPage()
|
||||
if (destination.isNotEmpty()) {
|
||||
navController.navigate(destination) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.activity.compose.setContent
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
@@ -33,7 +34,6 @@ 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.EntryProvider.Companion.PAGE_INFO_QUERY
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntry
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
|
||||
import com.android.settingslib.spa.framework.common.SettingsPage
|
||||
@@ -54,6 +54,12 @@ private const val ROUTE_ENTRY = "entry"
|
||||
private const val PARAM_NAME_PAGE_ID = "pid"
|
||||
private const val PARAM_NAME_ENTRY_ID = "eid"
|
||||
|
||||
/**
|
||||
* The Debug Activity to display all Spa Pages & Entries.
|
||||
* One can open the debug activity by:
|
||||
* $ adb shell am start -n <Activity>
|
||||
* For gallery, Activity = com.android.settingslib.spa.gallery/.GalleryDebugActivity
|
||||
*/
|
||||
open class DebugActivity(
|
||||
private val entryRepository: SettingsEntryRepository,
|
||||
private val browseActivityClass: Class<*>,
|
||||
@@ -75,15 +81,16 @@ open class DebugActivity(
|
||||
if (entryProviderAuthorities == null) return
|
||||
|
||||
try {
|
||||
val query = EntryProvider.QueryEnum.PAGE_INFO_QUERY
|
||||
contentResolver.query(
|
||||
Uri.parse("content://$entryProviderAuthorities/${PAGE_INFO_QUERY.queryPath}"),
|
||||
Uri.parse("content://$entryProviderAuthorities/${query.queryPath}"),
|
||||
null, null, null
|
||||
).use { cursor ->
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
val route = cursor.getString(PAGE_INFO_QUERY.getIndex(ColumnName.PAGE_ROUTE))
|
||||
val entryCount = cursor.getInt(PAGE_INFO_QUERY.getIndex(ColumnName.ENTRY_COUNT))
|
||||
val route = cursor.getString(query, EntryProvider.ColumnEnum.PAGE_ROUTE)
|
||||
val entryCount = cursor.getInt(query, EntryProvider.ColumnEnum.ENTRY_COUNT)
|
||||
val hasRuntimeParam =
|
||||
cursor.getInt(PAGE_INFO_QUERY.getIndex(ColumnName.HAS_RUNTIME_PARAM)) == 1
|
||||
cursor.getBoolean(query, EntryProvider.ColumnEnum.HAS_RUNTIME_PARAM)
|
||||
Log.d(
|
||||
"DEBUG ACTIVITY", "Page Info: $route ($entryCount) " +
|
||||
(if (hasRuntimeParam) "with" else "no") + "-runtime-params"
|
||||
@@ -106,13 +113,13 @@ open class DebugActivity(
|
||||
composable(
|
||||
route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}",
|
||||
arguments = listOf(
|
||||
navArgument(PARAM_NAME_PAGE_ID) { type = NavType.IntType },
|
||||
navArgument(PARAM_NAME_PAGE_ID) { type = NavType.StringType },
|
||||
)
|
||||
) { navBackStackEntry -> OnePage(navBackStackEntry.arguments) }
|
||||
composable(
|
||||
route = "$ROUTE_ENTRY/{$PARAM_NAME_ENTRY_ID}",
|
||||
arguments = listOf(
|
||||
navArgument(PARAM_NAME_ENTRY_ID) { type = NavType.IntType },
|
||||
navArgument(PARAM_NAME_ENTRY_ID) { type = NavType.StringType },
|
||||
)
|
||||
) { navBackStackEntry -> OneEntry(navBackStackEntry.arguments) }
|
||||
}
|
||||
@@ -121,13 +128,15 @@ open class DebugActivity(
|
||||
|
||||
@Composable
|
||||
fun RootPage() {
|
||||
val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
|
||||
val allEntry = remember { entryRepository.getAllEntries() }
|
||||
HomeScaffold(title = "Settings Debug") {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "List All Pages"
|
||||
override val title = "List All Pages (${allPageWithEntry.size})"
|
||||
override val onClick = navigator(route = ROUTE_All_PAGES)
|
||||
})
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = "List All Entries"
|
||||
override val title = "List All Entries (${allEntry.size})"
|
||||
override val onClick = navigator(route = ROUTE_All_ENTRIES)
|
||||
})
|
||||
}
|
||||
@@ -135,14 +144,15 @@ open class DebugActivity(
|
||||
|
||||
@Composable
|
||||
fun AllPages() {
|
||||
RegularScaffold(title = "All Pages") {
|
||||
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
|
||||
val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
|
||||
RegularScaffold(title = "All Pages (${allPageWithEntry.size})") {
|
||||
for (pageWithEntry in allPageWithEntry) {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title =
|
||||
"${pageWithEntry.page.name} (${pageWithEntry.entries.size})"
|
||||
"${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})"
|
||||
override val summary = pageWithEntry.page.formatArguments().toState()
|
||||
override val onClick =
|
||||
navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}")
|
||||
navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id()}")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -150,16 +160,18 @@ open class DebugActivity(
|
||||
|
||||
@Composable
|
||||
fun AllEntries() {
|
||||
RegularScaffold(title = "All Entries") {
|
||||
EntryList(entryRepository.getAllEntries())
|
||||
val allEntry = remember { entryRepository.getAllEntries() }
|
||||
RegularScaffold(title = "All Entries (${allEntry.size})") {
|
||||
EntryList(allEntry)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OnePage(arguments: Bundle?) {
|
||||
val id = arguments!!.getInt(PARAM_NAME_PAGE_ID)
|
||||
val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
|
||||
val pageWithEntry = entryRepository.getPageWithEntry(id)!!
|
||||
RegularScaffold(title = "Page ${pageWithEntry.page.name}") {
|
||||
RegularScaffold(title = "Page - ${pageWithEntry.page.displayName}") {
|
||||
Text(text = pageWithEntry.page.id().toString())
|
||||
Text(text = pageWithEntry.page.formatArguments())
|
||||
Text(text = "Entry size: ${pageWithEntry.entries.size}")
|
||||
Preference(model = object : PreferenceModel {
|
||||
@@ -173,15 +185,16 @@ open class DebugActivity(
|
||||
|
||||
@Composable
|
||||
fun OneEntry(arguments: Bundle?) {
|
||||
val id = arguments!!.getInt(PARAM_NAME_ENTRY_ID)
|
||||
val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
|
||||
val entry = entryRepository.getEntry(id)!!
|
||||
RegularScaffold(title = "Entry ${entry.displayName()}") {
|
||||
val entryContent = remember { entry.formatContent() }
|
||||
RegularScaffold(title = "Entry - ${entry.displayTitle()}") {
|
||||
Preference(model = object : PreferenceModel {
|
||||
override val title = "open entry"
|
||||
override val enabled = (!entry.hasRuntimeParam()).toState()
|
||||
override val onClick = openEntry(entry)
|
||||
})
|
||||
Text(text = entry.formatAll())
|
||||
Text(text = entryContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,10 +202,10 @@ open class DebugActivity(
|
||||
private fun EntryList(entries: Collection<SettingsEntry>) {
|
||||
for (entry in entries) {
|
||||
Preference(object : PreferenceModel {
|
||||
override val title = entry.displayName()
|
||||
override val title = entry.displayTitle()
|
||||
override val summary =
|
||||
"${entry.fromPage?.name} -> ${entry.toPage?.name}".toState()
|
||||
override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
|
||||
"${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
|
||||
override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id()}")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,35 +28,55 @@ import android.util.Log
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
|
||||
|
||||
/**
|
||||
* Enum to define all column names in provider.
|
||||
* The content provider to return entry related data, which can be used for search and hierarchy.
|
||||
* One can query the provider result by:
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
|
||||
* For gallery, AuthorityPath = com.android.spa.gallery.provider
|
||||
* Some examples:
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/page_start
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/page_info
|
||||
*/
|
||||
enum class ColumnName(val id: String) {
|
||||
PAGE_NAME("pageName"),
|
||||
PAGE_ROUTE("pageRoute"),
|
||||
ENTRY_COUNT("entryCount"),
|
||||
HAS_RUNTIME_PARAM("hasRuntimeParam"),
|
||||
PAGE_START_ADB("pageStartAdb"),
|
||||
}
|
||||
|
||||
data class QueryDefinition(
|
||||
val queryPath: String,
|
||||
val queryMatchCode: Int,
|
||||
val columnNames: List<ColumnName>,
|
||||
) {
|
||||
fun getColumns(): Array<String> {
|
||||
return columnNames.map { it.id }.toTypedArray()
|
||||
}
|
||||
|
||||
fun getIndex(name: ColumnName): Int {
|
||||
return columnNames.indexOf(name)
|
||||
}
|
||||
}
|
||||
|
||||
open class EntryProvider(
|
||||
private val entryRepository: SettingsEntryRepository,
|
||||
private val browseActivityComponentName: String? = null,
|
||||
) : ContentProvider() {
|
||||
|
||||
/**
|
||||
* Enum to define all column names in provider.
|
||||
*/
|
||||
enum class ColumnEnum(val id: String) {
|
||||
PAGE_ID("pageId"),
|
||||
PAGE_NAME("pageName"),
|
||||
PAGE_ROUTE("pageRoute"),
|
||||
ENTRY_COUNT("entryCount"),
|
||||
HAS_RUNTIME_PARAM("hasRuntimeParam"),
|
||||
PAGE_START_ADB("pageStartAdb"),
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum to define all queries supported in the provider.
|
||||
*/
|
||||
enum class QueryEnum(
|
||||
val queryPath: String,
|
||||
val queryMatchCode: Int,
|
||||
val columnNames: List<ColumnEnum>
|
||||
) {
|
||||
PAGE_START_COMMAND(
|
||||
"page_start", 1,
|
||||
listOf(ColumnEnum.PAGE_START_ADB)
|
||||
),
|
||||
PAGE_INFO_QUERY(
|
||||
"page_info", 2,
|
||||
listOf(
|
||||
ColumnEnum.PAGE_ID,
|
||||
ColumnEnum.PAGE_NAME,
|
||||
ColumnEnum.PAGE_ROUTE,
|
||||
ColumnEnum.ENTRY_COUNT,
|
||||
ColumnEnum.HAS_RUNTIME_PARAM,
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
private var mMatcher: UriMatcher? = null
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
@@ -92,13 +112,13 @@ open class EntryProvider(
|
||||
if (info != null) {
|
||||
mMatcher!!.addURI(
|
||||
info.authority,
|
||||
PAGE_START_COMMAND_QUERY.queryPath,
|
||||
PAGE_START_COMMAND_QUERY.queryMatchCode
|
||||
QueryEnum.PAGE_START_COMMAND.queryPath,
|
||||
QueryEnum.PAGE_START_COMMAND.queryMatchCode
|
||||
)
|
||||
mMatcher!!.addURI(
|
||||
info.authority,
|
||||
PAGE_INFO_QUERY.queryPath,
|
||||
PAGE_INFO_QUERY.queryMatchCode
|
||||
QueryEnum.PAGE_INFO_QUERY.queryPath,
|
||||
QueryEnum.PAGE_INFO_QUERY.queryMatchCode
|
||||
)
|
||||
}
|
||||
super.attachInfo(context, info)
|
||||
@@ -113,8 +133,8 @@ open class EntryProvider(
|
||||
): Cursor? {
|
||||
return try {
|
||||
when (mMatcher!!.match(uri)) {
|
||||
PAGE_START_COMMAND_QUERY.queryMatchCode -> queryPageStartCommand()
|
||||
PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
|
||||
QueryEnum.PAGE_START_COMMAND.queryMatchCode -> queryPageStartCommand()
|
||||
QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
|
||||
else -> throw UnsupportedOperationException("Unknown Uri $uri")
|
||||
}
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
@@ -126,13 +146,13 @@ open class EntryProvider(
|
||||
}
|
||||
|
||||
private fun queryPageStartCommand(): Cursor {
|
||||
val componentName = browseActivityComponentName ?: "[component-name]"
|
||||
val cursor = MatrixCursor(PAGE_START_COMMAND_QUERY.getColumns())
|
||||
val componentName = browseActivityComponentName ?: "<activity-component-name>"
|
||||
val cursor = MatrixCursor(QueryEnum.PAGE_START_COMMAND.getColumns())
|
||||
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
|
||||
val page = pageWithEntry.page
|
||||
if (!page.hasRuntimeParam()) {
|
||||
cursor.newRow().add(
|
||||
ColumnName.PAGE_START_ADB.id,
|
||||
ColumnEnum.PAGE_START_ADB.id,
|
||||
"adb shell am start -n $componentName" +
|
||||
" -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}"
|
||||
)
|
||||
@@ -142,31 +162,39 @@ open class EntryProvider(
|
||||
}
|
||||
|
||||
private fun queryPageInfo(): Cursor {
|
||||
val cursor = MatrixCursor(PAGE_INFO_QUERY.getColumns())
|
||||
val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
|
||||
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
|
||||
val page = pageWithEntry.page
|
||||
cursor.newRow().add(ColumnName.PAGE_NAME.id, page.name)
|
||||
.add(ColumnName.PAGE_ROUTE.id, page.buildRoute())
|
||||
.add(ColumnName.ENTRY_COUNT.id, pageWithEntry.entries.size)
|
||||
.add(ColumnName.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
|
||||
cursor.newRow()
|
||||
.add(ColumnEnum.PAGE_ID.id, page.id())
|
||||
.add(ColumnEnum.PAGE_NAME.id, page.displayName)
|
||||
.add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
|
||||
.add(ColumnEnum.ENTRY_COUNT.id, pageWithEntry.entries.size)
|
||||
.add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
|
||||
}
|
||||
return cursor
|
||||
}
|
||||
|
||||
companion object {
|
||||
val PAGE_START_COMMAND_QUERY = QueryDefinition(
|
||||
"page_start", 1,
|
||||
listOf(ColumnName.PAGE_START_ADB)
|
||||
)
|
||||
|
||||
val PAGE_INFO_QUERY = QueryDefinition(
|
||||
"page_info", 2,
|
||||
listOf(
|
||||
ColumnName.PAGE_NAME,
|
||||
ColumnName.PAGE_ROUTE,
|
||||
ColumnName.ENTRY_COUNT,
|
||||
ColumnName.HAS_RUNTIME_PARAM,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun EntryProvider.QueryEnum.getColumns(): Array<String> {
|
||||
return columnNames.map { it.id }.toTypedArray()
|
||||
}
|
||||
|
||||
fun EntryProvider.QueryEnum.getIndex(name: EntryProvider.ColumnEnum): Int {
|
||||
return columnNames.indexOf(name)
|
||||
}
|
||||
|
||||
fun Cursor.getString(query: EntryProvider.QueryEnum, columnName: EntryProvider.ColumnEnum): String {
|
||||
return this.getString(query.getIndex(columnName))
|
||||
}
|
||||
|
||||
fun Cursor.getInt(query: EntryProvider.QueryEnum, columnName: EntryProvider.ColumnEnum): Int {
|
||||
return this.getInt(query.getIndex(columnName))
|
||||
}
|
||||
|
||||
fun Cursor.getBoolean(
|
||||
query: EntryProvider.QueryEnum,
|
||||
columnName: EntryProvider.ColumnEnum
|
||||
): Boolean {
|
||||
return this.getInt(query.getIndex(columnName)) == 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 androidx.compose.runtime.Composable
|
||||
|
||||
/**
|
||||
* Defines interface of a entry marco, which contains all entry functions to support different
|
||||
* scenarios, such as browsing (UiLayout), search, etc.
|
||||
* SPA team will rebuild some entry marcos, in order to make the entry creation easier.
|
||||
*/
|
||||
interface EntryMarco {
|
||||
@Composable
|
||||
fun UiLayout() {}
|
||||
fun getSearchData(): EntrySearchData? = null
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Defines Search data of one Settings entry.
|
||||
*/
|
||||
data class EntrySearchData(
|
||||
val title: String = "",
|
||||
val keyword: List<String> = emptyList(),
|
||||
) {
|
||||
fun format(): String {
|
||||
val content = listOf(
|
||||
"search_title = $title",
|
||||
"search_keyword = $keyword",
|
||||
)
|
||||
return content.joinToString("\n")
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import androidx.lifecycle.ViewModel
|
||||
open class PageModel : ViewModel() {
|
||||
var initialized = false
|
||||
|
||||
fun initOnce(arguments: Bundle?) {
|
||||
fun initOnce(arguments: Bundle? = null) {
|
||||
// Initialize only once
|
||||
if (initialized) return
|
||||
initialized = true
|
||||
|
||||
@@ -18,84 +18,20 @@ package com.android.settingslib.spa.framework.common
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import androidx.navigation.NavType
|
||||
import com.android.settingslib.spa.framework.BrowseActivity
|
||||
import com.android.settingslib.spa.framework.util.navLink
|
||||
import androidx.compose.runtime.remember
|
||||
|
||||
const val INJECT_ENTRY_NAME = "INJECT"
|
||||
const val ROOT_ENTRY_NAME = "ROOT"
|
||||
const val ROOT_PAGE_NAME = "Root"
|
||||
|
||||
/**
|
||||
* 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(
|
||||
// The unique id of this page, which is computed by name + normalized(arguments)
|
||||
val id: Int,
|
||||
|
||||
// The name of the page, which is used to compute the unique id, and need to be stable.
|
||||
val name: String,
|
||||
|
||||
// Defined parameters of this page.
|
||||
val parameter: List<NamedNavArgument> = emptyList(),
|
||||
|
||||
// The arguments of this page.
|
||||
val arguments: Bundle? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun create(
|
||||
name: String,
|
||||
parameter: List<NamedNavArgument> = emptyList(),
|
||||
arguments: Bundle? = null
|
||||
): SettingsPage {
|
||||
return SettingsPageBuilder(name, parameter).setArguments(arguments).build()
|
||||
}
|
||||
}
|
||||
|
||||
fun formatArguments(): String {
|
||||
val normalizedArguments = parameter.normalize(arguments)
|
||||
if (normalizedArguments == null || normalizedArguments.isEmpty) return "[No arguments]"
|
||||
return normalizedArguments.toString().removeRange(0, 6)
|
||||
}
|
||||
|
||||
fun formatAll(): String {
|
||||
return "$name ${formatArguments()}"
|
||||
}
|
||||
|
||||
fun buildRoute(highlightEntryName: String? = null): String {
|
||||
val highlightParam =
|
||||
if (highlightEntryName == null)
|
||||
""
|
||||
else
|
||||
"?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryName"
|
||||
return name + parameter.navLink(arguments) + highlightParam
|
||||
}
|
||||
|
||||
fun hasRuntimeParam(): Boolean {
|
||||
return parameter.hasRuntimeParam(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines data of a Settings entry.
|
||||
*/
|
||||
data class SettingsEntry(
|
||||
// The unique id of this entry, which is computed by name + owner + fromPage + toPage.
|
||||
val id: Int,
|
||||
|
||||
// The name of the page, which is used to compute the unique id, and need to be stable.
|
||||
val name: String,
|
||||
private val name: String,
|
||||
|
||||
// The display name of the page, for better readability.
|
||||
val displayName: String,
|
||||
|
||||
// The owner page of this entry.
|
||||
val owner: SettingsPage,
|
||||
@@ -109,7 +45,7 @@ data class SettingsEntry(
|
||||
* Defines entry attributes here.
|
||||
* ========================================
|
||||
*/
|
||||
val isAllowSearch: Boolean,
|
||||
val isAllowSearch: Boolean = false,
|
||||
|
||||
/**
|
||||
* ========================================
|
||||
@@ -121,13 +57,7 @@ data class SettingsEntry(
|
||||
* 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 },
|
||||
private val searchDataImpl: (arguments: Bundle?) -> EntrySearchData? = { null },
|
||||
|
||||
/**
|
||||
* API to Render UI of this entry directly. For now, we use it in the internal injection, to
|
||||
@@ -135,68 +65,56 @@ data class SettingsEntry(
|
||||
* 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 uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
|
||||
private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
|
||||
) {
|
||||
fun formatAll(): String {
|
||||
// The unique id of this entry, which is computed by name + owner + fromPage + toPage.
|
||||
fun id(): String {
|
||||
return "$name:${owner.id()}(${fromPage?.id()}-${toPage?.id()})".toHashId()
|
||||
}
|
||||
|
||||
fun formatContent(): String {
|
||||
val content = listOf(
|
||||
"owner = ${owner.formatAll()}",
|
||||
"linkFrom = ${fromPage?.formatAll()}",
|
||||
"linkTo = ${toPage?.formatAll()}",
|
||||
"id = ${id()}",
|
||||
"owner = ${owner.formatDisplayTitle()}",
|
||||
"linkFrom = ${fromPage?.formatDisplayTitle()}",
|
||||
"linkTo = ${toPage?.formatDisplayTitle()}",
|
||||
"${getSearchData()?.format()}",
|
||||
)
|
||||
return content.joinToString("\n")
|
||||
}
|
||||
|
||||
// The display name of the entry, for better readability.
|
||||
fun displayName(): String {
|
||||
return "${owner.name}:$name"
|
||||
fun displayTitle(): String {
|
||||
return "${owner.displayName}:$displayName"
|
||||
}
|
||||
|
||||
private fun getDisplayPage(): SettingsPage {
|
||||
// Display the entry on its from-page, or on its owner page if the from-page is unset.
|
||||
private 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 getDisplayPage().buildRoute(name)
|
||||
return containerPage().buildRoute(id())
|
||||
}
|
||||
|
||||
fun hasRuntimeParam(): Boolean {
|
||||
return getDisplayPage().hasRuntimeParam()
|
||||
return containerPage().hasRuntimeParam()
|
||||
}
|
||||
|
||||
private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
|
||||
val arguments = Bundle()
|
||||
if (owner.arguments != null) arguments.putAll(owner.arguments)
|
||||
if (runtimeArguments != null) arguments.putAll(runtimeArguments)
|
||||
return arguments
|
||||
}
|
||||
|
||||
fun getSearchData(runtimeArguments: Bundle? = null): EntrySearchData? {
|
||||
return searchDataImpl(fullArgument(runtimeArguments))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UiLayout(runtimeArguments: Bundle? = null) {
|
||||
val arguments = Bundle()
|
||||
if (owner.arguments != null) arguments.putAll(owner.arguments)
|
||||
if (runtimeArguments != null) arguments.putAll(runtimeArguments)
|
||||
uiLayoutImpl(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
data class SettingsPageWithEntry(
|
||||
val page: SettingsPage,
|
||||
val entries: List<SettingsEntry>,
|
||||
)
|
||||
|
||||
class SettingsPageBuilder(
|
||||
private val name: String,
|
||||
private val parameter: List<NamedNavArgument> = emptyList()
|
||||
) {
|
||||
private var arguments: Bundle? = null
|
||||
|
||||
fun build(): SettingsPage {
|
||||
val normArguments = parameter.normalize(arguments)
|
||||
return SettingsPage(
|
||||
id = "$name:${normArguments?.toString()}".toUniqueId(),
|
||||
name = name,
|
||||
parameter = parameter,
|
||||
arguments = arguments,
|
||||
)
|
||||
}
|
||||
|
||||
fun setArguments(arguments: Bundle?): SettingsPageBuilder {
|
||||
this.arguments = arguments
|
||||
return this
|
||||
uiLayoutImpl(fullArgument(runtimeArguments))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,32 +122,41 @@ class SettingsPageBuilder(
|
||||
* The helper to build a Settings Entry instance.
|
||||
*/
|
||||
class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) {
|
||||
private var displayName = name
|
||||
private var fromPage: SettingsPage? = null
|
||||
private var toPage: SettingsPage? = null
|
||||
private var isAllowSearch: Boolean? = null
|
||||
|
||||
private var searchDataFn: () -> SearchData? = { null }
|
||||
private var uiLayoutFn: (@Composable (arguments: Bundle?) -> Unit) = {}
|
||||
// Attributes
|
||||
private var isAllowSearch: Boolean = false
|
||||
|
||||
// Functions
|
||||
private var searchDataFn: (arguments: Bundle?) -> EntrySearchData? = { null }
|
||||
private var uiLayoutFn: (@Composable (arguments: Bundle?) -> Unit) = { }
|
||||
|
||||
fun build(): SettingsEntry {
|
||||
return SettingsEntry(
|
||||
id = "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toUniqueId(),
|
||||
name = name,
|
||||
owner = owner,
|
||||
displayName = displayName,
|
||||
|
||||
// linking data
|
||||
fromPage = fromPage,
|
||||
toPage = toPage,
|
||||
|
||||
// attributes
|
||||
isAllowSearch = getIsSearchable(),
|
||||
isAllowSearch = isAllowSearch,
|
||||
|
||||
// functions
|
||||
searchData = searchDataFn,
|
||||
searchDataImpl = searchDataFn,
|
||||
uiLayoutImpl = uiLayoutFn,
|
||||
)
|
||||
}
|
||||
|
||||
fun setDisplayName(displayName: String): SettingsEntryBuilder {
|
||||
this.displayName = displayName
|
||||
return this
|
||||
}
|
||||
|
||||
fun setLink(
|
||||
fromPage: SettingsPage? = null,
|
||||
toPage: SettingsPage? = null
|
||||
@@ -244,7 +171,16 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSearchDataFn(fn: () -> SearchData?): SettingsEntryBuilder {
|
||||
fun setMarco(fn: (arguments: Bundle?) -> EntryMarco): SettingsEntryBuilder {
|
||||
setSearchDataFn { fn(it).getSearchData() }
|
||||
setUiLayoutFn {
|
||||
val marco = remember { fn(it) }
|
||||
marco.UiLayout()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSearchDataFn(fn: (arguments: Bundle?) -> EntrySearchData?): SettingsEntryBuilder {
|
||||
this.searchDataFn = fn
|
||||
return this
|
||||
}
|
||||
@@ -254,8 +190,6 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getIsSearchable(): Boolean = isAllowSearch ?: false
|
||||
|
||||
companion object {
|
||||
fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
|
||||
return SettingsEntryBuilder(entryName, owner)
|
||||
@@ -269,48 +203,19 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings
|
||||
return create(entryName, owner).setLink(toPage = owner)
|
||||
}
|
||||
|
||||
fun createInject(owner: SettingsPage, entryName: String? = null): SettingsEntryBuilder {
|
||||
val name = entryName ?: "${INJECT_ENTRY_NAME}_${owner.name}"
|
||||
return createLinkTo(name, owner)
|
||||
fun create(owner: SettingsPage, entryName: String, displayName: String? = null):
|
||||
SettingsEntryBuilder {
|
||||
return SettingsEntryBuilder(entryName, owner).setDisplayName(displayName ?: entryName)
|
||||
}
|
||||
|
||||
fun createRoot(owner: SettingsPage, entryName: String? = null): SettingsEntryBuilder {
|
||||
val name = entryName ?: "${ROOT_ENTRY_NAME}_${owner.name}"
|
||||
return createLinkTo(name, owner)
|
||||
fun createInject(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder {
|
||||
val name = displayName ?: "${INJECT_ENTRY_NAME}_${owner.displayName}"
|
||||
return createLinkTo(INJECT_ENTRY_NAME, owner).setDisplayName(name)
|
||||
}
|
||||
|
||||
fun createRoot(owner: SettingsPage, displayName: String? = null): SettingsEntryBuilder {
|
||||
val name = displayName ?: "${ROOT_ENTRY_NAME}_${owner.displayName}"
|
||||
return createLinkTo(ROOT_ENTRY_NAME, owner).setDisplayName(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.toUniqueId(): Int {
|
||||
return this.hashCode()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -21,15 +21,20 @@ import java.util.LinkedList
|
||||
|
||||
private const val MAX_ENTRY_SIZE = 5000
|
||||
|
||||
data class SettingsPageWithEntry(
|
||||
val page: SettingsPage,
|
||||
val entries: List<SettingsEntry>,
|
||||
)
|
||||
|
||||
/**
|
||||
* The repository to maintain all Settings entries
|
||||
*/
|
||||
class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
|
||||
// Map of entry unique Id to entry
|
||||
private val entryMap: Map<Int, SettingsEntry>
|
||||
private val entryMap: Map<String, SettingsEntry>
|
||||
|
||||
// Map of Settings page to its contained entries.
|
||||
private val pageWithEntryMap: Map<Int, SettingsPageWithEntry>
|
||||
private val pageWithEntryMap: Map<String, SettingsPageWithEntry>
|
||||
|
||||
init {
|
||||
logMsg("Initialize")
|
||||
@@ -39,23 +44,29 @@ class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
|
||||
val entryQueue = LinkedList<SettingsEntry>()
|
||||
for (page in sppRepository.getAllRootPages()) {
|
||||
val rootEntry = SettingsEntryBuilder.createRoot(owner = page).build()
|
||||
if (!entryMap.containsKey(rootEntry.id)) {
|
||||
val rootEntryId = rootEntry.id()
|
||||
if (!entryMap.containsKey(rootEntryId)) {
|
||||
entryQueue.push(rootEntry)
|
||||
entryMap.put(rootEntry.id, rootEntry)
|
||||
entryMap.put(rootEntryId, rootEntry)
|
||||
}
|
||||
}
|
||||
|
||||
while (entryQueue.isNotEmpty() && entryMap.size < MAX_ENTRY_SIZE) {
|
||||
val entry = entryQueue.pop()
|
||||
val page = entry.toPage
|
||||
if (page == null || pageWithEntryMap.containsKey(page.id)) continue
|
||||
val pageId = page?.id()
|
||||
if (pageId == null || pageWithEntryMap.containsKey(pageId)) continue
|
||||
val spp = sppRepository.getProviderOrNull(page.name) ?: continue
|
||||
val newEntries = spp.buildEntry(page.arguments)
|
||||
pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries)
|
||||
val newEntries = spp.buildEntry(page.arguments).map {
|
||||
// Set from-page if it is missing.
|
||||
if (it.fromPage == null) it.copy(fromPage = page) else it
|
||||
}
|
||||
pageWithEntryMap[pageId] = SettingsPageWithEntry(page, newEntries)
|
||||
for (newEntry in newEntries) {
|
||||
if (!entryMap.containsKey(newEntry.id)) {
|
||||
val newEntryId = newEntry.id()
|
||||
if (!entryMap.containsKey(newEntryId)) {
|
||||
entryQueue.push(newEntry)
|
||||
entryMap.put(newEntry.id, newEntry)
|
||||
entryMap.put(newEntryId, newEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +78,7 @@ class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
|
||||
return pageWithEntryMap.values
|
||||
}
|
||||
|
||||
fun getPageWithEntry(pageId: Int): SettingsPageWithEntry? {
|
||||
fun getPageWithEntry(pageId: String): SettingsPageWithEntry? {
|
||||
return pageWithEntryMap[pageId]
|
||||
}
|
||||
|
||||
@@ -75,7 +86,7 @@ class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
|
||||
return entryMap.values
|
||||
}
|
||||
|
||||
fun getEntry(entryId: Int): SettingsEntry? {
|
||||
fun getEntry(entryId: String): SettingsEntry? {
|
||||
return entryMap[entryId]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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
|
||||
import com.android.settingslib.spa.framework.BrowseActivity
|
||||
import com.android.settingslib.spa.framework.util.navLink
|
||||
|
||||
/**
|
||||
* Defines data to identify a Settings page.
|
||||
*/
|
||||
data class SettingsPage(
|
||||
// The name of the page, which is used to compute the unique id, and need to be stable.
|
||||
val name: String,
|
||||
|
||||
// The display name of the page, for better readability.
|
||||
val displayName: String,
|
||||
|
||||
// Defined parameters of this page.
|
||||
val parameter: List<NamedNavArgument> = emptyList(),
|
||||
|
||||
// The arguments of this page.
|
||||
val arguments: Bundle? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun create(
|
||||
name: String,
|
||||
parameter: List<NamedNavArgument> = emptyList(),
|
||||
arguments: Bundle? = null
|
||||
): SettingsPage {
|
||||
return SettingsPage(name, name, parameter, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
// The unique id of this page, which is computed by name + normalized(arguments)
|
||||
fun id(): String {
|
||||
val normArguments = parameter.normalize(arguments)
|
||||
return "$name:${normArguments?.toString()}".toHashId()
|
||||
}
|
||||
|
||||
// Returns if this Settings Page is created by the given Spp.
|
||||
fun isCreateBy(SppName: String): Boolean {
|
||||
return name == SppName
|
||||
}
|
||||
|
||||
fun formatArguments(): String {
|
||||
val normalizedArguments = parameter.normalize(arguments)
|
||||
if (normalizedArguments == null || normalizedArguments.isEmpty) return "[No arguments]"
|
||||
return normalizedArguments.toString().removeRange(0, 6)
|
||||
}
|
||||
|
||||
fun formatDisplayTitle(): String {
|
||||
return "$displayName ${formatArguments()}"
|
||||
}
|
||||
|
||||
fun buildRoute(highlightEntryName: String? = null): String {
|
||||
val highlightParam =
|
||||
if (highlightEntryName == null)
|
||||
""
|
||||
else
|
||||
"?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryName"
|
||||
return name + parameter.navLink(arguments) + highlightParam
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return this.hashCode().toString(36)
|
||||
}
|
||||
@@ -30,12 +30,8 @@ class SettingsPageProviderRepository(
|
||||
logMsg("Initialize Completed: ${pageProviderMap.size} spp")
|
||||
}
|
||||
|
||||
fun getDefaultStartPageName(): String {
|
||||
return if (rootPages.isNotEmpty()) {
|
||||
rootPages[0].name
|
||||
} else {
|
||||
""
|
||||
}
|
||||
fun getDefaultStartPage(): String {
|
||||
return if (rootPages.isEmpty()) "" else rootPages[0].buildRoute()
|
||||
}
|
||||
|
||||
fun getAllRootPages(): Collection<SettingsPage> {
|
||||
|
||||
@@ -49,7 +49,8 @@ val LocalNavController = compositionLocalOf<NavControllerWrapper> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun navigator(route: String): () -> Unit {
|
||||
fun navigator(route: String?): () -> Unit {
|
||||
if (route == null) return {}
|
||||
val navController = LocalNavController.current
|
||||
return { navController.navigate(route) }
|
||||
}
|
||||
|
||||
@@ -21,7 +21,39 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.android.settingslib.spa.framework.common.EntryMarco
|
||||
import com.android.settingslib.spa.framework.common.EntrySearchData
|
||||
import com.android.settingslib.spa.framework.compose.navigator
|
||||
import com.android.settingslib.spa.framework.compose.stateOf
|
||||
import com.android.settingslib.spa.widget.ui.createSettingsIcon
|
||||
|
||||
data class SimplePreferenceMarco(
|
||||
val title: String,
|
||||
val summary: String? = null,
|
||||
val icon: ImageVector? = null,
|
||||
val disabled: Boolean = false,
|
||||
val clickRoute: String? = null,
|
||||
val searchKeywords: List<String> = emptyList(),
|
||||
) : EntryMarco {
|
||||
@Composable
|
||||
override fun UiLayout() {
|
||||
Preference(model = object : PreferenceModel {
|
||||
override val title: String = this@SimplePreferenceMarco.title
|
||||
override val summary = stateOf(this@SimplePreferenceMarco.summary ?: "")
|
||||
override val icon = createSettingsIcon(this@SimplePreferenceMarco.icon)
|
||||
override val enabled = stateOf(!this@SimplePreferenceMarco.disabled)
|
||||
override val onClick = navigator(clickRoute)
|
||||
})
|
||||
}
|
||||
|
||||
override fun getSearchData(): EntrySearchData {
|
||||
return EntrySearchData(
|
||||
title = this@SimplePreferenceMarco.title,
|
||||
keyword = searchKeywords
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The widget model for [Preference] widget.
|
||||
@@ -71,9 +103,9 @@ interface PreferenceModel {
|
||||
@Composable
|
||||
fun Preference(model: PreferenceModel) {
|
||||
val modifier = remember(model.enabled.value, model.onClick) {
|
||||
model.onClick?.let { onClick ->
|
||||
Modifier.clickable(enabled = model.enabled.value, onClick = onClick)
|
||||
} ?: Modifier
|
||||
model.onClick?.let { onClick ->
|
||||
Modifier.clickable(enabled = model.enabled.value, onClick = onClick)
|
||||
} ?: Modifier
|
||||
}
|
||||
BasePreference(
|
||||
title = model.title,
|
||||
|
||||
@@ -33,3 +33,8 @@ fun SettingsIcon(imageVector: ImageVector) {
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
|
||||
fun createSettingsIcon(imageVector: ImageVector?): (@Composable () -> Unit)? {
|
||||
if (imageVector == null) return null
|
||||
return { SettingsIcon(imageVector = imageVector) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user