Merge "Let Preference support single line summary"

This commit is contained in:
TreeHugger Robot
2022-10-10 04:02:48 +00:00
committed by Android (Google) Code Review
6 changed files with 145 additions and 12 deletions

View File

@@ -19,4 +19,9 @@
<string name="app_label" translatable="false">Gallery</string>
<!-- Gallery App name. [DO NOT TRANSLATE] -->
<string name="app_name" translatable="false">SpaLib Gallery</string>
<!-- Title for single line summary preference. [DO NOT TRANSLATE] -->
<string name="single_line_summary_preference_title" translatable="false">Preference (singleLineSummary = true)</string>
<!-- Summary for single line summary preference. [DO NOT TRANSLATE] -->
<string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
</resources>

View File

@@ -24,12 +24,15 @@ import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
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.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.toState
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.createSettingsPage
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
@@ -55,6 +58,7 @@ object PreferencePageProvider : SettingsPageProvider {
enum class EntryEnum(val displayName: String) {
SIMPLE_PREFERENCE("preference"),
SUMMARY_PREFERENCE("preference_with_summary"),
SINGLE_LINE_SUMMARY_PREFERENCE("preference_with_single_line_summary"),
DISABLED_PREFERENCE("preference_disable"),
ASYNC_SUMMARY_PREFERENCE("preference_with_async_summary"),
MANUAL_UPDATE_PREFERENCE("preference_actionable"),
@@ -92,6 +96,7 @@ object PreferencePageProvider : SettingsPageProvider {
}
.build()
)
entryList.add(singleLineSummaryEntry())
entryList.add(
createEntry(EntryEnum.DISABLED_PREFERENCE)
.setIsAllowSearch(true)
@@ -163,6 +168,21 @@ object PreferencePageProvider : SettingsPageProvider {
return entryList
}
private fun singleLineSummaryEntry() = createEntry(EntryEnum.SINGLE_LINE_SUMMARY_PREFERENCE)
.setIsAllowSearch(true)
.setUiLayoutFn {
Preference(
model = object : PreferenceModel {
override val title: String =
stringResource(R.string.single_line_summary_preference_title)
override val summary =
stringResource(R.string.single_line_summary_preference_summary).toState()
},
singleLineSummary = true,
)
}
.build()
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = owner)
.setIsAllowSearch(true)

View File

@@ -34,7 +34,8 @@ internal fun BasePreference(
title: String,
summary: State<String>,
modifier: Modifier = Modifier,
icon: (@Composable () -> Unit)? = null,
singleLineSummary: Boolean = false,
icon: @Composable (() -> Unit)? = null,
enabled: State<Boolean> = true.toState(),
paddingStart: Dp = SettingsDimension.itemPaddingStart,
paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
@@ -43,7 +44,13 @@ internal fun BasePreference(
) {
BaseLayout(
title = title,
subTitle = { SettingsBody(summary) },
subTitle = {
if (singleLineSummary) {
SettingsBody(body = summary, maxLines = 1)
} else {
SettingsBody(body = summary)
}
},
modifier = modifier,
icon = icon,
enabled = enabled,

View File

@@ -101,7 +101,10 @@ interface PreferenceModel {
* Data is provided through [PreferenceModel].
*/
@Composable
fun Preference(model: PreferenceModel) {
fun Preference(
model: PreferenceModel,
singleLineSummary: Boolean = false,
) {
val modifier = remember(model.enabled.value, model.onClick) {
model.onClick?.let { onClick ->
Modifier.clickable(enabled = model.enabled.value, onClick = onClick)
@@ -110,6 +113,7 @@ fun Preference(model: PreferenceModel) {
BasePreference(
title = model.title,
summary = model.summary,
singleLineSummary = singleLineSummary,
modifier = modifier,
icon = model.icon,
enabled = model.enabled,

View File

@@ -17,13 +17,19 @@
package com.android.settingslib.spa.widget.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
fun SettingsTitle(title: State<String>) {
@@ -40,17 +46,25 @@ fun SettingsTitle(title: String) {
}
@Composable
fun SettingsBody(body: State<String>) {
SettingsBody(body.value)
fun SettingsBody(
body: State<String>,
maxLines: Int = Int.MAX_VALUE,
) {
SettingsBody(body = body.value, maxLines = maxLines)
}
@Composable
fun SettingsBody(body: String) {
fun SettingsBody(
body: String,
maxLines: Int = Int.MAX_VALUE,
) {
if (body.isNotEmpty()) {
Text(
text = body,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis,
maxLines = maxLines,
)
}
}
@@ -68,3 +82,19 @@ fun PlaceholderTitle(title: String) {
)
}
}
@Preview
@Composable
private fun BasePreferencePreview() {
SettingsTheme {
Column(Modifier.width(100.dp)) {
SettingsBody(
body = "Long long long long long long text",
)
SettingsBody(
body = "Long long long long long long text",
maxLines = 1,
)
}
}
}

View File

@@ -16,17 +16,27 @@
package com.android.settingslib.spa.widget.preference
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.assertHeightIsAtLeast
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.toState
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,11 +50,61 @@ class PreferenceTest {
fun title_displayed() {
composeTestRule.setContent {
Preference(object : PreferenceModel {
override val title = "Preference"
override val title = TITLE
})
}
composeTestRule.onNodeWithText("Preference").assertIsDisplayed()
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
fun longSummary_notSingleLine_atLeastTwoLinesHeight() {
var lineHeightDp: Dp = Dp.Unspecified
composeTestRule.setContent {
Box(Modifier.width(BOX_WIDTH)) {
Preference(object : PreferenceModel {
override val title = TITLE
override val summary = LONG_SUMMARY.toState()
})
}
lineHeightDp = with(LocalDensity.current) {
MaterialTheme.typography.bodyMedium.lineHeight.toDp()
}
}
composeTestRule.onNodeWithText(LONG_SUMMARY).assertHeightIsAtLeast(lineHeightDp.times(2))
}
@Test
fun longSummary_notSingleLine_onlyOneLineHeight() {
var lineHeightDp: Dp = Dp.Unspecified
composeTestRule.setContent {
Box(Modifier.width(BOX_WIDTH)) {
Preference(
model = object : PreferenceModel {
override val title = TITLE
override val summary = LONG_SUMMARY.toState()
},
singleLineSummary = true,
)
}
lineHeightDp = with(LocalDensity.current) {
MaterialTheme.typography.bodyMedium.lineHeight.toDp()
}
}
val summaryNode = composeTestRule.onNodeWithText(LONG_SUMMARY)
try {
// There is no assertHeightIsAtMost, so use the assertHeightIsAtLeast and catch the
// expected exception.
summaryNode.assertHeightIsAtLeast(lineHeightDp.times(2))
} catch (e: AssertionError) {
assertThat(e).hasMessageThat().contains("height")
return
}
fail("Expect AssertionError")
}
@Test
@@ -52,13 +112,13 @@ class PreferenceTest {
composeTestRule.setContent {
var count by remember { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = "Preference"
override val title = TITLE
override val summary = derivedStateOf { count.toString() }
override val onClick: (() -> Unit) = { count++ }
})
}
composeTestRule.onNodeWithText("Preference").performClick()
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onNodeWithText("1").assertIsDisplayed()
}
@@ -67,14 +127,21 @@ class PreferenceTest {
composeTestRule.setContent {
var count by remember { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = "Preference"
override val title = TITLE
override val summary = derivedStateOf { count.toString() }
override val enabled = false.toState()
override val onClick: (() -> Unit) = { count++ }
})
}
composeTestRule.onNodeWithText("Preference").performClick()
composeTestRule.onNodeWithText(TITLE).performClick()
composeTestRule.onNodeWithText("0").assertIsDisplayed()
}
companion object {
private const val TITLE = "Title"
private const val LONG_SUMMARY =
"Long long long long long long long long long long long long long long long summary"
private val BOX_WIDTH = 100.dp
}
}