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, +        ) +}  |