Merge "Updated the CredentialManager UI style." into udc-dev am: 97a4d91c0f
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21532412 Change-Id: If4b1028e25170fdc22f3e456964f2a6bd754b64b Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -94,6 +94,8 @@
|
|||||||
<string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
|
<string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
|
||||||
<!-- Spoken content description of the close "X" icon button. -->
|
<!-- Spoken content description of the close "X" icon button. -->
|
||||||
<string name="accessibility_close_button">Close</string>
|
<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. -->
|
<!-- 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] -->
|
<!-- 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] -->
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
|
<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:windowContentOverlay">@null</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowBackground">@android:color/transparent</item>
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
<item name="android:windowIsTranslucent">true</item>
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="fontFamily">google-sans</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package com.android.credentialmanager
|
package com.android.credentialmanager
|
||||||
|
|
||||||
import android.app.slice.Slice
|
|
||||||
import android.app.slice.SliceSpec
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.credentials.CreateCredentialRequest
|
import android.credentials.CreateCredentialRequest
|
||||||
@@ -35,7 +33,6 @@ import android.credentials.ui.RequestInfo
|
|||||||
import android.credentials.ui.BaseDialogResult
|
import android.credentials.ui.BaseDialogResult
|
||||||
import android.credentials.ui.ProviderPendingIntentResponse
|
import android.credentials.ui.ProviderPendingIntentResponse
|
||||||
import android.credentials.ui.UserSelectionDialogResult
|
import android.credentials.ui.UserSelectionDialogResult
|
||||||
import android.net.Uri
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -230,7 +227,8 @@ class CredentialManagerRepo(
|
|||||||
context,
|
context,
|
||||||
"key1", "subkey-1", "elisa.beckett@gmail.com",
|
"key1", "subkey-1", "elisa.beckett@gmail.com",
|
||||||
20, 7, 27, Instant.ofEpochSecond(10L),
|
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(
|
CreateTestUtils.newCreateEntry(
|
||||||
context,
|
context,
|
||||||
@@ -239,11 +237,9 @@ class CredentialManagerRepo(
|
|||||||
null
|
null
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
).setRemoteEntry(
|
||||||
.setRemoteEntry(
|
CreateTestUtils.newRemoteCreateEntry(context, "key2", "subkey-1")
|
||||||
newRemoteEntry("key2", "subkey-1")
|
).build(),
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
CreateCredentialProviderData
|
CreateCredentialProviderData
|
||||||
.Builder("com.dashlane")
|
.Builder("com.dashlane")
|
||||||
.setSaveEntries(
|
.setSaveEntries(
|
||||||
@@ -258,11 +254,11 @@ class CredentialManagerRepo(
|
|||||||
context,
|
context,
|
||||||
"key1", "subkey-4", "elisa.work@dashlane.com",
|
"key1", "subkey-4", "elisa.work@dashlane.com",
|
||||||
20, 7, 27, Instant.ofEpochSecond(14L),
|
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(
|
).setRemoteEntry(
|
||||||
newRemoteEntry("key4", "subkey-1")
|
GetTestUtils.newRemoteCredentialEntry(context, "key4", "subkey-1")
|
||||||
).build(),
|
).build(),
|
||||||
GetCredentialProviderData.Builder("com.dashlane")
|
GetCredentialProviderData.Builder("com.dashlane")
|
||||||
.setCredentialEntries(
|
.setCredentialEntries(
|
||||||
@@ -333,10 +329,12 @@ class CredentialManagerRepo(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
).setAuthenticationEntries(
|
).setAuthenticationEntries(
|
||||||
listOf(GetTestUtils.newAuthenticationEntry(
|
listOf(
|
||||||
|
GetTestUtils.newAuthenticationEntry(
|
||||||
context, "key2", "subkey-1", "foo@email.com",
|
context, "key2", "subkey-1", "foo@email.com",
|
||||||
AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT
|
AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
).setActionChips(
|
).setActionChips(
|
||||||
listOf(
|
listOf(
|
||||||
GetTestUtils.newActionEntry(
|
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 {
|
private fun testCreatePasskeyRequestInfo(): RequestInfo {
|
||||||
val request = CreatePublicKeyCredentialRequest(
|
val request = CreatePublicKeyCredentialRequest(
|
||||||
"{\"extensions\": {\n" +
|
"{\"extensions\": {\n" +
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import com.android.credentialmanager.createflow.CreateCredentialScreen
|
|||||||
import com.android.credentialmanager.createflow.hasContentToDisplay
|
import com.android.credentialmanager.createflow.hasContentToDisplay
|
||||||
import com.android.credentialmanager.getflow.GetCredentialScreen
|
import com.android.credentialmanager.getflow.GetCredentialScreen
|
||||||
import com.android.credentialmanager.getflow.hasContentToDisplay
|
import com.android.credentialmanager.getflow.hasContentToDisplay
|
||||||
import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
|
import com.android.credentialmanager.ui.theme.PlatformTheme
|
||||||
|
|
||||||
@ExperimentalMaterialApi
|
@ExperimentalMaterialApi
|
||||||
class CredentialSelectorActivity : ComponentActivity() {
|
class CredentialSelectorActivity : ComponentActivity() {
|
||||||
@@ -50,7 +50,7 @@ class CredentialSelectorActivity : ComponentActivity() {
|
|||||||
val userConfigRepo = UserConfigRepo(this)
|
val userConfigRepo = UserConfigRepo(this)
|
||||||
val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
|
val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
|
||||||
setContent {
|
setContent {
|
||||||
CredentialSelectorTheme {
|
PlatformTheme {
|
||||||
CredentialManagerBottomSheet(
|
CredentialManagerBottomSheet(
|
||||||
credManRepo,
|
credManRepo,
|
||||||
userConfigRepo
|
userConfigRepo
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ class GetFlowUtils {
|
|||||||
pendingIntent = structuredAuthEntry.pendingIntent,
|
pendingIntent = structuredAuthEntry.pendingIntent,
|
||||||
fillInIntent = entry.frameworkExtrasIntent,
|
fillInIntent = entry.frameworkExtrasIntent,
|
||||||
title = title,
|
title = title,
|
||||||
|
providerDisplayName = providerDisplayName,
|
||||||
icon = providerIcon,
|
icon = providerIcon,
|
||||||
isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED,
|
isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED,
|
||||||
isLastUnlocked =
|
isLastUnlocked =
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import android.provider.Settings
|
|||||||
import androidx.credentials.provider.CreateEntry
|
import androidx.credentials.provider.CreateEntry
|
||||||
import androidx.credentials.provider.PasswordCredentialEntry
|
import androidx.credentials.provider.PasswordCredentialEntry
|
||||||
import androidx.credentials.provider.PublicKeyCredentialEntry
|
import androidx.credentials.provider.PublicKeyCredentialEntry
|
||||||
|
import androidx.credentials.provider.RemoteCreateEntry
|
||||||
|
import androidx.credentials.provider.RemoteCredentialEntry
|
||||||
|
|
||||||
import java.time.Instant
|
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(
|
internal fun newActionEntry(
|
||||||
context: Context,
|
context: Context,
|
||||||
key: String,
|
key: String,
|
||||||
@@ -203,5 +220,20 @@ class CreateTestUtils {
|
|||||||
Intent()
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,7 +451,7 @@ private fun Modifier.bottomSheetSwipeable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Scrim(
|
internal fun Scrim(
|
||||||
color: Color,
|
color: Color,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
visible: Boolean
|
visible: Boolean
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.credentialmanager.common.ui
|
package com.android.credentialmanager.common.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import com.android.credentialmanager.R
|
import com.android.credentialmanager.R
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
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.material.icons.outlined.VisibilityOff
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
@@ -32,17 +32,22 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
|
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ActionButton(text: String, onClick: () -> Unit) {
|
fun ActionButton(text: String, onClick: () -> Unit) {
|
||||||
TextButton(
|
TextButton(
|
||||||
|
modifier = Modifier.padding(vertical = 4.dp),
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
colors = ButtonDefaults.textButtonColors(
|
colors = ButtonDefaults.textButtonColors(
|
||||||
contentColor = MaterialTheme.colorScheme.primary,
|
contentColor = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(text = text)
|
LargeLabelText(
|
||||||
|
text = text,
|
||||||
|
modifier = Modifier.padding(vertical = 10.dp, horizontal = 12.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,33 +16,73 @@
|
|||||||
|
|
||||||
package com.android.credentialmanager.common.ui
|
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.ColumnScope
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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
|
* Container card for the whole sheet.
|
||||||
* background color with surface corlor.
|
*
|
||||||
|
* Surface 1 color. No vertical padding. 24dp horizontal padding. 24dp bottom padding. 24dp top
|
||||||
|
* padding if [topAppBar] is not present, and none otherwise.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ContainerCard(
|
fun SheetContainerCard(
|
||||||
|
topAppBar: (@Composable () -> Unit)? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
shape: Shape = CardDefaults.shape,
|
|
||||||
border: BorderStroke? = null,
|
|
||||||
content: @Composable ColumnScope.() -> Unit,
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth().wrapContentHeight(),
|
||||||
shape = shape,
|
border = null,
|
||||||
border = border,
|
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
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 CredentialContainerCard(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = modifier.fillMaxWidth().wrapContentHeight(),
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
border = null,
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
),
|
),
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -16,21 +16,28 @@
|
|||||||
|
|
||||||
package com.android.credentialmanager.common.ui
|
package com.android.credentialmanager.common.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
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
|
@Composable
|
||||||
fun ConfirmButton(text: String, onClick: () -> Unit) {
|
fun ConfirmButton(text: String, onClick: () -> Unit) {
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
|
modifier = Modifier.padding(vertical = 4.dp),
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
colors = ButtonDefaults.filledTonalButtonColors(
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(text = text)
|
LargeLabelText(
|
||||||
|
text = text,
|
||||||
|
modifier = Modifier.padding(vertical = 10.dp, horizontal = 24.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -16,55 +16,314 @@
|
|||||||
|
|
||||||
package com.android.credentialmanager.common.ui
|
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.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.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.SuggestionChip
|
import androidx.compose.material3.SuggestionChip
|
||||||
import androidx.compose.material3.SuggestionChipDefaults
|
import androidx.compose.material3.SuggestionChipDefaults
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
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.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
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.EntryShape
|
||||||
|
import com.android.credentialmanager.ui.theme.Shapes
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Entry(
|
fun Entry(
|
||||||
onClick: () -> Unit,
|
|
||||||
label: @Composable () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
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(
|
SuggestionChip(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth().wrapContentHeight(),
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
shape = EntryShape.FullSmallRoundedCorner,
|
shape = EntryShape.FullSmallRoundedCorner,
|
||||||
label = label,
|
label = {
|
||||||
icon = icon,
|
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,
|
border = null,
|
||||||
colors = SuggestionChipDefaults.suggestionChipColors(
|
colors = SuggestionChipDefaults.suggestionChipColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
|
ElevationTokens.Level3
|
||||||
|
),
|
||||||
|
// TODO: remove?
|
||||||
labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
iconContentColor = 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
|
@Composable
|
||||||
fun TransparentBackgroundEntry(
|
fun ActionEntry(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
label: @Composable () -> Unit,
|
entryHeadlineText: String,
|
||||||
modifier: Modifier = Modifier,
|
entrySecondLineText: String? = null,
|
||||||
icon: @Composable (() -> Unit)? = null,
|
iconImageBitmap: ImageBitmap,
|
||||||
) {
|
) {
|
||||||
SuggestionChip(
|
SuggestionChip(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
label = label,
|
shape = Shapes.large,
|
||||||
icon = icon,
|
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,
|
border = null,
|
||||||
colors = SuggestionChipDefaults.suggestionChipColors(
|
colors = SuggestionChipDefaults.suggestionChipColors(
|
||||||
containerColor = Color.Transparent,
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,69 +16,144 @@
|
|||||||
|
|
||||||
package com.android.credentialmanager.common.ui
|
package com.android.credentialmanager.common.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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
|
@Composable
|
||||||
fun TextOnSurface(
|
fun HeadlineText(text: String, modifier: Modifier = Modifier) {
|
||||||
text: String,
|
Text(
|
||||||
modifier: Modifier = Modifier,
|
modifier = modifier.wrapContentHeight(),
|
||||||
textAlign: TextAlign? = null,
|
|
||||||
style: TextStyle,
|
|
||||||
) {
|
|
||||||
TextInternal(
|
|
||||||
text = text,
|
text = text,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
modifier = modifier,
|
textAlign = TextAlign.Center,
|
||||||
textAlign = textAlign,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
style = style,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body-medium typography; on-surface-variant color.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun TextSecondary(
|
fun BodyMediumText(text: String, modifier: Modifier = Modifier) {
|
||||||
text: String,
|
Text(
|
||||||
modifier: Modifier = Modifier,
|
modifier = modifier.wrapContentHeight(),
|
||||||
textAlign: TextAlign? = null,
|
|
||||||
style: TextStyle,
|
|
||||||
) {
|
|
||||||
TextInternal(
|
|
||||||
text = text,
|
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
|
||||||
modifier = modifier,
|
|
||||||
textAlign = textAlign,
|
|
||||||
style = style,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TextOnSurfaceVariant(
|
|
||||||
text: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
textAlign: TextAlign? = null,
|
|
||||||
style: TextStyle,
|
|
||||||
) {
|
|
||||||
TextInternal(
|
|
||||||
text = text,
|
text = text,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = modifier,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
textAlign = textAlign,
|
|
||||||
style = style,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body-small typography; on-surface-variant color.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun TextInternal(
|
fun BodySmallText(text: String, modifier: Modifier = Modifier) {
|
||||||
text: String,
|
Text(
|
||||||
color: Color,
|
modifier = modifier.wrapContentHeight(),
|
||||||
modifier: Modifier,
|
text = text,
|
||||||
textAlign: TextAlign?,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
style: TextStyle,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
) {
|
)
|
||||||
Text(text = text, color = color, modifier = modifier, textAlign = textAlign, style = style)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -20,40 +20,22 @@ import android.text.TextUtils
|
|||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
import androidx.activity.result.IntentSenderRequest
|
import androidx.activity.result.IntentSenderRequest
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
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.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.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import com.android.credentialmanager.CredentialSelectorViewModel
|
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.CredentialType
|
||||||
import com.android.credentialmanager.common.ProviderActivityState
|
import com.android.credentialmanager.common.ProviderActivityState
|
||||||
import com.android.credentialmanager.common.ui.ActionButton
|
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.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.Entry
|
||||||
import com.android.credentialmanager.common.ui.ModalBottomSheet
|
import com.android.credentialmanager.common.ui.ModalBottomSheet
|
||||||
import com.android.credentialmanager.common.ui.TextOnSurface
|
import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
|
||||||
import com.android.credentialmanager.common.ui.TextSecondary
|
import com.android.credentialmanager.common.ui.SheetContainerCard
|
||||||
import com.android.credentialmanager.common.ui.TextOnSurfaceVariant
|
import com.android.credentialmanager.common.ui.SnackbarActionText
|
||||||
import com.android.credentialmanager.common.ui.ContainerCard
|
import com.android.credentialmanager.common.ui.HeadlineText
|
||||||
import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
|
import com.android.credentialmanager.common.ui.CredentialListSectionHeader
|
||||||
import com.android.credentialmanager.ui.theme.EntryShape
|
import com.android.credentialmanager.common.ui.Snackbar
|
||||||
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GetCredentialScreen(
|
fun GetCredentialScreen(
|
||||||
@@ -154,12 +138,8 @@ fun PrimarySelectionCard(
|
|||||||
val sortedUserNameToCredentialEntryList =
|
val sortedUserNameToCredentialEntryList =
|
||||||
providerDisplayInfo.sortedUserNameToCredentialEntryList
|
providerDisplayInfo.sortedUserNameToCredentialEntryList
|
||||||
val authenticationEntryList = providerDisplayInfo.authenticationEntryList
|
val authenticationEntryList = providerDisplayInfo.authenticationEntryList
|
||||||
ContainerCard() {
|
SheetContainerCard {
|
||||||
Column() {
|
HeadlineText(
|
||||||
TextOnSurface(
|
|
||||||
modifier = Modifier.padding(all = 24.dp),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
if (sortedUserNameToCredentialEntryList
|
if (sortedUserNameToCredentialEntryList
|
||||||
.size == 1 && authenticationEntryList.isEmpty()
|
.size == 1 && authenticationEntryList.isEmpty()
|
||||||
@@ -178,15 +158,9 @@ fun PrimarySelectionCard(
|
|||||||
requestDisplayInfo.appName
|
requestDisplayInfo.appName
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
Divider(thickness = 24.dp, color = Color.Transparent)
|
||||||
ContainerCard(
|
CredentialContainerCard {
|
||||||
shape = MaterialTheme.shapes.medium,
|
val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 24.dp)
|
|
||||||
.align(alignment = Alignment.CenterHorizontally)
|
|
||||||
) {
|
|
||||||
val usernameForCredentialSize = sortedUserNameToCredentialEntryList
|
|
||||||
.size
|
|
||||||
val authenticationEntrySize = authenticationEntryList.size
|
val authenticationEntrySize = authenticationEntryList.size
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||||
@@ -228,10 +202,7 @@ fun PrimarySelectionCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Divider(
|
Divider(thickness = 24.dp, color = Color.Transparent)
|
||||||
thickness = 24.dp,
|
|
||||||
color = Color.Transparent
|
|
||||||
)
|
|
||||||
var totalEntriesCount = sortedUserNameToCredentialEntryList
|
var totalEntriesCount = sortedUserNameToCredentialEntryList
|
||||||
.flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList
|
.flatMap { it.sortedCredentialEntryList }.size + authenticationEntryList
|
||||||
.size + providerInfoList.flatMap { it.actionEntryList }.size
|
.size + providerInfoList.flatMap { it.actionEntryList }.size
|
||||||
@@ -239,38 +210,28 @@ fun PrimarySelectionCard(
|
|||||||
// Row horizontalArrangement differs on only one actionButton(should place on most
|
// 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
|
// 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)
|
// time(should be one on the left, one on the right)
|
||||||
Row(
|
CtaButtonRow(
|
||||||
horizontalArrangement =
|
leftButton = if (totalEntriesCount > 1) {
|
||||||
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) {
|
|
||||||
ActionButton(
|
ActionButton(
|
||||||
stringResource(R.string.get_dialog_use_saved_passkey_for),
|
stringResource(R.string.get_dialog_use_saved_passkey_for),
|
||||||
onMoreOptionSelected
|
onMoreOptionSelected
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Only one sign-in options exist
|
} else null,
|
||||||
if (activeEntry != null) {
|
rightButton = if (activeEntry != null) { // Only one sign-in options exist
|
||||||
|
{
|
||||||
ConfirmButton(
|
ConfirmButton(
|
||||||
stringResource(R.string.string_continue),
|
stringResource(R.string.string_continue),
|
||||||
onClick = onConfirm
|
onClick = onConfirm
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} else null,
|
||||||
Divider(
|
|
||||||
thickness = 18.dp,
|
|
||||||
color = Color.Transparent,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Draws the secondary credential selection page, where all sign-in options are listed. */
|
/** Draws the secondary credential selection page, where all sign-in options are listed. */
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AllSignInOptionCard(
|
fun AllSignInOptionCard(
|
||||||
providerInfoList: List<ProviderInfo>,
|
providerInfoList: List<ProviderInfo>,
|
||||||
@@ -283,39 +244,13 @@ fun AllSignInOptionCard(
|
|||||||
val sortedUserNameToCredentialEntryList =
|
val sortedUserNameToCredentialEntryList =
|
||||||
providerDisplayInfo.sortedUserNameToCredentialEntryList
|
providerDisplayInfo.sortedUserNameToCredentialEntryList
|
||||||
val authenticationEntryList = providerDisplayInfo.authenticationEntryList
|
val authenticationEntryList = providerDisplayInfo.authenticationEntryList
|
||||||
ContainerCard() {
|
SheetContainerCard(topAppBar = {
|
||||||
Column() {
|
MoreOptionTopAppBar(
|
||||||
TopAppBar(
|
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
),
|
|
||||||
title = {
|
|
||||||
TextOnSurface(
|
|
||||||
text = stringResource(R.string.get_dialog_title_sign_in_options),
|
text = stringResource(R.string.get_dialog_title_sign_in_options),
|
||||||
style = MaterialTheme.typography.titleMedium
|
onNavigationIconClicked = if (isNoAccount) onCancel else onBackButtonClicked,
|
||||||
)
|
)
|
||||||
},
|
}) {
|
||||||
navigationIcon = {
|
LazyColumn {
|
||||||
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
|
// For username
|
||||||
items(sortedUserNameToCredentialEntryList) { item ->
|
items(sortedUserNameToCredentialEntryList) { item ->
|
||||||
PerUserNameCredentials(
|
PerUserNameCredentials(
|
||||||
@@ -332,12 +267,6 @@ fun AllSignInOptionCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
|
||||||
Divider(
|
|
||||||
thickness = 8.dp,
|
|
||||||
color = Color.Transparent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// From another device
|
// From another device
|
||||||
val remoteEntry = providerDisplayInfo.remoteEntry
|
val remoteEntry = providerDisplayInfo.remoteEntry
|
||||||
if (remoteEntry != null) {
|
if (remoteEntry != null) {
|
||||||
@@ -365,8 +294,6 @@ fun AllSignInOptionCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: create separate rows for primary and secondary pages.
|
// TODO: create separate rows for primary and secondary pages.
|
||||||
// TODO: reuse rows and columns across types.
|
// TODO: reuse rows and columns across types.
|
||||||
@@ -381,17 +308,11 @@ fun ActionChips(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSecondary(
|
CredentialListSectionHeader(
|
||||||
text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
|
text = stringResource(R.string.get_dialog_heading_manage_sign_ins)
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
|
||||||
)
|
)
|
||||||
// TODO: tweak padding.
|
CredentialContainerCard {
|
||||||
ContainerCard(
|
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
) {
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
actionChips.forEach {
|
actionChips.forEach {
|
||||||
ActionEntryRow(it, onEntrySelected)
|
ActionEntryRow(it, onEntrySelected)
|
||||||
}
|
}
|
||||||
@@ -404,38 +325,20 @@ fun RemoteEntryCard(
|
|||||||
remoteEntry: RemoteEntryInfo,
|
remoteEntry: RemoteEntryInfo,
|
||||||
onEntrySelected: (BaseEntry) -> Unit,
|
onEntrySelected: (BaseEntry) -> Unit,
|
||||||
) {
|
) {
|
||||||
TextSecondary(
|
CredentialListSectionHeader(
|
||||||
text = stringResource(R.string.get_dialog_heading_from_another_device),
|
text = stringResource(R.string.get_dialog_heading_from_another_device)
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
|
||||||
)
|
)
|
||||||
ContainerCard(
|
CredentialContainerCard {
|
||||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
Entry(
|
Entry(
|
||||||
onClick = { onEntrySelected(remoteEntry) },
|
onClick = { onEntrySelected(remoteEntry) },
|
||||||
icon = {
|
iconPainter = painterResource(R.drawable.ic_other_devices),
|
||||||
Icon(
|
entryHeadlineText = stringResource(
|
||||||
painter = painterResource(R.drawable.ic_other_devices),
|
R.string.get_dialog_option_headline_use_a_different_device
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,15 +349,10 @@ fun LockedCredentials(
|
|||||||
authenticationEntryList: List<AuthenticationEntryInfo>,
|
authenticationEntryList: List<AuthenticationEntryInfo>,
|
||||||
onEntrySelected: (BaseEntry) -> Unit,
|
onEntrySelected: (BaseEntry) -> Unit,
|
||||||
) {
|
) {
|
||||||
TextSecondary(
|
CredentialListSectionHeader(
|
||||||
text = stringResource(R.string.get_dialog_heading_locked_password_managers),
|
text = stringResource(R.string.get_dialog_heading_locked_password_managers)
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
|
||||||
)
|
)
|
||||||
ContainerCard(
|
CredentialContainerCard {
|
||||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
@@ -471,17 +369,12 @@ fun PerUserNameCredentials(
|
|||||||
perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
|
perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
|
||||||
onEntrySelected: (BaseEntry) -> Unit,
|
onEntrySelected: (BaseEntry) -> Unit,
|
||||||
) {
|
) {
|
||||||
TextSecondary(
|
CredentialListSectionHeader(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
|
R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
|
||||||
),
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
|
||||||
)
|
)
|
||||||
ContainerCard(
|
)
|
||||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
CredentialContainerCard {
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
@@ -500,32 +393,13 @@ fun CredentialEntryRow(
|
|||||||
) {
|
) {
|
||||||
Entry(
|
Entry(
|
||||||
onClick = { onEntrySelected(credentialEntryInfo) },
|
onClick = { onEntrySelected(credentialEntryInfo) },
|
||||||
icon = {
|
iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
|
||||||
if (credentialEntryInfo.icon != null) {
|
// Fall back to iconPainter if iconImageBitmap isn't available
|
||||||
Image(
|
iconPainter =
|
||||||
modifier = Modifier.padding(start = 10.dp).size(32.dp),
|
if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in)
|
||||||
bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
|
else null,
|
||||||
contentDescription = null,
|
entryHeadlineText = credentialEntryInfo.userName,
|
||||||
)
|
entrySecondLineText = if (
|
||||||
} 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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) {
|
credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
|
||||||
"••••••••••••"
|
"••••••••••••"
|
||||||
} else {
|
} else {
|
||||||
@@ -538,15 +412,9 @@ fun CredentialEntryRow(
|
|||||||
) +
|
) +
|
||||||
credentialEntryInfo.displayName
|
credentialEntryInfo.displayName
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(bottom = 16.dp, start = 5.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AuthenticationEntryRow(
|
fun AuthenticationEntryRow(
|
||||||
authenticationEntryInfo: AuthenticationEntryInfo,
|
authenticationEntryInfo: AuthenticationEntryInfo,
|
||||||
@@ -554,116 +422,52 @@ fun AuthenticationEntryRow(
|
|||||||
) {
|
) {
|
||||||
Entry(
|
Entry(
|
||||||
onClick = { onEntrySelected(authenticationEntryInfo) },
|
onClick = { onEntrySelected(authenticationEntryInfo) },
|
||||||
icon = {
|
iconImageBitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
|
||||||
Image(
|
entryHeadlineText = authenticationEntryInfo.title,
|
||||||
modifier = Modifier.padding(start = 10.dp).size(32.dp),
|
entrySecondLineText = stringResource(
|
||||||
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)
|
if (authenticationEntryInfo.isUnlockedAndEmpty)
|
||||||
R.string.locked_credential_entry_label_subtext_no_sign_in
|
R.string.locked_credential_entry_label_subtext_no_sign_in
|
||||||
else R.string.locked_credential_entry_label_subtext_tap_to_unlock
|
else R.string.locked_credential_entry_label_subtext_tap_to_unlock
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
isLockedAuthEntry = !authenticationEntryInfo.isUnlockedAndEmpty,
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (!authenticationEntryInfo.isUnlockedAndEmpty) {
|
|
||||||
Icon(
|
|
||||||
Icons.Outlined.Lock,
|
|
||||||
null,
|
|
||||||
Modifier.align(alignment = Alignment.CenterVertically).padding(end = 10.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ActionEntryRow(
|
fun ActionEntryRow(
|
||||||
actionEntryInfo: ActionEntryInfo,
|
actionEntryInfo: ActionEntryInfo,
|
||||||
onEntrySelected: (BaseEntry) -> Unit,
|
onEntrySelected: (BaseEntry) -> Unit,
|
||||||
) {
|
) {
|
||||||
TransparentBackgroundEntry(
|
ActionEntry(
|
||||||
icon = {
|
iconImageBitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
|
||||||
Image(
|
entryHeadlineText = actionEntryInfo.title,
|
||||||
modifier = Modifier.padding(start = 10.dp).size(24.dp),
|
entrySecondLineText = actionEntryInfo.subTitle,
|
||||||
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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick = { onEntrySelected(actionEntryInfo) },
|
onClick = { onEntrySelected(actionEntryInfo) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RemoteCredentialSnackBarScreen(
|
fun RemoteCredentialSnackBarScreen(
|
||||||
onClick: (Boolean) -> Unit,
|
onClick: (Boolean) -> Unit,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
) {
|
) {
|
||||||
// TODO: Change the height, width and position according to the design
|
|
||||||
Snackbar(
|
Snackbar(
|
||||||
modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp),
|
|
||||||
shape = EntryShape.FullMediumRoundedCorner,
|
|
||||||
containerColor = LocalAndroidColorScheme.current.colorBackground,
|
|
||||||
contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
|
|
||||||
action = {
|
action = {
|
||||||
TextButton(
|
TextButton(
|
||||||
|
modifier = Modifier.padding(top = 12.dp, bottom = 12.dp, start = 16.dp),
|
||||||
onClick = { onClick(true) },
|
onClick = { onClick(true) },
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.snackbar_action))
|
SnackbarActionText(
|
||||||
}
|
text = stringResource(R.string.snackbar_action),
|
||||||
},
|
Modifier.padding(vertical = 6.dp)
|
||||||
dismissAction = {
|
|
||||||
IconButton(onClick = onCancel) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Close,
|
|
||||||
contentDescription = stringResource(
|
|
||||||
R.string.accessibility_close_button
|
|
||||||
),
|
|
||||||
tint = LocalAndroidColorScheme.current.colorAccentTertiary
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
onDismiss = onCancel,
|
||||||
Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
|
contentText = stringResource(R.string.get_dialog_use_saved_passkey_for),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EmptyAuthEntrySnackBarScreen(
|
fun EmptyAuthEntrySnackBarScreen(
|
||||||
authenticationEntryList: List<AuthenticationEntryInfo>,
|
authenticationEntryList: List<AuthenticationEntryInfo>,
|
||||||
@@ -676,22 +480,8 @@ fun EmptyAuthEntrySnackBarScreen(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Change the height, width and position according to the design
|
|
||||||
Snackbar(
|
Snackbar(
|
||||||
modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp),
|
onDismiss = onCancel,
|
||||||
shape = EntryShape.FullMediumRoundedCorner,
|
contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName),
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -96,6 +96,7 @@ class AuthenticationEntryInfo(
|
|||||||
pendingIntent: PendingIntent?,
|
pendingIntent: PendingIntent?,
|
||||||
fillInIntent: Intent?,
|
fillInIntent: Intent?,
|
||||||
val title: String,
|
val title: String,
|
||||||
|
val providerDisplayName: String,
|
||||||
val icon: Drawable,
|
val icon: Drawable,
|
||||||
// The entry had been unlocked and turned out to be empty. Used to determine whether to
|
// 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.
|
// show "Tap to unlock" or "No sign-in info" for this entry.
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ import androidx.compose.runtime.staticCompositionLocalOf
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.android.internal.R
|
import com.android.internal.R
|
||||||
|
|
||||||
|
/** File copied from PlatformComposeCore. */
|
||||||
|
|
||||||
/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
|
/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
|
||||||
val LocalAndroidColorScheme =
|
val LocalAndroidColorScheme =
|
||||||
staticCompositionLocalOf<AndroidColorScheme> {
|
staticCompositionLocalOf<AndroidColorScheme> {
|
||||||
throw IllegalStateException(
|
throw IllegalStateException(
|
||||||
"No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
|
"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.
|
* most of the colors in this class will be removed in favor of their M3 counterpart.
|
||||||
*/
|
*/
|
||||||
class AndroidColorScheme internal constructor(context: Context) {
|
class AndroidColorScheme internal constructor(context: Context) {
|
||||||
|
|
||||||
val colorPrimary = getColor(context, R.attr.colorPrimary)
|
val colorPrimary = getColor(context, R.attr.colorPrimary)
|
||||||
val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
|
val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
|
||||||
val colorAccent = getColor(context, R.attr.colorAccent)
|
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 colorForeground = getColor(context, R.attr.colorForeground)
|
||||||
val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
|
val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
|
||||||
|
|
||||||
private fun getColor(context: Context, attr: Int): Color {
|
companion object {
|
||||||
|
fun getColor(context: Context, attr: Int): Color {
|
||||||
val ta = context.obtainStyledAttributes(intArrayOf(attr))
|
val ta = context.obtainStyledAttributes(intArrayOf(attr))
|
||||||
@ColorInt val color = ta.getColor(0, 0)
|
@ColorInt val color = ta.getColor(0, 0)
|
||||||
ta.recycle()
|
ta.recycle()
|
||||||
return Color(color)
|
return Color(color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
val Grey100 = Color(0xFFF1F3F4)
|
/** Read the [Color] from the given [attribute]. */
|
||||||
val Purple200 = Color(0xFFBB86FC)
|
@Composable
|
||||||
val Purple500 = Color(0xFF6200EE)
|
@ReadOnlyComposable
|
||||||
val Purple700 = Color(0xFF3700B3)
|
fun colorAttr(@AttrRes attribute: Int): Color {
|
||||||
val Teal200 = Color(0xFF03DAC5)
|
return AndroidColorScheme.getColor(LocalContext.current, attribute)
|
||||||
val lightColorAccentSecondary = Color(0xFFC2E7FF)
|
}
|
||||||
val lightBackgroundColor = Color(0xFFF0F0F0)
|
|
||||||
val lightSurface1 = Color(0xFF6991D6)
|
|
||||||
val textColorSecondary = Color(0xFF40484B)
|
|
||||||
val textColorPrimary = Color(0xFF191C1D)
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
)
|
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user