diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index e9b2e1041c222..3e652517270da 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -117,6 +117,8 @@ " • " Sign-in options + + View more For %1$s diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt index 7a720b1e858bb..1707a11dde4db 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -52,6 +52,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection @@ -80,6 +81,7 @@ fun Entry( /** If true, draws a trailing lock icon. */ isLockedAuthEntry: Boolean = false, enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, ) { val iconPadding = Modifier.wrapContentSize().padding( // Horizontal padding should be 16dp, but the suggestion chip itself @@ -104,7 +106,11 @@ fun Entry( ) { // Apply weight so that the trailing icon can always show. Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) { - SmallTitleText(text = entryHeadlineText, enforceOneLine = enforceOneLine) + SmallTitleText( + text = entryHeadlineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) if (passwordValue != null) { Row( modifier = Modifier.fillMaxWidth().padding(top = 4.dp), @@ -142,10 +148,18 @@ fun Entry( ) } } else if (entrySecondLineText != null) { - BodySmallText(text = entrySecondLineText, enforceOneLine = enforceOneLine) + BodySmallText( + text = entrySecondLineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) } if (entryThirdLineText != null) { - BodySmallText(text = entryThirdLineText, enforceOneLine = enforceOneLine) + BodySmallText( + text = entryThirdLineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) } } if (isLockedAuthEntry) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt index 22871bcbe767a..90c863467a07e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt @@ -22,6 +22,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -58,14 +59,20 @@ fun BodyMediumText(text: String, modifier: Modifier = Modifier) { * Body-small typography; on-surface-variant color. */ @Composable -fun BodySmallText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) { +fun BodySmallText( + text: String, + modifier: Modifier = Modifier, + enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, +) { Text( modifier = modifier.wrapContentSize(), text = text, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall, overflow = TextOverflow.Ellipsis, - maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE + maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE, + onTextLayout = onTextLayout, ) } @@ -86,14 +93,20 @@ fun LargeTitleText(text: String, modifier: Modifier = Modifier) { * Title-small typography; on-surface color. */ @Composable -fun SmallTitleText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) { +fun SmallTitleText( + text: String, + modifier: Modifier = Modifier, + enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, +) { Text( modifier = modifier.wrapContentSize(), text = text, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.titleSmall, overflow = TextOverflow.Ellipsis, - maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE + maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE, + onTextLayout = onTextLayout, ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 6d7ecd70eb0bb..c1ea1d8d17461 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -34,11 +34,15 @@ import androidx.compose.material3.Divider import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel @@ -158,6 +162,7 @@ fun PrimarySelectionCard( onMoreOptionSelected: () -> Unit, onLog: @Composable (UiEventEnum) -> Unit, ) { + val showMoreForTruncatedEntry = remember { mutableStateOf(false) } val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList @@ -209,6 +214,8 @@ fun PrimarySelectionCard( Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size val authenticationEntrySize = authenticationEntryList.size + // If true, render a view more button for the single truncated entry on the + // front page. // Show max 4 entries in this primary page if (usernameForCredentialSize + authenticationEntrySize <= 4) { sortedUserNameToCredentialEntryList.forEach { @@ -216,6 +223,9 @@ fun PrimarySelectionCard( credentialEntryInfo = it.sortedCredentialEntryList.first(), onEntrySelected = onEntrySelected, enforceOneLine = true, + onTextLayout = { + showMoreForTruncatedEntry.value = it.hasVisualOverflow + } ) } authenticationEntryList.forEach { @@ -269,6 +279,13 @@ fun PrimarySelectionCard( onMoreOptionSelected ) } + } else if (showMoreForTruncatedEntry.value) { + { + ActionButton( + stringResource(R.string.button_label_view_more), + onMoreOptionSelected + ) + } } else null, rightButton = if (activeEntry != null) { // Only one sign-in options exist { @@ -438,6 +455,7 @@ fun CredentialEntryRow( credentialEntryInfo: CredentialEntryInfo, onEntrySelected: (BaseEntry) -> Unit, enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, ) { Entry( onClick = { onEntrySelected(credentialEntryInfo) }, @@ -463,6 +481,7 @@ fun CredentialEntryRow( ) }, enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, ) }