Merge "Let Preference support single line summary"
This commit is contained in:
committed by
Android (Google) Code Review
commit
0a54f2f4cb
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user