diff options
30 files changed, 1818 insertions, 1381 deletions
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 3eb58f1532d8..ee512427fa9c 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -94,6 +94,8 @@ <string name="accessibility_back_arrow_button">"Go back to the previous page"</string> <!-- Spoken content description of the close "X" icon button. --> <string name="accessibility_close_button">Close</string> + <!-- Spoken content description of the close "X" icon button. [CHAR LIMIT=NONE] --> + <string name="accessibility_snackbar_dismiss">Dismiss</string> <!-- Strings for the get flow. --> <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] --> diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml index a58a0383b9b2..c7e47962472f 100644 --- a/packages/CredentialManager/res/values/themes.xml +++ b/packages/CredentialManager/res/values/themes.xml @@ -1,13 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material"> - <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowIsTranslucent">true</item> - <item name="android:colorBackgroundCacheHint">@null</item> - <item name="fontFamily">google-sans</item> + <item name="android:statusBarColor">@android:color/transparent</item> + <item name="android:navigationBarColor">@android:color/transparent</item> </style> </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 30b97bf60d8a..89ce4511b47a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -16,8 +16,6 @@ package com.android.credentialmanager -import android.app.slice.Slice -import android.app.slice.SliceSpec import android.content.Context import android.content.Intent import android.credentials.CreateCredentialRequest @@ -35,7 +33,6 @@ import android.credentials.ui.RequestInfo import android.credentials.ui.BaseDialogResult import android.credentials.ui.ProviderPendingIntentResponse import android.credentials.ui.UserSelectionDialogResult -import android.net.Uri import android.os.IBinder import android.os.Binder import android.os.Bundle @@ -230,7 +227,8 @@ class CredentialManagerRepo( context, "key1", "subkey-1", "elisa.beckett@gmail.com", 20, 7, 27, Instant.ofEpochSecond(10L), - "Legal note" + "You can use your passkey on this or other devices. It is saved to " + + "the Password Manager for elisa.beckett@gmail.com." ), CreateTestUtils.newCreateEntry( context, @@ -239,11 +237,9 @@ class CredentialManagerRepo( null ), ) - ) - .setRemoteEntry( - newRemoteEntry("key2", "subkey-1") - ) - .build(), + ).setRemoteEntry( + CreateTestUtils.newRemoteCreateEntry(context, "key2", "subkey-1") + ).build(), CreateCredentialProviderData .Builder("com.dashlane") .setSaveEntries( @@ -258,11 +254,11 @@ class CredentialManagerRepo( context, "key1", "subkey-4", "elisa.work@dashlane.com", 20, 7, 27, Instant.ofEpochSecond(14L), - null + "You can use your passkey on this or other devices. It is saved to " + + "the Password Manager for elisa.work@dashlane.com" ), ) - ) - .build(), + ).build(), ) } @@ -318,7 +314,7 @@ class CredentialManagerRepo( ), ) ).setRemoteEntry( - newRemoteEntry("key4", "subkey-1") + GetTestUtils.newRemoteCredentialEntry(context, "key4", "subkey-1") ).build(), GetCredentialProviderData.Builder("com.dashlane") .setCredentialEntries( @@ -333,10 +329,12 @@ class CredentialManagerRepo( ), ) ).setAuthenticationEntries( - listOf(GetTestUtils.newAuthenticationEntry( - context, "key2", "subkey-1", "foo@email.com", - AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT - )) + listOf( + GetTestUtils.newAuthenticationEntry( + context, "key2", "subkey-1", "foo@email.com", + AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT, + ) + ) ).setActionChips( listOf( GetTestUtils.newActionEntry( @@ -348,20 +346,6 @@ class CredentialManagerRepo( ) } - - private fun newRemoteEntry( - key: String, - subkey: String, - ): Entry { - return Entry( - key, - subkey, - Slice.Builder( - Uri.EMPTY, SliceSpec("type", 1) - ).build() - ) - } - private fun testCreatePasskeyRequestInfo(): RequestInfo { val request = CreatePublicKeyCredentialRequest( "{\"extensions\": {\n" + diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index d618e746f4b8..a3e4c81571a1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -39,7 +39,7 @@ import com.android.credentialmanager.createflow.CreateCredentialScreen import com.android.credentialmanager.createflow.hasContentToDisplay import com.android.credentialmanager.getflow.GetCredentialScreen import com.android.credentialmanager.getflow.hasContentToDisplay -import com.android.credentialmanager.ui.theme.CredentialSelectorTheme +import com.android.credentialmanager.ui.theme.PlatformTheme @ExperimentalMaterialApi class CredentialSelectorActivity : ComponentActivity() { @@ -50,7 +50,7 @@ class CredentialSelectorActivity : ComponentActivity() { val userConfigRepo = UserConfigRepo(this) val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo) setContent { - CredentialSelectorTheme { + PlatformTheme { CredentialManagerBottomSheet( credManRepo, userConfigRepo diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index aa0959c82b4b..ccfc132364a5 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -287,6 +287,7 @@ class GetFlowUtils { pendingIntent = structuredAuthEntry.pendingIntent, fillInIntent = entry.frameworkExtrasIntent, title = title, + providerDisplayName = providerDisplayName, icon = providerIcon, isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED, isLastUnlocked = diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt index eb3d188eab07..9216429eac52 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt @@ -29,6 +29,8 @@ import android.provider.Settings import androidx.credentials.provider.CreateEntry import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry +import androidx.credentials.provider.RemoteCreateEntry +import androidx.credentials.provider.RemoteCredentialEntry import java.time.Instant @@ -69,6 +71,21 @@ class GetTestUtils { ) } + internal fun newRemoteCredentialEntry( + context: Context, + key: String, + subkey: String, + ): Entry { + val intent = Intent(Settings.ACTION_SYNC_SETTINGS) + val pendingIntent = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + return Entry( + key, + subkey, + RemoteCredentialEntry(pendingIntent).slice + ) + } + internal fun newActionEntry( context: Context, key: String, @@ -203,5 +220,20 @@ class CreateTestUtils { Intent() ) } + + internal fun newRemoteCreateEntry( + context: Context, + key: String, + subkey: String, + ): Entry { + val intent = Intent(Settings.ACTION_SYNC_SETTINGS) + val pendingIntent = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + return Entry( + key, + subkey, + RemoteCreateEntry(pendingIntent).slice + ) + } } }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt index 58edb25336f8..335d58ac85f6 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt @@ -451,7 +451,7 @@ private fun Modifier.bottomSheetSwipeable( } @Composable -private fun Scrim( +internal fun Scrim( color: Color, onDismiss: () -> Unit, visible: Boolean diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt index 984057a1c960..b94840f369e1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt @@ -16,6 +16,7 @@ package com.android.credentialmanager.common.ui +import androidx.compose.foundation.layout.padding import com.android.credentialmanager.R import androidx.compose.material.Icon import androidx.compose.material.IconButton @@ -24,7 +25,6 @@ import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -32,17 +32,22 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme @Composable fun ActionButton(text: String, onClick: () -> Unit) { TextButton( + modifier = Modifier.padding(vertical = 4.dp), onClick = onClick, colors = ButtonDefaults.textButtonColors( contentColor = MaterialTheme.colorScheme.primary, ) ) { - Text(text = text) + LargeLabelText( + text = text, + modifier = Modifier.padding(vertical = 10.dp, horizontal = 12.dp), + ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt index 85e5c1ee69d5..a622e07d8bb1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt @@ -16,33 +16,73 @@ package com.android.credentialmanager.common.ui -import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp /** - * By default the card is filled with surfaceVariant color. This container card instead fills the - * background color with surface corlor. + * Container card for the whole sheet. + * + * Surface 1 color. No vertical padding. 24dp horizontal padding. 24dp bottom padding. 24dp top + * padding if [topAppBar] is not present, and none otherwise. + */ +@Composable +fun SheetContainerCard( + topAppBar: (@Composable () -> Unit)? = null, + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + Card( + modifier = modifier.fillMaxWidth().wrapContentHeight(), + border = null, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + ElevationTokens.Level1 + ), + ), + ) { + if (topAppBar != null) { + topAppBar() + } + Column( + modifier = Modifier.padding( + start = 24.dp, + end = 24.dp, + bottom = 18.dp, + top = if (topAppBar == null) 24.dp else 0.dp + ).fillMaxWidth().wrapContentHeight(), + horizontalAlignment = Alignment.CenterHorizontally, + content = content, + ) + } +} + +/** + * Container card for the entries. + * + * Surface 3 color. No padding. Four rounded corner shape. */ @Composable -fun ContainerCard( +fun CredentialContainerCard( modifier: Modifier = Modifier, - shape: Shape = CardDefaults.shape, - border: BorderStroke? = null, content: @Composable ColumnScope.() -> Unit, ) { Card( - modifier = modifier.fillMaxWidth(), - shape = shape, - border = border, + modifier = modifier.fillMaxWidth().wrapContentHeight(), + shape = MaterialTheme.shapes.medium, + border = null, colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface, + containerColor = Color.Transparent, ), content = content, ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt new file mode 100644 index 000000000000..b2489fd57b5d --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.credentialmanager.common.ui + +import androidx.compose.material3.ColorScheme +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.ln + +fun ColorScheme.surfaceColorAtElevation(elevation: Dp): Color { + if (elevation == 0.dp) return surface + val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f + return surfaceTint.copy(alpha = alpha).compositeOver(surface) +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Columns.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Columns.kt new file mode 100644 index 000000000000..23ad53c007da --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Columns.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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.credentialmanager.common.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp + +@Composable +fun EntryListColumn(content: LazyListScope.() -> Unit) { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(2.dp), + content = content, + ) +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt index d8ee750a88d6..8f48f6bf7b23 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt @@ -16,21 +16,28 @@ package com.android.credentialmanager.common.ui +import androidx.compose.foundation.layout.padding import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +/** Primary container color; label-large button text; on-primary button text color. */ @Composable fun ConfirmButton(text: String, onClick: () -> Unit) { FilledTonalButton( + modifier = Modifier.padding(vertical = 4.dp), onClick = onClick, colors = ButtonDefaults.filledTonalButtonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, ) ) { - Text(text = text) + LargeLabelText( + text = text, + modifier = Modifier.padding(vertical = 10.dp, horizontal = 24.dp), + ) } }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt new file mode 100644 index 000000000000..e1e666ef908b --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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.credentialmanager.common.ui + +import androidx.compose.ui.unit.dp + +/** Copied from androidx.compose.material3.tokens. */ +internal object ElevationTokens { + val Level0 = 0.0.dp + val Level1 = 1.0.dp + val Level2 = 3.0.dp + val Level3 = 6.0.dp + val Level4 = 8.0.dp + val Level5 = 12.0.dp +}
\ No newline at end of file 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 aefd534da63c..0eaaf970f37f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -16,55 +16,314 @@ package com.android.credentialmanager.common.ui +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.outlined.Lock import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SuggestionChip import androidx.compose.material3.SuggestionChipDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import com.android.credentialmanager.R import com.android.credentialmanager.ui.theme.EntryShape +import com.android.credentialmanager.ui.theme.Shapes -@OptIn(ExperimentalMaterial3Api::class) @Composable fun Entry( - onClick: () -> Unit, - label: @Composable () -> Unit, modifier: Modifier = Modifier, - icon: @Composable (() -> Unit)? = null, + onClick: () -> Unit, + entryHeadlineText: String, + entrySecondLineText: String? = null, + entryThirdLineText: String? = null, + /** Supply one and only one of the [iconImageBitmap], [iconImageVector], or [iconPainter] for + * drawing the leading icon. */ + iconImageBitmap: ImageBitmap? = null, + shouldApplyIconImageBitmapTint: Boolean = false, + iconImageVector: ImageVector? = null, + iconPainter: Painter? = null, + /** This will replace the [entrySecondLineText] value and render the text along with a + * mask on / off toggle for hiding / displaying the password value. */ + passwordValue: String? = null, + /** If true, draws a trailing lock icon. */ + isLockedAuthEntry: Boolean = false, ) { SuggestionChip( - modifier = modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth().wrapContentHeight(), onClick = onClick, shape = EntryShape.FullSmallRoundedCorner, - label = label, - icon = icon, + label = { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.wrapContentSize()) { + SmallTitleText(entryHeadlineText) + if (passwordValue != null) { + Row(modifier = Modifier.fillMaxWidth()) { + val visualTransformation = remember { PasswordVisualTransformation() } + val originalPassword by remember { + mutableStateOf(passwordValue) + } + val displayedPassword = remember { + mutableStateOf( + visualTransformation.filter( + AnnotatedString(originalPassword) + ).text.text + ) + } + BodySmallText(displayedPassword.value) + ToggleVisibilityButton( + modifier = Modifier.padding(start = 5.dp).size(24.dp), + onToggle = { + if (it) { + displayedPassword.value = originalPassword + } else { + displayedPassword.value = visualTransformation.filter( + AnnotatedString(originalPassword) + ).text.text + } + }, + ) + } + } else if (entrySecondLineText != null) { + BodySmallText(entrySecondLineText) + } + if (entryThirdLineText != null) { + BodySmallText(entryThirdLineText) + } + } + if (isLockedAuthEntry) { + Box(modifier = Modifier.wrapContentSize().padding(end = 16.dp)) { + Icon( + imageVector = Icons.Outlined.Lock, + // Decorative purpose only. + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + }, + icon = + if (iconImageBitmap != null) { + if (shouldApplyIconImageBitmapTint) { + { + Box(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + Icon( + modifier = Modifier.size(24.dp), + bitmap = iconImageBitmap, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + // Decorative purpose only. + contentDescription = null, + ) + } + } + } else { + { + Box(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + Image( + modifier = Modifier.size(24.dp), + bitmap = iconImageBitmap, + // Decorative purpose only. + contentDescription = null, + ) + } + } + } + } else if (iconImageVector != null) { + { + Box(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = iconImageVector, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + // Decorative purpose only. + contentDescription = null, + ) + } + } + } else if (iconPainter != null) { + { + Box(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + Icon( + modifier = Modifier.size(24.dp), + painter = iconPainter, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + // Decorative purpose only. + contentDescription = null, + ) + } + } + } else { + null + }, border = null, colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, + containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( + ElevationTokens.Level3 + ), + // TODO: remove? labelColor = MaterialTheme.colorScheme.onSurfaceVariant, iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, ), ) } -@OptIn(ExperimentalMaterial3Api::class) +/** + * A variation of the normal entry in that its background is transparent and the paddings are + * different (no horizontal padding). + */ @Composable -fun TransparentBackgroundEntry( +fun ActionEntry( onClick: () -> Unit, - label: @Composable () -> Unit, - modifier: Modifier = Modifier, - icon: @Composable (() -> Unit)? = null, + entryHeadlineText: String, + entrySecondLineText: String? = null, + iconImageBitmap: ImageBitmap, ) { SuggestionChip( - modifier = modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().wrapContentHeight(), onClick = onClick, - label = label, - icon = icon, + shape = Shapes.large, + label = { + Column(modifier = Modifier.wrapContentSize() + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { + SmallTitleText(entryHeadlineText) + if (entrySecondLineText != null) { + BodySmallText(entrySecondLineText) + } + } + }, + icon = { + Box(modifier = Modifier.wrapContentSize().padding(vertical = 16.dp)) { + Image( + modifier = Modifier.size(24.dp), + bitmap = iconImageBitmap, + // Decorative purpose only. + contentDescription = null, + ) + } + }, border = null, colors = SuggestionChipDefaults.suggestionChipColors( containerColor = Color.Transparent, ), ) +} + +/** + * A single row of leading icon and text describing a benefit of passkeys, used by the + * [com.android.credentialmanager.createflow.PasskeyIntroCard]. + */ +@Composable +fun PasskeyBenefitRow( + leadingIconPainter: Painter, + text: String, +) { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = leadingIconPainter, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + // Decorative purpose only. + contentDescription = null, + ) + BodyMediumText(text = text) + } +} + +/** + * A single row of one or two CTA buttons for continuing or cancelling the current step. + */ +@Composable +fun CtaButtonRow( + leftButton: (@Composable () -> Unit)? = null, + rightButton: (@Composable () -> Unit)? = null, +) { + Row( + horizontalArrangement = + if (leftButton == null) Arrangement.End + else if (rightButton == null) Arrangement.Start + else Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + if (leftButton != null) { + leftButton() + } + if (rightButton != null) { + rightButton() + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MoreOptionTopAppBar( + text: String, + onNavigationIconClicked: () -> Unit, +) { + TopAppBar( + title = { + LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp)) + }, + navigationIcon = { + IconButton( + modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp), + onClick = onNavigationIconClicked + ) { + Box( + modifier = Modifier.size(48.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = stringResource( + R.string.accessibility_back_arrow_button + ), + modifier = Modifier.size(16.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + modifier = Modifier.padding(top = 12.dp, bottom = 8.dp) + ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt new file mode 100644 index 000000000000..ac79844457a8 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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.credentialmanager.common.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + +/** Tinted primary; centered; 32X32. */ +@Composable +fun HeadlineIcon(bitmap: ImageBitmap, tint: Color? = null) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + ) { + Icon( + modifier = Modifier.size(32.dp), + bitmap = bitmap, + tint = tint ?: MaterialTheme.colorScheme.primary, + // Decorative purpose only. + contentDescription = null, + ) + } +} + +@Composable +fun HeadlineIcon(imageVector: ImageVector) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + ) { + Icon( + modifier = Modifier.size(32.dp), + imageVector = imageVector, + tint = MaterialTheme.colorScheme.primary, + // Decorative purpose only. + contentDescription = null, + ) + } +} + +@Composable +fun HeadlineIcon(painter: Painter) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + ) { + Icon( + modifier = Modifier.size(32.dp), + painter = painter, + tint = MaterialTheme.colorScheme.primary, + // Decorative purpose only. + contentDescription = null, + ) + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt new file mode 100644 index 000000000000..c63771e49516 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 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.credentialmanager.common.ui + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun CredentialListSectionHeader(text: String) { + InternalSectionHeader(text, MaterialTheme.colorScheme.onSurfaceVariant) +} + +@Composable +fun MoreAboutPasskeySectionHeader(text: String) { + InternalSectionHeader(text, MaterialTheme.colorScheme.onSurface) +} + +@Composable +private fun InternalSectionHeader(text: String, color: Color) { + Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) { + SectionHeaderText( + text, + modifier = Modifier.padding(top = 20.dp, bottom = 8.dp), + color = color + ) + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt new file mode 100644 index 000000000000..8061da79a5ae --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 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.credentialmanager.common.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.android.credentialmanager.R +import com.android.credentialmanager.common.material.Scrim +import com.android.credentialmanager.ui.theme.Shapes + +@Composable +fun Snackbar( + contentText: String, + action: (@Composable () -> Unit)? = null, + onDismiss: () -> Unit, +) { + BoxWithConstraints { + Box(Modifier.fillMaxSize()) { + Scrim( + color = Color.Transparent, + onDismiss = onDismiss, + visible = true + ) + } + Box( + modifier = Modifier + .align(Alignment.BottomCenter).wrapContentSize().padding(bottom = 18.dp) + ) { + Card( + shape = Shapes.medium, + modifier = Modifier.wrapContentSize(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.inverseSurface, + ) + ) { + Row( + modifier = Modifier.wrapContentSize(), + verticalAlignment = Alignment.CenterVertically, + ) { + SnackbarContentText(contentText, modifier = Modifier.padding( + top = 18.dp, bottom = 18.dp, start = 24.dp, + )) + if (action != null) { + action() + } + IconButton(onClick = onDismiss, modifier = Modifier.padding( + top = 18.dp, bottom = 18.dp, start = 16.dp, end = 24.dp, + )) { + Icon( + Icons.Filled.Close, + contentDescription = stringResource( + R.string.accessibility_snackbar_dismiss + ), + tint = MaterialTheme.colorScheme.inverseOnSurface, + ) + } + } + } + } + } +}
\ No newline at end of file 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 3a66dda73832..8f7c37efe787 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt @@ -16,69 +16,144 @@ package com.android.credentialmanager.common.ui +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.MaterialTheme 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.TextStyle import androidx.compose.ui.text.style.TextAlign +/** + * The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X". + * + * Centered horizontally; headline-small typography; on-surface color. + */ @Composable -fun TextOnSurface( - text: String, - modifier: Modifier = Modifier, - textAlign: TextAlign? = null, - style: TextStyle, -) { - TextInternal( +fun HeadlineText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), text = text, color = MaterialTheme.colorScheme.onSurface, - modifier = modifier, - textAlign = textAlign, - style = style, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.headlineSmall, ) } +/** + * Body-medium typography; on-surface-variant color. + */ @Composable -fun TextSecondary( - text: String, - modifier: Modifier = Modifier, - textAlign: TextAlign? = null, - style: TextStyle, -) { - TextInternal( +fun BodyMediumText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), text = text, - color = MaterialTheme.colorScheme.secondary, - modifier = modifier, - textAlign = textAlign, - style = style, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyMedium, ) } +/** + * Body-small typography; on-surface-variant color. + */ @Composable -fun TextOnSurfaceVariant( - text: String, - modifier: Modifier = Modifier, - textAlign: TextAlign? = null, - style: TextStyle, -) { - TextInternal( +fun BodySmallText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), text = text, color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = modifier, - textAlign = textAlign, - style = style, + style = MaterialTheme.typography.bodySmall, + ) +} + +/** + * Title-large typography; on-surface color. + */ +@Composable +fun LargeTitleText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), + text = text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.titleLarge, ) } +/** + * Title-small typography; on-surface color. + */ +@Composable +fun SmallTitleText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), + text = text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.titleSmall, + ) +} + +/** + * Title-small typography. + */ @Composable -private fun TextInternal( - text: String, - color: Color, - modifier: Modifier, - textAlign: TextAlign?, - style: TextStyle, -) { - Text(text = text, color = color, modifier = modifier, textAlign = textAlign, style = style) +fun SectionHeaderText(text: String, modifier: Modifier = Modifier, color: Color) { + Text( + modifier = modifier.wrapContentHeight(), + text = text, + color = color, + style = MaterialTheme.typography.titleSmall, + ) +} + +/** + * Body-medium typography; inverse-on-surface color. + */ +@Composable +fun SnackbarContentText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), + text = text, + color = MaterialTheme.colorScheme.inverseOnSurface, + style = MaterialTheme.typography.bodyMedium, + ) +} + +/** + * Label-large typography; inverse-primary color. + */ +@Composable +fun SnackbarActionText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), + text = text, + color = MaterialTheme.colorScheme.inversePrimary, + style = MaterialTheme.typography.labelLarge, + ) +} + +/** + * Label-large typography; on-surface-variant color; centered. + */ +@Composable +fun LargeLabelTextOnSurfaceVariant(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), + text = text, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.labelLarge, + ) +} + +/** + * Label-large typography; color following parent spec; centered. + */ +@Composable +fun LargeLabelText(text: String, modifier: Modifier = Modifier) { + Text( + modifier = modifier.wrapContentHeight(), + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.labelLarge, + ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 942eb490aa20..379b3e3adf30 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -8,28 +8,19 @@ import androidx.activity.result.IntentSenderRequest import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.filled.Add 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.Alignment @@ -38,9 +29,6 @@ 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.AnnotatedString -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel @@ -49,15 +37,21 @@ import com.android.credentialmanager.common.BaseEntry import com.android.credentialmanager.common.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.ui.ActionButton +import com.android.credentialmanager.common.ui.BodyMediumText +import com.android.credentialmanager.common.ui.BodySmallText import com.android.credentialmanager.common.ui.ConfirmButton +import com.android.credentialmanager.common.ui.CredentialContainerCard +import com.android.credentialmanager.common.ui.CtaButtonRow import com.android.credentialmanager.common.ui.Entry +import com.android.credentialmanager.common.ui.EntryListColumn +import com.android.credentialmanager.common.ui.HeadlineIcon +import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.ModalBottomSheet -import com.android.credentialmanager.common.ui.TextOnSurface -import com.android.credentialmanager.common.ui.TextSecondary -import com.android.credentialmanager.common.ui.TextOnSurfaceVariant -import com.android.credentialmanager.common.ui.ContainerCard -import com.android.credentialmanager.common.ui.ToggleVisibilityButton -import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme +import com.android.credentialmanager.common.ui.MoreAboutPasskeySectionHeader +import com.android.credentialmanager.common.ui.MoreOptionTopAppBar +import com.android.credentialmanager.common.ui.SheetContainerCard +import com.android.credentialmanager.common.ui.PasskeyBenefitRow +import com.android.credentialmanager.common.ui.HeadlineText @Composable fun CreateCredentialScreen( @@ -73,7 +67,7 @@ fun CreateCredentialScreen( when (viewModel.uiState.providerActivityState) { ProviderActivityState.NOT_APPLICABLE -> { when (createCredentialUiState.currentScreenState) { - CreateScreenState.PASSKEY_INTRO -> ConfirmationCard( + CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard( onConfirm = viewModel::createFlowOnConfirmIntro, onLearnMore = viewModel::createFlowOnLearnMore, ) @@ -157,119 +151,59 @@ fun CreateCredentialScreen( ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable -fun ConfirmationCard( +fun PasskeyIntroCard( onConfirm: () -> Unit, onLearnMore: () -> Unit, ) { - ContainerCard() { - Column() { - val onboardingImageResource = remember { - mutableStateOf(R.drawable.ic_passkeys_onboarding) - } - if (isSystemInDarkTheme()) { - onboardingImageResource.value = R.drawable.ic_passkeys_onboarding_dark - } else { - onboardingImageResource.value = R.drawable.ic_passkeys_onboarding - } - Image( - painter = painterResource(onboardingImageResource.value), - contentDescription = null, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp) - ) - TextOnSurface( - text = stringResource(R.string.passkey_creation_intro_title), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier - .padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - Divider( - thickness = 16.dp, - color = Color.Transparent - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(R.drawable.ic_passkeys_onboarding_password), - contentDescription = null - ) - TextSecondary( - text = stringResource(R.string.passkey_creation_intro_body_password), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 16.dp, end = 4.dp), - ) - } - Divider( - thickness = 16.dp, - color = Color.Transparent - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint), - contentDescription = null - ) - TextSecondary( - text = stringResource(R.string.passkey_creation_intro_body_fingerprint), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 16.dp, end = 4.dp), - ) - } - Divider( - thickness = 16.dp, - color = Color.Transparent - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(R.drawable.ic_passkeys_onboarding_device), - contentDescription = null - ) - TextSecondary( - text = stringResource(R.string.passkey_creation_intro_body_device), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 16.dp, end = 4.dp), - ) - } - Divider( - thickness = 32.dp, - color = Color.Transparent - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { + SheetContainerCard { + val onboardingImageResource = remember { + mutableStateOf(R.drawable.ic_passkeys_onboarding) + } + if (isSystemInDarkTheme()) { + onboardingImageResource.value = R.drawable.ic_passkeys_onboarding_dark + } else { + onboardingImageResource.value = R.drawable.ic_passkeys_onboarding + } + Image( + painter = painterResource(onboardingImageResource.value), + contentDescription = null, + modifier = Modifier + .align(alignment = Alignment.CenterHorizontally).size(316.dp, 168.dp) + ) + Divider(thickness = 16.dp, color = Color.Transparent) + HeadlineText(text = stringResource(R.string.passkey_creation_intro_title)) + Divider(thickness = 16.dp, color = Color.Transparent) + PasskeyBenefitRow( + leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_password), + text = stringResource(R.string.passkey_creation_intro_body_password), + ) + Divider(thickness = 16.dp, color = Color.Transparent) + PasskeyBenefitRow( + leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint), + text = stringResource(R.string.passkey_creation_intro_body_fingerprint), + ) + Divider(thickness = 16.dp, color = Color.Transparent) + PasskeyBenefitRow( + leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_device), + text = stringResource(R.string.passkey_creation_intro_body_device), + ) + Divider(thickness = 24.dp, color = Color.Transparent) + + CtaButtonRow( + leftButton = { ActionButton( stringResource(R.string.string_learn_more), onClick = onLearnMore ) + }, + rightButton = { ConfirmButton( stringResource(R.string.string_continue), onClick = onConfirm ) - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 18.dp) - ) - } + }, + ) } } @@ -283,102 +217,68 @@ fun ProviderSelectionCard( onDisabledProvidersSelected: () -> Unit, onMoreOptionsSelected: () -> Unit, ) { - ContainerCard() { - Column() { - Icon( - bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(top = 24.dp, bottom = 16.dp).size(32.dp) - ) - TextOnSurface( - text = stringResource( - R.string.choose_provider_title, - when (requestDisplayInfo.type) { - CredentialType.PASSKEY -> - stringResource(R.string.passkeys) - CredentialType.PASSWORD -> - stringResource(R.string.passwords) - CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) - } - ), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - Divider( - thickness = 16.dp, - color = Color.Transparent - ) - TextSecondary( - text = stringResource(R.string.choose_provider_body), - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(horizontal = 28.dp), + SheetContainerCard { + HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) + Divider(thickness = 16.dp, color = Color.Transparent) + HeadlineText( + text = stringResource( + R.string.choose_provider_title, + when (requestDisplayInfo.type) { + CredentialType.PASSKEY -> + stringResource(R.string.passkeys) + CredentialType.PASSWORD -> + stringResource(R.string.passwords) + CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) + } ) - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier.padding( - start = 24.dp, - end = 24.dp, - top = 24.dp, - bottom = if (hasRemoteEntry) 24.dp else 16.dp - ).align(alignment = Alignment.CenterHorizontally), + ) + Divider(thickness = 24.dp, color = Color.Transparent) + + BodyMediumText(text = stringResource(R.string.choose_provider_body)) + Divider(thickness = 16.dp, color = Color.Transparent) + CredentialContainerCard { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(2.dp) ) { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { - sortedCreateOptionsPairs.forEach { entry -> - item { - MoreOptionsInfoRow( - requestDisplayInfo = requestDisplayInfo, - providerInfo = entry.second, - createOptionInfo = entry.first, - onOptionSelected = { - onOptionSelected( - ActiveEntry( - entry.second, - entry.first - ) - ) - } - ) - } - } + sortedCreateOptionsPairs.forEach { entry -> item { - MoreOptionsDisabledProvidersRow( - disabledProviders = disabledProviderList, - onDisabledProvidersSelected = - onDisabledProvidersSelected, + MoreOptionsInfoRow( + requestDisplayInfo = requestDisplayInfo, + providerInfo = entry.second, + createOptionInfo = entry.first, + onOptionSelected = { + onOptionSelected( + ActiveEntry( + entry.second, + entry.first + ) + ) + } ) } } + item { + MoreOptionsDisabledProvidersRow( + disabledProviders = disabledProviderList, + onDisabledProvidersSelected = onDisabledProvidersSelected, + ) + } } - if (hasRemoteEntry) { - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - Row( - horizontalArrangement = Arrangement.Start, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { + } + if (hasRemoteEntry) { + Divider(thickness = 24.dp, color = Color.Transparent) + CtaButtonRow( + leftButton = { ActionButton( stringResource(R.string.string_more_options), onMoreOptionsSelected ) } - } - Divider( - thickness = 24.dp, - color = Color.Transparent, ) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsSelectionCard( requestDisplayInfo: RequestDisplayInfo, @@ -393,158 +293,103 @@ fun MoreOptionsSelectionCard( onDisabledProvidersSelected: () -> Unit, onRemoteEntrySelected: (BaseEntry) -> Unit, ) { - ContainerCard() { - Column() { - TopAppBar( - title = { - TextOnSurface( - text = - stringResource( - R.string.save_credential_to_title, - when (requestDisplayInfo.type) { - CredentialType.PASSKEY -> - stringResource(R.string.passkey) - CredentialType.PASSWORD -> - stringResource(R.string.password) - CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) - } - ), - style = MaterialTheme.typography.titleMedium, - ) - }, - navigationIcon = { - IconButton( - onClick = - if (isFromProviderSelection) - onBackProviderSelectionButtonSelected - else onBackCreationSelectionButtonSelected - ) { - Icon( - Icons.Filled.ArrowBack, - stringResource(R.string.accessibility_back_arrow_button) - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), - modifier = Modifier.padding(top = 12.dp) - ) - Divider( - thickness = 8.dp, - color = Color.Transparent - ) - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally) - ) { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { - // Only in the flows with default provider(not first time use) we can show the - // createOptions here, or they will be shown on ProviderSelectionCard - if (hasDefaultProvider) { - sortedCreateOptionsPairs.forEach { entry -> - item { - MoreOptionsInfoRow( - requestDisplayInfo = requestDisplayInfo, - providerInfo = entry.second, - createOptionInfo = entry.first, - onOptionSelected = { - onOptionSelected( - ActiveEntry( - entry.second, - entry.first - ) + SheetContainerCard(topAppBar = { + MoreOptionTopAppBar( + text = stringResource( + R.string.save_credential_to_title, + when (requestDisplayInfo.type) { + CredentialType.PASSKEY -> + stringResource(R.string.passkey) + CredentialType.PASSWORD -> + stringResource(R.string.password) + CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info) + } + ), + onNavigationIconClicked = + if (isFromProviderSelection) onBackProviderSelectionButtonSelected + else onBackCreationSelectionButtonSelected, + ) + }) { + Divider(thickness = 16.dp, color = Color.Transparent) + CredentialContainerCard { + EntryListColumn { + // Only in the flows with default provider(not first time use) we can show the + // createOptions here, or they will be shown on ProviderSelectionCard + if (hasDefaultProvider) { + sortedCreateOptionsPairs.forEach { entry -> + item { + MoreOptionsInfoRow( + requestDisplayInfo = requestDisplayInfo, + providerInfo = entry.second, + createOptionInfo = entry.first, + onOptionSelected = { + onOptionSelected( + ActiveEntry( + entry.second, + entry.first ) - }) - } + ) + }) } + } + item { + MoreOptionsDisabledProvidersRow( + disabledProviders = disabledProviderList, + onDisabledProvidersSelected = + onDisabledProvidersSelected, + ) + } + } + enabledProviderList.forEach { + if (it.remoteEntry != null) { item { - MoreOptionsDisabledProvidersRow( - disabledProviders = disabledProviderList, - onDisabledProvidersSelected = - onDisabledProvidersSelected, + RemoteEntryRow( + remoteInfo = it.remoteEntry!!, + onRemoteEntrySelected = onRemoteEntrySelected, ) } - } - enabledProviderList.forEach { - if (it.remoteEntry != null) { - item { - RemoteEntryRow( - remoteInfo = it.remoteEntry!!, - onRemoteEntrySelected = onRemoteEntrySelected, - ) - } - return@forEach - } + return@forEach } } } - Divider( - thickness = 8.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 40.dp) - ) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsRowIntroCard( providerInfo: EnabledProviderInfo, onChangeDefaultSelected: () -> Unit, onUseOnceSelected: () -> Unit, ) { - ContainerCard() { - Column() { - Icon( - Icons.Outlined.NewReleases, - contentDescription = null, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(all = 24.dp), - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - ) - TextOnSurface( - text = stringResource( - R.string.use_provider_for_all_title, - providerInfo.displayName - ), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, + SheetContainerCard { + HeadlineIcon(imageVector = Icons.Outlined.NewReleases) + Divider(thickness = 24.dp, color = Color.Transparent) + HeadlineText( + text = stringResource( + R.string.use_provider_for_all_title, + providerInfo.displayName ) - TextSecondary( - text = stringResource(R.string.use_provider_for_all_description), - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding(all = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { + ) + Divider(thickness = 24.dp, color = Color.Transparent) + BodyMediumText(text = stringResource(R.string.use_provider_for_all_description)) + CtaButtonRow( + leftButton = { ActionButton( stringResource(R.string.use_once), onClick = onUseOnceSelected ) + }, + rightButton = { ConfirmButton( stringResource(R.string.set_as_default), onClick = onChangeDefaultSelected ) - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 40.dp) - ) - } + }, + ) } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun CreationSelectionCard( requestDisplayInfo: RequestDisplayInfo, @@ -556,113 +401,82 @@ fun CreationSelectionCard( onMoreOptionsSelected: () -> Unit, hasDefaultProvider: Boolean, ) { - ContainerCard() { - Column() { - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - Icon( - bitmap = providerInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null, - tint = Color.Unspecified, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally).size(32.dp) - ) - TextSecondary( - text = providerInfo.displayName, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 10.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - TextOnSurface( - text = when (requestDisplayInfo.type) { - CredentialType.PASSKEY -> stringResource( - R.string.choose_create_option_passkey_title, - requestDisplayInfo.appName - ) - CredentialType.PASSWORD -> stringResource( - R.string.choose_create_option_password_title, - requestDisplayInfo.appName - ) - CredentialType.UNKNOWN -> stringResource( - R.string.choose_create_option_sign_in_title, - requestDisplayInfo.appName - ) - }, - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(all = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - ) { - PrimaryCreateOptionRow( - requestDisplayInfo = requestDisplayInfo, - entryInfo = createOptionInfo, - onOptionSelected = onOptionSelected + SheetContainerCard { + HeadlineIcon( + bitmap = providerInfo.icon.toBitmap().asImageBitmap(), + tint = Color.Unspecified, + ) + Divider(thickness = 4.dp, color = Color.Transparent) + LargeLabelTextOnSurfaceVariant(text = providerInfo.displayName) + Divider(thickness = 16.dp, color = Color.Transparent) + HeadlineText( + text = when (requestDisplayInfo.type) { + CredentialType.PASSKEY -> stringResource( + R.string.choose_create_option_passkey_title, + requestDisplayInfo.appName + ) + CredentialType.PASSWORD -> stringResource( + R.string.choose_create_option_password_title, + requestDisplayInfo.appName + ) + CredentialType.UNKNOWN -> stringResource( + R.string.choose_create_option_sign_in_title, + requestDisplayInfo.appName ) } - var createOptionsSize = 0 - var remoteEntry: RemoteInfo? = null - enabledProviderList.forEach { enabledProvider -> - if (enabledProvider.remoteEntry != null) { - remoteEntry = enabledProvider.remoteEntry - } - createOptionsSize += enabledProvider.createOptions.size - } - val shouldShowMoreOptionsButton = if (!hasDefaultProvider) { - // User has already been presented with all options on the default provider - // selection screen. Don't show them again. Therefore, only show the more option - // button if remote option is present. - remoteEntry != null - } else { - createOptionsSize > 1 || remoteEntry != null + ) + Divider(thickness = 24.dp, color = Color.Transparent) + CredentialContainerCard { + PrimaryCreateOptionRow( + requestDisplayInfo = requestDisplayInfo, + entryInfo = createOptionInfo, + onOptionSelected = onOptionSelected + ) + } + Divider(thickness = 24.dp, color = Color.Transparent) + var createOptionsSize = 0 + var remoteEntry: RemoteInfo? = null + enabledProviderList.forEach { enabledProvider -> + if (enabledProvider.remoteEntry != null) { + remoteEntry = enabledProvider.remoteEntry } - Row( - horizontalArrangement = - if (shouldShowMoreOptionsButton) Arrangement.SpaceBetween else Arrangement.End, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - if (shouldShowMoreOptionsButton) { + createOptionsSize += enabledProvider.createOptions.size + } + val shouldShowMoreOptionsButton = if (!hasDefaultProvider) { + // User has already been presented with all options on the default provider + // selection screen. Don't show them again. Therefore, only show the more option + // button if remote option is present. + remoteEntry != null + } else { + createOptionsSize > 1 || remoteEntry != null + } + CtaButtonRow( + leftButton = if (shouldShowMoreOptionsButton) { + { ActionButton( stringResource(R.string.string_more_options), - onClick = onMoreOptionsSelected + onMoreOptionsSelected ) } + } else null, + rightButton = { ConfirmButton( stringResource(R.string.string_continue), onClick = onConfirm ) - } - if (createOptionInfo.footerDescription != null) { - Divider( - thickness = 1.dp, - color = Color.LightGray, - modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 18.dp) - ) - TextSecondary( - text = createOptionInfo.footerDescription, - style = MaterialTheme.typography.bodyLarge, - modifier = Modifier.padding( - start = 29.dp, top = 8.dp, bottom = 18.dp, end = 28.dp - ) - ) - } + }, + ) + if (createOptionInfo.footerDescription != null) { Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 16.dp) + thickness = 1.dp, + color = MaterialTheme.colorScheme.outlineVariant, + modifier = Modifier.padding(vertical = 16.dp) ) + BodySmallText(text = createOptionInfo.footerDescription) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ExternalOnlySelectionCard( requestDisplayInfo: RequestDisplayInfo, @@ -670,148 +484,75 @@ fun ExternalOnlySelectionCard( onOptionSelected: (BaseEntry) -> Unit, onConfirm: () -> Unit, ) { - ContainerCard() { - Column() { - Icon( - painter = painterResource(R.drawable.ic_other_devices), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(all = 24.dp).size(32.dp) - ) - TextOnSurface( - text = stringResource(R.string.create_passkey_in_other_device_title), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - ) - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - ) { - PrimaryCreateOptionRow( - requestDisplayInfo = requestDisplayInfo, - entryInfo = activeRemoteEntry, - onOptionSelected = onOptionSelected - ) - } - Divider( - thickness = 24.dp, - color = Color.Transparent + SheetContainerCard { + HeadlineIcon(painter = painterResource(R.drawable.ic_other_devices)) + Divider(thickness = 16.dp, color = Color.Transparent) + HeadlineText(text = stringResource(R.string.create_passkey_in_other_device_title)) + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + CredentialContainerCard { + PrimaryCreateOptionRow( + requestDisplayInfo = requestDisplayInfo, + entryInfo = activeRemoteEntry, + onOptionSelected = onOptionSelected ) - Row( - horizontalArrangement = Arrangement.End, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { + } + Divider(thickness = 24.dp, color = Color.Transparent) + CtaButtonRow( + rightButton = { ConfirmButton( stringResource(R.string.string_continue), onClick = onConfirm ) - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 16.dp) - ) - } + }, + ) } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreAboutPasskeysIntroCard( onBackPasskeyIntroButtonSelected: () -> Unit, ) { - ContainerCard() { - Column() { - TopAppBar( - title = { - TextOnSurface( - text = - stringResource( - R.string.more_about_passkeys_title - ), - style = MaterialTheme.typography.titleMedium, - ) - }, - navigationIcon = { - IconButton( - onClick = onBackPasskeyIntroButtonSelected - ) { - Icon( - Icons.Filled.ArrowBack, - stringResource(R.string.accessibility_back_arrow_button) - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), - modifier = Modifier.padding(top = 12.dp) - ) - Column( - modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 68.dp) - ) { - TextOnSurfaceVariant( - text = stringResource(R.string.passwordless_technology_title), - style = MaterialTheme.typography.titleLarge, - ) - TextSecondary( - text = stringResource(R.string.passwordless_technology_detail), - style = MaterialTheme.typography.bodyMedium, - ) - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - TextOnSurfaceVariant( - text = stringResource(R.string.public_key_cryptography_title), - style = MaterialTheme.typography.titleLarge, - ) - TextSecondary( - text = stringResource(R.string.public_key_cryptography_detail), - style = MaterialTheme.typography.bodyMedium, - ) - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - TextOnSurfaceVariant( - text = stringResource(R.string.improved_account_security_title), - style = MaterialTheme.typography.titleLarge, - ) - TextSecondary( - text = stringResource(R.string.improved_account_security_detail), - style = MaterialTheme.typography.bodyMedium, - ) - Divider( - thickness = 24.dp, - color = Color.Transparent + SheetContainerCard(topAppBar = { + MoreOptionTopAppBar( + text = stringResource(R.string.more_about_passkeys_title), + onNavigationIconClicked = onBackPasskeyIntroButtonSelected, + ) + }) { + LazyColumn( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + item { + MoreAboutPasskeySectionHeader( + text = stringResource(R.string.passwordless_technology_title) + ) + BodyMediumText(text = stringResource(R.string.passwordless_technology_detail)) + } + item { + MoreAboutPasskeySectionHeader( + text = stringResource(R.string.public_key_cryptography_title) ) - TextOnSurfaceVariant( - text = stringResource(R.string.seamless_transition_title), - style = MaterialTheme.typography.titleLarge, + BodyMediumText(text = stringResource(R.string.public_key_cryptography_detail)) + } + item { + MoreAboutPasskeySectionHeader( + text = stringResource(R.string.improved_account_security_title) ) - TextSecondary( - text = stringResource(R.string.seamless_transition_detail), - style = MaterialTheme.typography.bodyMedium, + BodyMediumText(text = stringResource(R.string.improved_account_security_detail)) + } + item { + MoreAboutPasskeySectionHeader( + text = stringResource(R.string.seamless_transition_title) ) + BodyMediumText(text = stringResource(R.string.seamless_transition_detail)) } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 24.dp) - ) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun PrimaryCreateOptionRow( requestDisplayInfo: RequestDisplayInfo, @@ -820,115 +561,37 @@ fun PrimaryCreateOptionRow( ) { Entry( onClick = { onOptionSelected(entryInfo) }, - icon = { - if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) { - Image( - bitmap = entryInfo.profileIcon.toBitmap().asImageBitmap(), - contentDescription = null, - modifier = Modifier.padding(start = 10.dp).size(32.dp), - ) - } else { - Icon( - bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.padding(start = 10.dp).size(32.dp), - ) - } + iconImageBitmap = + if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) { + entryInfo.profileIcon.toBitmap().asImageBitmap() + } else { + requestDisplayInfo.typeIcon.toBitmap().asImageBitmap() }, - label = { - Column() { - when (requestDisplayInfo.type) { - CredentialType.PASSKEY -> { - TextOnSurfaceVariant( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), - ) - TextSecondary( - text = if (requestDisplayInfo.subtitle != null) { - requestDisplayInfo.subtitle + " • " + stringResource( - R.string.passkey_before_subtitle - ) - } else { - stringResource(R.string.passkey_before_subtitle) - }, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } - CredentialType.PASSWORD -> { - TextOnSurfaceVariant( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), - ) - Row( - modifier = Modifier.fillMaxWidth().padding( - top = 4.dp, bottom = 16.dp, - start = 5.dp - ), - verticalAlignment = Alignment.CenterVertically - ) { - val visualTransformation = remember { PasswordVisualTransformation() } - // This subtitle would never be null for create password - val originalPassword by remember { - mutableStateOf(requestDisplayInfo.subtitle ?: "") - } - val displayedPassword = remember { - mutableStateOf( - visualTransformation.filter( - AnnotatedString(originalPassword) - ).text.text - ) - } - TextSecondary( - text = displayedPassword.value, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(top = 4.dp, bottom = 4.dp), - ) - - ToggleVisibilityButton(modifier = Modifier.padding(start = 4.dp) - .height(24.dp).width(24.dp), onToggle = { - if (it) { - displayedPassword.value = originalPassword - } else { - displayedPassword.value = visualTransformation.filter( - AnnotatedString(originalPassword) - ).text.text - } - }) - } - } - CredentialType.UNKNOWN -> { - if (requestDisplayInfo.subtitle != null) { - TextOnSurfaceVariant( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), - ) - TextOnSurfaceVariant( - text = requestDisplayInfo.subtitle, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else { - TextOnSurfaceVariant( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding( - top = 16.dp, bottom = 16.dp, start = 5.dp - ), - ) - } - } + shouldApplyIconImageBitmapTint = !(entryInfo is CreateOptionInfo && + entryInfo.profileIcon != null), + entryHeadlineText = requestDisplayInfo.title, + entrySecondLineText = when (requestDisplayInfo.type) { + CredentialType.PASSKEY -> { + if (requestDisplayInfo.subtitle != null) { + requestDisplayInfo.subtitle + " • " + stringResource( + R.string.passkey_before_subtitle + ) + } else { + stringResource(R.string.passkey_before_subtitle) } } - } + // Set passwordValue instead + CredentialType.PASSWORD -> null + CredentialType.UNKNOWN -> requestDisplayInfo.subtitle + }, + passwordValue = + if (requestDisplayInfo.type == CredentialType.PASSWORD) + // This subtitle would never be null for create password + requestDisplayInfo.subtitle ?: "" + else null, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsInfoRow( requestDisplayInfo: RequestDisplayInfo, @@ -938,93 +601,46 @@ fun MoreOptionsInfoRow( ) { Entry( onClick = onOptionSelected, - icon = { - Image( - modifier = Modifier.padding(start = 10.dp).size(32.dp), - bitmap = providerInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null - ) - }, - label = { - Column() { - TextOnSurfaceVariant( - text = providerInfo.displayName, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), + iconImageBitmap = providerInfo.icon.toBitmap().asImageBitmap(), + entryHeadlineText = providerInfo.displayName, + entrySecondLineText = createOptionInfo.userProviderDisplayName, + entryThirdLineText = + if (requestDisplayInfo.type == CredentialType.PASSKEY || + requestDisplayInfo.type == CredentialType.PASSWORD) { + if (createOptionInfo.passwordCount != null && + createOptionInfo.passkeyCount != null + ) { + stringResource( + R.string.more_options_usage_passwords_passkeys, + createOptionInfo.passwordCount, + createOptionInfo.passkeyCount ) - if (createOptionInfo.userProviderDisplayName != null) { - TextSecondary( - text = createOptionInfo.userProviderDisplayName, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 5.dp), - ) - } - if (requestDisplayInfo.type == CredentialType.PASSKEY || - requestDisplayInfo.type == CredentialType.PASSWORD - ) { - if (createOptionInfo.passwordCount != null && - createOptionInfo.passkeyCount != null - ) { - TextSecondary( - text = - stringResource( - R.string.more_options_usage_passwords_passkeys, - createOptionInfo.passwordCount, - createOptionInfo.passkeyCount - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else if (createOptionInfo.passwordCount != null) { - TextSecondary( - text = - stringResource( - R.string.more_options_usage_passwords, - createOptionInfo.passwordCount - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else if (createOptionInfo.passkeyCount != null) { - TextSecondary( - text = - stringResource( - R.string.more_options_usage_passkeys, - createOptionInfo.passkeyCount - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else { - Divider( - thickness = 16.dp, - color = Color.Transparent, - ) - } - } else { - if (createOptionInfo.totalCredentialCount != null) { - TextSecondary( - text = - stringResource( - R.string.more_options_usage_credentials, - createOptionInfo.totalCredentialCount - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } else { - Divider( - thickness = 16.dp, - color = Color.Transparent, - ) - } - } + } else if (createOptionInfo.passwordCount != null) { + stringResource( + R.string.more_options_usage_passwords, + createOptionInfo.passwordCount + ) + } else if (createOptionInfo.passkeyCount != null) { + stringResource( + R.string.more_options_usage_passkeys, + createOptionInfo.passkeyCount + ) + } else { + null } - } + } else { + if (createOptionInfo.totalCredentialCount != null) { + stringResource( + R.string.more_options_usage_credentials, + createOptionInfo.totalCredentialCount + ) + } else { + null + } + }, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun MoreOptionsDisabledProvidersRow( disabledProviders: List<ProviderInfo>?, @@ -1033,36 +649,15 @@ fun MoreOptionsDisabledProvidersRow( if (disabledProviders != null && disabledProviders.isNotEmpty()) { Entry( onClick = onDisabledProvidersSelected, - icon = { - Icon( - Icons.Filled.Add, - contentDescription = null, - modifier = Modifier.padding(start = 16.dp), - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - ) + iconImageVector = Icons.Filled.Add, + entryHeadlineText = stringResource(R.string.other_password_manager), + entrySecondLineText = disabledProviders.joinToString(separator = " • ") { + it.displayName }, - label = { - Column() { - TextOnSurfaceVariant( - text = stringResource(R.string.other_password_manager), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp), - ) - // TODO: Update the subtitle once design is confirmed - TextSecondary( - text = disabledProviders.joinToString(separator = " • ") { - it.displayName - }, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp), - ) - } - } ) } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun RemoteEntryRow( remoteInfo: RemoteInfo, @@ -1070,23 +665,7 @@ fun RemoteEntryRow( ) { Entry( onClick = { onRemoteEntrySelected(remoteInfo) }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_other_devices), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.padding(start = 10.dp) - ) - }, - label = { - Column() { - TextOnSurfaceVariant( - text = stringResource(R.string.another_device), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 10.dp, top = 18.dp, bottom = 18.dp) - .align(alignment = Alignment.CenterHorizontally), - ) - } - } + iconPainter = painterResource(R.drawable.ic_other_devices), + entryHeadlineText = stringResource(R.string.another_device), ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 5704820444a4..54f8e5cea798 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -20,40 +20,22 @@ import android.text.TextUtils import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult import androidx.activity.result.IntentSenderRequest - -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.outlined.Lock import androidx.compose.material3.Divider -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Snackbar -import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment 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.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel @@ -62,16 +44,18 @@ import com.android.credentialmanager.common.BaseEntry import com.android.credentialmanager.common.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.ui.ActionButton +import com.android.credentialmanager.common.ui.ActionEntry import com.android.credentialmanager.common.ui.ConfirmButton +import com.android.credentialmanager.common.ui.CredentialContainerCard +import com.android.credentialmanager.common.ui.CtaButtonRow import com.android.credentialmanager.common.ui.Entry import com.android.credentialmanager.common.ui.ModalBottomSheet -import com.android.credentialmanager.common.ui.TextOnSurface -import com.android.credentialmanager.common.ui.TextSecondary -import com.android.credentialmanager.common.ui.TextOnSurfaceVariant -import com.android.credentialmanager.common.ui.ContainerCard -import com.android.credentialmanager.common.ui.TransparentBackgroundEntry -import com.android.credentialmanager.ui.theme.EntryShape -import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme +import com.android.credentialmanager.common.ui.MoreOptionTopAppBar +import com.android.credentialmanager.common.ui.SheetContainerCard +import com.android.credentialmanager.common.ui.SnackbarActionText +import com.android.credentialmanager.common.ui.HeadlineText +import com.android.credentialmanager.common.ui.CredentialListSectionHeader +import com.android.credentialmanager.common.ui.Snackbar @Composable fun GetCredentialScreen( @@ -154,123 +138,100 @@ fun PrimarySelectionCard( val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList - ContainerCard() { - Column() { - TextOnSurface( - modifier = Modifier.padding(all = 24.dp), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.headlineSmall, - text = stringResource( - if (sortedUserNameToCredentialEntryList - .size == 1 && authenticationEntryList.isEmpty() - ) { - if (sortedUserNameToCredentialEntryList.first() - .sortedCredentialEntryList.first().credentialType - == CredentialType.PASSKEY - ) R.string.get_dialog_title_use_passkey_for - else R.string.get_dialog_title_use_sign_in_for - } else if ( - sortedUserNameToCredentialEntryList - .isEmpty() && authenticationEntryList.size == 1 - ) { - R.string.get_dialog_title_use_sign_in_for - } else R.string.get_dialog_title_choose_sign_in_for, - requestDisplayInfo.appName - ), - ) - - ContainerCard( - shape = MaterialTheme.shapes.medium, - modifier = Modifier - .padding(horizontal = 24.dp) - .align(alignment = Alignment.CenterHorizontally) - ) { - val usernameForCredentialSize = sortedUserNameToCredentialEntryList - .size - val authenticationEntrySize = authenticationEntryList.size - LazyColumn( - verticalArrangement = Arrangement.spacedBy(2.dp) + SheetContainerCard { + HeadlineText( + text = stringResource( + if (sortedUserNameToCredentialEntryList + .size == 1 && authenticationEntryList.isEmpty() ) { - // Show max 4 entries in this primary page - if (usernameForCredentialSize + authenticationEntrySize <= 4) { - items(sortedUserNameToCredentialEntryList) { - CredentialEntryRow( - credentialEntryInfo = it.sortedCredentialEntryList.first(), - onEntrySelected = onEntrySelected, - ) - } - items(authenticationEntryList) { - AuthenticationEntryRow( - authenticationEntryInfo = it, - onEntrySelected = onEntrySelected, - ) - } - } else if (usernameForCredentialSize < 4) { - items(sortedUserNameToCredentialEntryList) { - CredentialEntryRow( - credentialEntryInfo = it.sortedCredentialEntryList.first(), - onEntrySelected = onEntrySelected, - ) - } - items(authenticationEntryList.take(4 - usernameForCredentialSize)) { - AuthenticationEntryRow( - authenticationEntryInfo = it, - onEntrySelected = onEntrySelected, - ) - } - } else { - items(sortedUserNameToCredentialEntryList.take(4)) { - CredentialEntryRow( - credentialEntryInfo = it.sortedCredentialEntryList.first(), - onEntrySelected = onEntrySelected, - ) - } + if (sortedUserNameToCredentialEntryList.first() + .sortedCredentialEntryList.first().credentialType + == CredentialType.PASSKEY + ) R.string.get_dialog_title_use_passkey_for + else R.string.get_dialog_title_use_sign_in_for + } else if ( + sortedUserNameToCredentialEntryList + .isEmpty() && authenticationEntryList.size == 1 + ) { + R.string.get_dialog_title_use_sign_in_for + } else R.string.get_dialog_title_choose_sign_in_for, + requestDisplayInfo.appName + ), + ) + Divider(thickness = 24.dp, color = Color.Transparent) + CredentialContainerCard { + val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size + val authenticationEntrySize = authenticationEntryList.size + LazyColumn( + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + // Show max 4 entries in this primary page + if (usernameForCredentialSize + authenticationEntrySize <= 4) { + items(sortedUserNameToCredentialEntryList) { + CredentialEntryRow( + credentialEntryInfo = it.sortedCredentialEntryList.first(), + onEntrySelected = onEntrySelected, + ) + } + items(authenticationEntryList) { + AuthenticationEntryRow( + authenticationEntryInfo = it, + onEntrySelected = onEntrySelected, + ) + } + } else if (usernameForCredentialSize < 4) { + items(sortedUserNameToCredentialEntryList) { + CredentialEntryRow( + credentialEntryInfo = it.sortedCredentialEntryList.first(), + onEntrySelected = onEntrySelected, + ) + } + items(authenticationEntryList.take(4 - usernameForCredentialSize)) { + AuthenticationEntryRow( + authenticationEntryInfo = it, + onEntrySelected = onEntrySelected, + ) + } + } else { + items(sortedUserNameToCredentialEntryList.take(4)) { + CredentialEntryRow( + credentialEntryInfo = it.sortedCredentialEntryList.first(), + onEntrySelected = onEntrySelected, + ) } } } - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - var totalEntriesCount = sortedUserNameToCredentialEntryList - .flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList - .size + providerInfoList.flatMap { it.actionEntryList }.size - if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1 - // Row horizontalArrangement differs on only one actionButton(should place on most - // left)/only one confirmButton(should place on most right)/two buttons exist the same - // time(should be one on the left, one on the right) - Row( - horizontalArrangement = - if (totalEntriesCount <= 1 && activeEntry != null) Arrangement.End - else if (totalEntriesCount > 1 && activeEntry == null) Arrangement.Start - else Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - if (totalEntriesCount > 1) { + } + Divider(thickness = 24.dp, color = Color.Transparent) + var totalEntriesCount = sortedUserNameToCredentialEntryList + .flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList + .size + providerInfoList.flatMap { it.actionEntryList }.size + if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1 + // Row horizontalArrangement differs on only one actionButton(should place on most + // left)/only one confirmButton(should place on most right)/two buttons exist the same + // time(should be one on the left, one on the right) + CtaButtonRow( + leftButton = if (totalEntriesCount > 1) { + { ActionButton( stringResource(R.string.get_dialog_use_saved_passkey_for), onMoreOptionSelected ) } - // Only one sign-in options exist - if (activeEntry != null) { + } else null, + rightButton = if (activeEntry != null) { // Only one sign-in options exist + { ConfirmButton( stringResource(R.string.string_continue), onClick = onConfirm ) } - } - Divider( - thickness = 18.dp, - color = Color.Transparent, - modifier = Modifier.padding(bottom = 16.dp) - ) - } + } else null, + ) } } /** Draws the secondary credential selection page, where all sign-in options are listed. */ -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AllSignInOptionCard( providerInfoList: List<ProviderInfo>, @@ -283,87 +244,53 @@ fun AllSignInOptionCard( val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList - ContainerCard() { - Column() { - TopAppBar( - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent, - ), - title = { - TextOnSurface( - text = stringResource(R.string.get_dialog_title_sign_in_options), - style = MaterialTheme.typography.titleMedium + SheetContainerCard(topAppBar = { + MoreOptionTopAppBar( + text = stringResource(R.string.get_dialog_title_sign_in_options), + onNavigationIconClicked = if (isNoAccount) onCancel else onBackButtonClicked, + ) + }) { + LazyColumn { + // For username + items(sortedUserNameToCredentialEntryList) { item -> + PerUserNameCredentials( + perUserNameCredentialEntryList = item, + onEntrySelected = onEntrySelected, + ) + } + // Locked password manager + if (authenticationEntryList.isNotEmpty()) { + item { + LockedCredentials( + authenticationEntryList = authenticationEntryList, + onEntrySelected = onEntrySelected, + ) + } + } + // From another device + val remoteEntry = providerDisplayInfo.remoteEntry + if (remoteEntry != null) { + item { + RemoteEntryCard( + remoteEntry = remoteEntry, + onEntrySelected = onEntrySelected, ) - }, - navigationIcon = { - IconButton(onClick = if (isNoAccount) onCancel else onBackButtonClicked) { - Icon( - Icons.Filled.ArrowBack, - contentDescription = stringResource( - R.string.accessibility_back_arrow_button) - ) - } - }, - modifier = Modifier.padding(top = 12.dp) - ) - - ContainerCard( - shape = MaterialTheme.shapes.large, - modifier = Modifier - .padding(start = 24.dp, end = 24.dp, bottom = 24.dp) - .align(alignment = Alignment.CenterHorizontally), - ) { - LazyColumn( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - // For username - items(sortedUserNameToCredentialEntryList) { item -> - PerUserNameCredentials( - perUserNameCredentialEntryList = item, - onEntrySelected = onEntrySelected, - ) - } - // Locked password manager - if (authenticationEntryList.isNotEmpty()) { - item { - LockedCredentials( - authenticationEntryList = authenticationEntryList, - onEntrySelected = onEntrySelected, - ) - } - } - item { - Divider( - thickness = 8.dp, - color = Color.Transparent, - ) - } - // From another device - val remoteEntry = providerDisplayInfo.remoteEntry - if (remoteEntry != null) { - item { - RemoteEntryCard( - remoteEntry = remoteEntry, - onEntrySelected = onEntrySelected, - ) - } - } - item { - Divider( - thickness = 1.dp, - color = Color.LightGray, - modifier = Modifier.padding(top = 16.dp) - ) - } - // Manage sign-ins (action chips) - item { - ActionChips( - providerInfoList = providerInfoList, - onEntrySelected = onEntrySelected - ) - } } } + item { + Divider( + thickness = 1.dp, + color = Color.LightGray, + modifier = Modifier.padding(top = 16.dp) + ) + } + // Manage sign-ins (action chips) + item { + ActionChips( + providerInfoList = providerInfoList, + onEntrySelected = onEntrySelected + ) + } } } } @@ -381,17 +308,11 @@ fun ActionChips( return } - TextSecondary( - text = stringResource(R.string.get_dialog_heading_manage_sign_ins), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 8.dp) + CredentialListSectionHeader( + text = stringResource(R.string.get_dialog_heading_manage_sign_ins) ) - // TODO: tweak padding. - ContainerCard( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.medium, - ) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + CredentialContainerCard { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { actionChips.forEach { ActionEntryRow(it, onEntrySelected) } @@ -404,38 +325,20 @@ fun RemoteEntryCard( remoteEntry: RemoteEntryInfo, onEntrySelected: (BaseEntry) -> Unit, ) { - TextSecondary( - text = stringResource(R.string.get_dialog_heading_from_another_device), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 8.dp) + CredentialListSectionHeader( + text = stringResource(R.string.get_dialog_heading_from_another_device) ) - ContainerCard( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.medium, - ) { + CredentialContainerCard { Column( modifier = Modifier.fillMaxWidth().wrapContentHeight(), verticalArrangement = Arrangement.spacedBy(2.dp), ) { Entry( onClick = { onEntrySelected(remoteEntry) }, - icon = { - Icon( - painter = painterResource(R.drawable.ic_other_devices), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.padding(start = 10.dp) - ) - }, - label = { - TextOnSurfaceVariant( - text = stringResource( - R.string.get_dialog_option_headline_use_a_different_device), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 10.dp, top = 18.dp, bottom = 18.dp) - .align(alignment = Alignment.CenterHorizontally) - ) - } + iconPainter = painterResource(R.drawable.ic_other_devices), + entryHeadlineText = stringResource( + R.string.get_dialog_option_headline_use_a_different_device + ), ) } } @@ -446,15 +349,10 @@ fun LockedCredentials( authenticationEntryList: List<AuthenticationEntryInfo>, onEntrySelected: (BaseEntry) -> Unit, ) { - TextSecondary( - text = stringResource(R.string.get_dialog_heading_locked_password_managers), - style = MaterialTheme.typography.labelLarge, - modifier = Modifier.padding(vertical = 8.dp) + CredentialListSectionHeader( + text = stringResource(R.string.get_dialog_heading_locked_password_managers) ) - ContainerCard( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.medium, - ) { + CredentialContainerCard { Column( modifier = Modifier.fillMaxWidth().wrapContentHeight(), verticalArrangement = Arrangement.spacedBy(2.dp), @@ -471,17 +369,12 @@ fun PerUserNameCredentials( perUserNameCredentialEntryList: PerUserNameCredentialEntryList, onEntrySelected: (BaseEntry) -> Unit, ) { - TextSecondary( + CredentialListSectionHeader( text = stringResource( R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName - ), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 8.dp) + ) ) - ContainerCard( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - shape = MaterialTheme.shapes.medium, - ) { + CredentialContainerCard { Column( modifier = Modifier.fillMaxWidth().wrapContentHeight(), verticalArrangement = Arrangement.spacedBy(2.dp), @@ -500,53 +393,28 @@ fun CredentialEntryRow( ) { Entry( onClick = { onEntrySelected(credentialEntryInfo) }, - icon = { - if (credentialEntryInfo.icon != null) { - Image( - modifier = Modifier.padding(start = 10.dp).size(32.dp), - bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null, - ) - } else { - Icon( - modifier = Modifier.padding(start = 10.dp).size(32.dp), - painter = painterResource(R.drawable.ic_other_sign_in), - contentDescription = null, - tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant - ) - } + iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(), + // Fall back to iconPainter if iconImageBitmap isn't available + iconPainter = + if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in) + else null, + entryHeadlineText = credentialEntryInfo.userName, + entrySecondLineText = if ( + credentialEntryInfo.credentialType == CredentialType.PASSWORD) { + "••••••••••••" + } else { + if (TextUtils.isEmpty(credentialEntryInfo.displayName)) + credentialEntryInfo.credentialTypeDisplayName + else + credentialEntryInfo.credentialTypeDisplayName + + stringResource( + R.string.get_dialog_sign_in_type_username_separator + ) + + credentialEntryInfo.displayName }, - label = { - Column() { - // TODO: fix the text values. - TextOnSurfaceVariant( - text = credentialEntryInfo.userName, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, start = 5.dp) - ) - TextSecondary( - text = if ( - credentialEntryInfo.credentialType == CredentialType.PASSWORD) { - "••••••••••••" - } else { - if (TextUtils.isEmpty(credentialEntryInfo.displayName)) - credentialEntryInfo.credentialTypeDisplayName - else - credentialEntryInfo.credentialTypeDisplayName + - stringResource( - R.string.get_dialog_sign_in_type_username_separator - ) + - credentialEntryInfo.displayName - }, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp, start = 5.dp) - ) - } - } ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AuthenticationEntryRow( authenticationEntryInfo: AuthenticationEntryInfo, @@ -554,144 +422,66 @@ fun AuthenticationEntryRow( ) { Entry( onClick = { onEntrySelected(authenticationEntryInfo) }, - icon = { - Image( - modifier = Modifier.padding(start = 10.dp).size(32.dp), - bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null - ) - }, - label = { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp), - ) { - Column() { - TextOnSurfaceVariant( - text = authenticationEntryInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp) - ) - TextSecondary( - text = stringResource( - if (authenticationEntryInfo.isUnlockedAndEmpty) - R.string.locked_credential_entry_label_subtext_no_sign_in - else R.string.locked_credential_entry_label_subtext_tap_to_unlock - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp) - ) - } - if (!authenticationEntryInfo.isUnlockedAndEmpty) { - Icon( - Icons.Outlined.Lock, - null, - Modifier.align(alignment = Alignment.CenterVertically).padding(end = 10.dp), - ) - } - } - } + iconImageBitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(), + entryHeadlineText = authenticationEntryInfo.title, + entrySecondLineText = stringResource( + if (authenticationEntryInfo.isUnlockedAndEmpty) + R.string.locked_credential_entry_label_subtext_no_sign_in + else R.string.locked_credential_entry_label_subtext_tap_to_unlock + ), + isLockedAuthEntry = !authenticationEntryInfo.isUnlockedAndEmpty, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun ActionEntryRow( actionEntryInfo: ActionEntryInfo, onEntrySelected: (BaseEntry) -> Unit, ) { - TransparentBackgroundEntry( - icon = { - Image( - modifier = Modifier.padding(start = 10.dp).size(24.dp), - bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(), - contentDescription = null, - ) - }, - label = { - Column() { - TextOnSurfaceVariant( - text = actionEntryInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 8.dp), - ) - if (actionEntryInfo.subTitle != null) { - TextSecondary( - text = actionEntryInfo.subTitle, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(start = 8.dp), - ) - } - } - }, + ActionEntry( + iconImageBitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(), + entryHeadlineText = actionEntryInfo.title, + entrySecondLineText = actionEntryInfo.subTitle, onClick = { onEntrySelected(actionEntryInfo) }, ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun RemoteCredentialSnackBarScreen( onClick: (Boolean) -> Unit, onCancel: () -> Unit, ) { - // TODO: Change the height, width and position according to the design Snackbar( - modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp), - shape = EntryShape.FullMediumRoundedCorner, - containerColor = LocalAndroidColorScheme.current.colorBackground, - contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, action = { TextButton( + modifier = Modifier.padding(top = 12.dp, bottom = 12.dp, start = 16.dp), onClick = { onClick(true) }, ) { - Text(text = stringResource(R.string.snackbar_action)) - } - }, - dismissAction = { - IconButton(onClick = onCancel) { - Icon( - Icons.Filled.Close, - contentDescription = stringResource( - R.string.accessibility_close_button - ), - tint = LocalAndroidColorScheme.current.colorAccentTertiary + SnackbarActionText( + text = stringResource(R.string.snackbar_action), + Modifier.padding(vertical = 6.dp) ) } }, - ) { - Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for)) - } + onDismiss = onCancel, + contentText = stringResource(R.string.get_dialog_use_saved_passkey_for), + ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun EmptyAuthEntrySnackBarScreen( authenticationEntryList: List<AuthenticationEntryInfo>, onCancel: () -> Unit, onLastLockedAuthEntryNotFound: () -> Unit, ) { - val lastLocked = authenticationEntryList.firstOrNull({it.isLastUnlocked}) + val lastLocked = authenticationEntryList.firstOrNull({ it.isLastUnlocked }) if (lastLocked == null) { onLastLockedAuthEntryNotFound() return } - // TODO: Change the height, width and position according to the design Snackbar( - modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp), - shape = EntryShape.FullMediumRoundedCorner, - containerColor = LocalAndroidColorScheme.current.colorBackground, - contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - dismissAction = { - IconButton(onClick = onCancel) { - Icon( - Icons.Filled.Close, - contentDescription = stringResource(R.string.accessibility_close_button), - tint = LocalAndroidColorScheme.current.colorAccentTertiary - ) - } - }, - ) { - Text(text = stringResource(R.string.no_sign_in_info_in, lastLocked.title)) - } + onDismiss = onCancel, + contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName), + ) }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 49415c01033d..02e75578eb73 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -96,6 +96,7 @@ class AuthenticationEntryInfo( pendingIntent: PendingIntent?, fillInIntent: Intent?, val title: String, + val providerDisplayName: String, val icon: Drawable, // The entry had been unlocked and turned out to be empty. Used to determine whether to // show "Tap to unlock" or "No sign-in info" for this entry. diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt index 15ae3295416b..120e4938c322 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt @@ -22,12 +22,14 @@ import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color import com.android.internal.R +/** File copied from PlatformComposeCore. */ + /** CompositionLocal used to pass [AndroidColorScheme] down the tree. */ val LocalAndroidColorScheme = staticCompositionLocalOf<AndroidColorScheme> { throw IllegalStateException( "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " + - "Composable surrounded by a CredentialSelectorTheme {}." + "Composable surrounded by a PlatformTheme {}." ) } @@ -38,7 +40,6 @@ val LocalAndroidColorScheme = * most of the colors in this class will be removed in favor of their M3 counterpart. */ class AndroidColorScheme internal constructor(context: Context) { - val colorPrimary = getColor(context, R.attr.colorPrimary) val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark) val colorAccent = getColor(context, R.attr.colorAccent) @@ -66,10 +67,12 @@ class AndroidColorScheme internal constructor(context: Context) { val colorForeground = getColor(context, R.attr.colorForeground) val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse) - private fun getColor(context: Context, attr: Int): Color { - val ta = context.obtainStyledAttributes(intArrayOf(attr)) - @ColorInt val color = ta.getColor(0, 0) - ta.recycle() - return Color(color) + companion object { + fun getColor(context: Context, attr: Int): Color { + val ta = context.obtainStyledAttributes(intArrayOf(attr)) + @ColorInt val color = ta.getColor(0, 0) + ta.recycle() + return Color(color) + } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt index abb4bfbf915e..c9238459633c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt @@ -1,14 +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.credentialmanager.ui.theme +import android.annotation.AttrRes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext -val Grey100 = Color(0xFFF1F3F4) -val Purple200 = Color(0xFFBB86FC) -val Purple500 = Color(0xFF6200EE) -val Purple700 = Color(0xFF3700B3) -val Teal200 = Color(0xFF03DAC5) -val lightColorAccentSecondary = Color(0xFFC2E7FF) -val lightBackgroundColor = Color(0xFFF0F0F0) -val lightSurface1 = Color(0xFF6991D6) -val textColorSecondary = Color(0xFF40484B) -val textColorPrimary = Color(0xFF191C1D) +/** Read the [Color] from the given [attribute]. */ +@Composable +@ReadOnlyComposable +fun colorAttr(@AttrRes attribute: Int): Color { + return AndroidColorScheme.getColor(LocalContext.current, attribute) +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt new file mode 100644 index 000000000000..662199a4bba5 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt @@ -0,0 +1,64 @@ +/* + * 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.credentialmanager.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.android.credentialmanager.ui.theme.typography.TypeScaleTokens +import com.android.credentialmanager.ui.theme.typography.TypefaceNames +import com.android.credentialmanager.ui.theme.typography.TypefaceTokens +import com.android.credentialmanager.ui.theme.typography.TypographyTokens +import com.android.credentialmanager.ui.theme.typography.platformTypography + +/** File copied from PlatformComposeCore. */ + +/** The Material 3 theme that should wrap all Platform Composables. */ +@Composable +fun PlatformTheme( + isDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val context = LocalContext.current + + // TODO(b/230605885): Define our color scheme. + val colorScheme = + if (isDarkTheme) { + dynamicDarkColorScheme(context) + } else { + dynamicLightColorScheme(context) + } + val androidColorScheme = AndroidColorScheme(context) + val typefaceNames = remember(context) { TypefaceNames.get(context) } + val typography = + remember(typefaceNames) { + platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames)))) + } + + MaterialTheme(colorScheme, typography = typography) { + CompositionLocalProvider( + LocalAndroidColorScheme provides androidColorScheme, + ) { + content() + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt deleted file mode 100644 index 3ca0e4494ab6..000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.android.credentialmanager.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.platform.LocalContext - -@Composable -fun CredentialSelectorTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - val context = LocalContext.current - - val colorScheme = - if (darkTheme) { - dynamicDarkColorScheme(context) - } else { - dynamicLightColorScheme(context) - } - val androidColorScheme = AndroidColorScheme(context) - val typography = Typography - - MaterialTheme( - colorScheme, - typography = typography, - shapes = Shapes - ) { - CompositionLocalProvider( - LocalAndroidColorScheme provides androidColorScheme, - ) { - content() - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt deleted file mode 100644 index e09abbb3ffff..000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.android.credentialmanager.ui.theme - -import androidx.compose.material3.Typography -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp - -// Set of Material typography styles to start with -val Typography = Typography( - titleMedium = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 24.sp, - lineHeight = 32.sp, - ), - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 14.sp, - lineHeight = 20.sp, - ), - bodyMedium = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 14.sp, - lineHeight = 20.sp, - color = textColorSecondary - ), - labelLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - lineHeight = 20.sp, - ), - titleLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Medium, - fontSize = 16.sp, - lineHeight = 24.sp, - color = textColorPrimary - ), - - /* Other default text styles to override - button = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.W500, - fontSize = 14.sp - ), - caption = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 12.sp - ) - */ -) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt new file mode 100644 index 000000000000..984e4f19e4d4 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt @@ -0,0 +1,48 @@ +/* + * 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.credentialmanager.ui.theme.typography + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Typography + +/** File copied from PlatformComposeCore. */ + +/** + * The typography for Platform Compose code. + * + * Do not use directly and call [MaterialTheme.typography] instead to access the different text + * styles. + */ +internal fun platformTypography(typographyTokens: TypographyTokens): Typography { + return Typography( + displayLarge = typographyTokens.displayLarge, + displayMedium = typographyTokens.displayMedium, + displaySmall = typographyTokens.displaySmall, + headlineLarge = typographyTokens.headlineLarge, + headlineMedium = typographyTokens.headlineMedium, + headlineSmall = typographyTokens.headlineSmall, + titleLarge = typographyTokens.titleLarge, + titleMedium = typographyTokens.titleMedium, + titleSmall = typographyTokens.titleSmall, + bodyLarge = typographyTokens.bodyLarge, + bodyMedium = typographyTokens.bodyMedium, + bodySmall = typographyTokens.bodySmall, + labelLarge = typographyTokens.labelLarge, + labelMedium = typographyTokens.labelMedium, + labelSmall = typographyTokens.labelSmall, + ) +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt new file mode 100644 index 000000000000..b2dd20720f6a --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt @@ -0,0 +1,98 @@ +/* + * 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.credentialmanager.ui.theme.typography + +import androidx.compose.ui.unit.sp + +/** File copied from PlatformComposeCore. */ +internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) { + val bodyLargeFont = typefaceTokens.plain + val bodyLargeLineHeight = 24.0.sp + val bodyLargeSize = 16.sp + val bodyLargeTracking = 0.0.sp + val bodyLargeWeight = TypefaceTokens.WeightRegular + val bodyMediumFont = typefaceTokens.plain + val bodyMediumLineHeight = 20.0.sp + val bodyMediumSize = 14.sp + val bodyMediumTracking = 0.0.sp + val bodyMediumWeight = TypefaceTokens.WeightRegular + val bodySmallFont = typefaceTokens.plain + val bodySmallLineHeight = 16.0.sp + val bodySmallSize = 12.sp + val bodySmallTracking = 0.1.sp + val bodySmallWeight = TypefaceTokens.WeightRegular + val displayLargeFont = typefaceTokens.brand + val displayLargeLineHeight = 64.0.sp + val displayLargeSize = 57.sp + val displayLargeTracking = 0.0.sp + val displayLargeWeight = TypefaceTokens.WeightRegular + val displayMediumFont = typefaceTokens.brand + val displayMediumLineHeight = 52.0.sp + val displayMediumSize = 45.sp + val displayMediumTracking = 0.0.sp + val displayMediumWeight = TypefaceTokens.WeightRegular + val displaySmallFont = typefaceTokens.brand + val displaySmallLineHeight = 44.0.sp + val displaySmallSize = 36.sp + val displaySmallTracking = 0.0.sp + val displaySmallWeight = TypefaceTokens.WeightRegular + val headlineLargeFont = typefaceTokens.brand + val headlineLargeLineHeight = 40.0.sp + val headlineLargeSize = 32.sp + val headlineLargeTracking = 0.0.sp + val headlineLargeWeight = TypefaceTokens.WeightRegular + val headlineMediumFont = typefaceTokens.brand + val headlineMediumLineHeight = 36.0.sp + val headlineMediumSize = 28.sp + val headlineMediumTracking = 0.0.sp + val headlineMediumWeight = TypefaceTokens.WeightRegular + val headlineSmallFont = typefaceTokens.brand + val headlineSmallLineHeight = 32.0.sp + val headlineSmallSize = 24.sp + val headlineSmallTracking = 0.0.sp + val headlineSmallWeight = TypefaceTokens.WeightRegular + val labelLargeFont = typefaceTokens.plain + val labelLargeLineHeight = 20.0.sp + val labelLargeSize = 14.sp + val labelLargeTracking = 0.0.sp + val labelLargeWeight = TypefaceTokens.WeightMedium + val labelMediumFont = typefaceTokens.plain + val labelMediumLineHeight = 16.0.sp + val labelMediumSize = 12.sp + val labelMediumTracking = 0.1.sp + val labelMediumWeight = TypefaceTokens.WeightMedium + val labelSmallFont = typefaceTokens.plain + val labelSmallLineHeight = 16.0.sp + val labelSmallSize = 11.sp + val labelSmallTracking = 0.1.sp + val labelSmallWeight = TypefaceTokens.WeightMedium + val titleLargeFont = typefaceTokens.brand + val titleLargeLineHeight = 28.0.sp + val titleLargeSize = 22.sp + val titleLargeTracking = 0.0.sp + val titleLargeWeight = TypefaceTokens.WeightRegular + val titleMediumFont = typefaceTokens.plain + val titleMediumLineHeight = 24.0.sp + val titleMediumSize = 16.sp + val titleMediumTracking = 0.0.sp + val titleMediumWeight = TypefaceTokens.WeightMedium + val titleSmallFont = typefaceTokens.plain + val titleSmallLineHeight = 20.0.sp + val titleSmallSize = 14.sp + val titleSmallTracking = 0.0.sp + val titleSmallWeight = TypefaceTokens.WeightMedium +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt new file mode 100644 index 000000000000..3cc761f1cc60 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt @@ -0,0 +1,75 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalTextApi::class) + +package com.android.credentialmanager.ui.theme.typography + +import android.content.Context +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.font.DeviceFontFamilyName +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight + +/** File copied from PlatformComposeCore. */ +internal class TypefaceTokens(typefaceNames: TypefaceNames) { + companion object { + val WeightMedium = FontWeight.Medium + val WeightRegular = FontWeight.Normal + } + + private val brandFont = DeviceFontFamilyName(typefaceNames.brand) + private val plainFont = DeviceFontFamilyName(typefaceNames.plain) + + val brand = + FontFamily( + Font(brandFont, weight = WeightMedium), + Font(brandFont, weight = WeightRegular), + ) + val plain = + FontFamily( + Font(plainFont, weight = WeightMedium), + Font(plainFont, weight = WeightRegular), + ) +} + +internal data class TypefaceNames +private constructor( + val brand: String, + val plain: String, +) { + private enum class Config(val configName: String, val default: String) { + Brand("config_headlineFontFamily", "sans-serif"), + Plain("config_bodyFontFamily", "sans-serif"), + } + + companion object { + fun get(context: Context): TypefaceNames { + return TypefaceNames( + brand = getTypefaceName(context, Config.Brand), + plain = getTypefaceName(context, Config.Plain), + ) + } + + private fun getTypefaceName(context: Context, config: Config): String { + return context + .getString(context.resources.getIdentifier(config.configName, "string", "android")) + .takeIf { it.isNotEmpty() } + ?: config.default + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt new file mode 100644 index 000000000000..aadab92f40cc --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt @@ -0,0 +1,143 @@ +/* + * 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.credentialmanager.ui.theme.typography + +import androidx.compose.ui.text.TextStyle + +/** File copied from PlatformComposeCore. */ +internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) { + val bodyLarge = + TextStyle( + fontFamily = typeScaleTokens.bodyLargeFont, + fontWeight = typeScaleTokens.bodyLargeWeight, + fontSize = typeScaleTokens.bodyLargeSize, + lineHeight = typeScaleTokens.bodyLargeLineHeight, + letterSpacing = typeScaleTokens.bodyLargeTracking, + ) + val bodyMedium = + TextStyle( + fontFamily = typeScaleTokens.bodyMediumFont, + fontWeight = typeScaleTokens.bodyMediumWeight, + fontSize = typeScaleTokens.bodyMediumSize, + lineHeight = typeScaleTokens.bodyMediumLineHeight, + letterSpacing = typeScaleTokens.bodyMediumTracking, + ) + val bodySmall = + TextStyle( + fontFamily = typeScaleTokens.bodySmallFont, + fontWeight = typeScaleTokens.bodySmallWeight, + fontSize = typeScaleTokens.bodySmallSize, + lineHeight = typeScaleTokens.bodySmallLineHeight, + letterSpacing = typeScaleTokens.bodySmallTracking, + ) + val displayLarge = + TextStyle( + fontFamily = typeScaleTokens.displayLargeFont, + fontWeight = typeScaleTokens.displayLargeWeight, + fontSize = typeScaleTokens.displayLargeSize, + lineHeight = typeScaleTokens.displayLargeLineHeight, + letterSpacing = typeScaleTokens.displayLargeTracking, + ) + val displayMedium = + TextStyle( + fontFamily = typeScaleTokens.displayMediumFont, + fontWeight = typeScaleTokens.displayMediumWeight, + fontSize = typeScaleTokens.displayMediumSize, + lineHeight = typeScaleTokens.displayMediumLineHeight, + letterSpacing = typeScaleTokens.displayMediumTracking, + ) + val displaySmall = + TextStyle( + fontFamily = typeScaleTokens.displaySmallFont, + fontWeight = typeScaleTokens.displaySmallWeight, + fontSize = typeScaleTokens.displaySmallSize, + lineHeight = typeScaleTokens.displaySmallLineHeight, + letterSpacing = typeScaleTokens.displaySmallTracking, + ) + val headlineLarge = + TextStyle( + fontFamily = typeScaleTokens.headlineLargeFont, + fontWeight = typeScaleTokens.headlineLargeWeight, + fontSize = typeScaleTokens.headlineLargeSize, + lineHeight = typeScaleTokens.headlineLargeLineHeight, + letterSpacing = typeScaleTokens.headlineLargeTracking, + ) + val headlineMedium = + TextStyle( + fontFamily = typeScaleTokens.headlineMediumFont, + fontWeight = typeScaleTokens.headlineMediumWeight, + fontSize = typeScaleTokens.headlineMediumSize, + lineHeight = typeScaleTokens.headlineMediumLineHeight, + letterSpacing = typeScaleTokens.headlineMediumTracking, + ) + val headlineSmall = + TextStyle( + fontFamily = typeScaleTokens.headlineSmallFont, + fontWeight = typeScaleTokens.headlineSmallWeight, + fontSize = typeScaleTokens.headlineSmallSize, + lineHeight = typeScaleTokens.headlineSmallLineHeight, + letterSpacing = typeScaleTokens.headlineSmallTracking, + ) + val labelLarge = + TextStyle( + fontFamily = typeScaleTokens.labelLargeFont, + fontWeight = typeScaleTokens.labelLargeWeight, + fontSize = typeScaleTokens.labelLargeSize, + lineHeight = typeScaleTokens.labelLargeLineHeight, + letterSpacing = typeScaleTokens.labelLargeTracking, + ) + val labelMedium = + TextStyle( + fontFamily = typeScaleTokens.labelMediumFont, + fontWeight = typeScaleTokens.labelMediumWeight, + fontSize = typeScaleTokens.labelMediumSize, + lineHeight = typeScaleTokens.labelMediumLineHeight, + letterSpacing = typeScaleTokens.labelMediumTracking, + ) + val labelSmall = + TextStyle( + fontFamily = typeScaleTokens.labelSmallFont, + fontWeight = typeScaleTokens.labelSmallWeight, + fontSize = typeScaleTokens.labelSmallSize, + lineHeight = typeScaleTokens.labelSmallLineHeight, + letterSpacing = typeScaleTokens.labelSmallTracking, + ) + val titleLarge = + TextStyle( + fontFamily = typeScaleTokens.titleLargeFont, + fontWeight = typeScaleTokens.titleLargeWeight, + fontSize = typeScaleTokens.titleLargeSize, + lineHeight = typeScaleTokens.titleLargeLineHeight, + letterSpacing = typeScaleTokens.titleLargeTracking, + ) + val titleMedium = + TextStyle( + fontFamily = typeScaleTokens.titleMediumFont, + fontWeight = typeScaleTokens.titleMediumWeight, + fontSize = typeScaleTokens.titleMediumSize, + lineHeight = typeScaleTokens.titleMediumLineHeight, + letterSpacing = typeScaleTokens.titleMediumTracking, + ) + val titleSmall = + TextStyle( + fontFamily = typeScaleTokens.titleSmallFont, + fontWeight = typeScaleTokens.titleSmallWeight, + fontSize = typeScaleTokens.titleSmallSize, + lineHeight = typeScaleTokens.titleSmallLineHeight, + letterSpacing = typeScaleTokens.titleSmallTracking, + ) +} |