diff options
6 files changed, 145 insertions, 12 deletions
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml index 0d08d682b4ac..0d1a1fe312e2 100644 --- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml +++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml @@ -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> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt index af30e79f45b5..f7f01eaa4a93 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt @@ -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) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt index 4b2c8e41a388..c75f41b30d3e 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt @@ -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, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt index 47abc87327ad..b900b6413d0a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt @@ -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, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt index 59b413cef56e..123354f371f3 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt @@ -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, + ) + } + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt index a92f8713308f..06936e155b06 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/PreferenceTest.kt @@ -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 + } } |