summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/CredentialManager/res/values/strings.xml2
-rw-r--r--packages/CredentialManager/res/values/themes.xml6
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt46
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt4
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt32
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt9
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt62
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt30
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Columns.kt31
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/ConfirmButton.kt15
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt29
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt291
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/HeadlineIcon.kt81
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt48
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt92
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt155
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt1103
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt584
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt17
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt36
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/PlatformTheme.kt64
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt38
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt56
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/PlatformTypography.kt48
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypeScaleTokens.kt98
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypefaceTokens.kt75
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/typography/TypographyTokens.kt143
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,
+ )
+}