diff options
26 files changed, 1085 insertions, 10 deletions
diff --git a/packages/CredentialManager/horologist/Android.bp b/packages/CredentialManager/horologist/Android.bp index bb324bb8350d..bb255bdb1306 100644 --- a/packages/CredentialManager/horologist/Android.bp +++ b/packages/CredentialManager/horologist/Android.bp @@ -16,6 +16,7 @@ android_library { "androidx.compose.foundation_foundation", "androidx.compose.runtime_runtime", "androidx.compose.ui_ui", + "androidx.compose.ui_ui-tooling", "androidx.navigation_navigation-compose", "androidx.lifecycle_lifecycle-extensions", "androidx.lifecycle_lifecycle-runtime-ktx", diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/BelowTimeTextPreview.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/BelowTimeTextPreview.kt new file mode 100644 index 000000000000..e6025fc0911f --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/BelowTimeTextPreview.kt @@ -0,0 +1,26 @@ +/* + * Copyright 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 + * + * https://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.google.android.horologist.compose.layout + +import androidx.compose.runtime.Composable +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +@OptIn(ExperimentalHorologistApi::class) +@Composable +public fun belowTimeTextPreview(): ScalingLazyColumnState { + return ScalingLazyColumnDefaults.belowTimeText().create() +} diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Button.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Button.kt new file mode 100644 index 000000000000..57e0c106b5f0 --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Button.kt @@ -0,0 +1,143 @@ +/* + * Copyright 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 + * + * https://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.google.android.horologist.compose.material + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.Dp +import androidx.wear.compose.material.Button +import androidx.wear.compose.material.ButtonColors +import androidx.wear.compose.material.ButtonDefaults +import androidx.wear.compose.material.ButtonDefaults.DefaultButtonSize +import androidx.wear.compose.material.ButtonDefaults.DefaultIconSize +import androidx.wear.compose.material.ButtonDefaults.LargeButtonSize +import androidx.wear.compose.material.ButtonDefaults.LargeIconSize +import androidx.wear.compose.material.ButtonDefaults.SmallButtonSize +import androidx.wear.compose.material.ButtonDefaults.SmallIconSize +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +/** + * This component is an alternative to [Button], providing the following: + * - a convenient way of providing an icon and choosing its size from a range of sizes recommended + * by the Wear guidelines; + */ +@ExperimentalHorologistApi +@Composable +public fun Button( + imageVector: ImageVector, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + colors: ButtonColors = ButtonDefaults.primaryButtonColors(), + buttonSize: ButtonSize = ButtonSize.Default, + iconRtlMode: IconRtlMode = IconRtlMode.Default, + enabled: Boolean = true, +) { + Button( + icon = imageVector, + contentDescription = contentDescription, + onClick = onClick, + modifier = modifier, + colors = colors, + buttonSize = buttonSize, + iconRtlMode = iconRtlMode, + enabled = enabled, + ) +} + +/** + * This component is an alternative to [Button], providing the following: + * - a convenient way of providing an icon and choosing its size from a range of sizes recommended + * by the Wear guidelines; + */ +@ExperimentalHorologistApi +@Composable +public fun Button( + @DrawableRes id: Int, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + colors: ButtonColors = ButtonDefaults.primaryButtonColors(), + buttonSize: ButtonSize = ButtonSize.Default, + iconRtlMode: IconRtlMode = IconRtlMode.Default, + enabled: Boolean = true, +) { + Button( + icon = id, + contentDescription = contentDescription, + onClick = onClick, + modifier = modifier, + colors = colors, + buttonSize = buttonSize, + iconRtlMode = iconRtlMode, + enabled = enabled, + ) +} + +@OptIn(ExperimentalHorologistApi::class) +@Composable +internal fun Button( + icon: Any, + contentDescription: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + colors: ButtonColors = ButtonDefaults.primaryButtonColors(), + buttonSize: ButtonSize = ButtonSize.Default, + iconRtlMode: IconRtlMode = IconRtlMode.Default, + enabled: Boolean = true, +) { + Button( + onClick = onClick, + modifier = modifier.size(buttonSize.tapTargetSize), + enabled = enabled, + colors = colors, + ) { + val iconModifier = Modifier + .size(buttonSize.iconSize) + .align(Alignment.Center) + + Icon( + icon = icon, + contentDescription = contentDescription, + modifier = iconModifier, + rtlMode = iconRtlMode, + ) + } +} + +@ExperimentalHorologistApi +public sealed class ButtonSize( + public val iconSize: Dp, + public val tapTargetSize: Dp, +) { + public object Default : + ButtonSize(iconSize = DefaultIconSize, tapTargetSize = DefaultButtonSize) + + public object Large : ButtonSize(iconSize = LargeIconSize, tapTargetSize = LargeButtonSize) + public object Small : ButtonSize(iconSize = SmallIconSize, tapTargetSize = SmallButtonSize) + + /** + * Custom sizes should follow the [accessibility principles and guidance for touch targets](https://developer.android.com/training/wearables/accessibility#set-minimum). + */ + public data class Custom(val customIconSize: Dp, val customTapTargetSize: Dp) : + ButtonSize(iconSize = customIconSize, tapTargetSize = customTapTargetSize) +} + diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Icon.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Icon.kt new file mode 100644 index 000000000000..74e54c0ba479 --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/Icon.kt @@ -0,0 +1,129 @@ +/* + * Copyright 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 + * + * https://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.google.android.horologist.compose.material + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.LayoutDirection +import androidx.wear.compose.material.Icon +import androidx.wear.compose.material.LocalContentAlpha +import androidx.wear.compose.material.LocalContentColor +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +/** + * This component is an alternative to [Icon], providing the following: + * - a convenient way of setting the icon to be mirrored in RTL mode; + */ +@ExperimentalHorologistApi +@Composable +public fun Icon( + imageVector: ImageVector, + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + rtlMode: IconRtlMode = IconRtlMode.Default, +) { + val shouldMirror = + rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl + Icon( + modifier = modifier.scale( + scaleX = if (shouldMirror) -1f else 1f, + scaleY = 1f, + ), + imageVector = imageVector, + contentDescription = contentDescription, + tint = tint, + ) +} + +/** + * This component is an alternative to [Icon], providing the following: + * - a convenient way of setting the icon to be mirrored in RTL mode; + */ +@ExperimentalHorologistApi +@Composable +public fun Icon( + @DrawableRes id: Int, + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + rtlMode: IconRtlMode = IconRtlMode.Default, +) { + val shouldMirror = + rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl + + Icon( + painter = painterResource(id = id), + contentDescription = contentDescription, + modifier = modifier.scale( + scaleX = if (shouldMirror) -1f else 1f, + scaleY = 1f, + ), + tint = tint, + ) +} + +@OptIn(ExperimentalHorologistApi::class) +@Composable +internal fun Icon( + icon: Any, + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), + rtlMode: IconRtlMode = IconRtlMode.Default, +) { + val shouldMirror = + rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl + + val iconModifier = modifier.scale( + scaleX = if (shouldMirror) -1f else 1f, + scaleY = 1f, + ) + when (icon) { + is ImageVector -> { + Icon( + imageVector = icon, + modifier = iconModifier, + contentDescription = contentDescription, + tint = tint, + ) + } + + is Int -> { + Icon( + painter = painterResource(id = icon), + contentDescription = contentDescription, + modifier = iconModifier, + tint = tint, + ) + } + + else -> throw IllegalArgumentException("Type not supported.") + } +} + +@ExperimentalHorologistApi +public enum class IconRtlMode { + Default, + Mirrored, +} diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/util/A11y.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/util/A11y.kt new file mode 100644 index 000000000000..39de2e142edc --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/material/util/A11y.kt @@ -0,0 +1,28 @@ +/* + * Copyright 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 + * + * https://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.google.android.horologist.compose.material.util + +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +/** + * Make explicit that a conscious decision was made to mark an element as decorative, so it does not + * have associated actions or state. + * + * https://developer.android.com/jetpack/compose/accessibility#describe-visual + */ +@ExperimentalHorologistApi +public val DECORATIVE_ELEMENT_CONTENT_DESCRIPTION: String? = null diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/tools/WearPreview.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/tools/WearPreview.kt new file mode 100644 index 000000000000..0bfceeea916c --- /dev/null +++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/tools/WearPreview.kt @@ -0,0 +1,25 @@ +/* + * Copyright 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 + * + * https://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.google.android.horologist.compose.tools + +import androidx.compose.ui.tooling.preview.Preview + +@Preview( + backgroundColor = 0xff000000, + showBackground = true, +) +public annotation class WearPreview diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp index ae4281e5561c..38d98a9d47f7 100644 --- a/packages/CredentialManager/shared/Android.bp +++ b/packages/CredentialManager/shared/Android.bp @@ -14,5 +14,6 @@ android_library { static_libs: [ "androidx.core_core-ktx", "androidx.credentials_credentials", + "guava", ], } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt index 6627af526dee..43d8fb3228d5 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/IntentParser.kt @@ -17,11 +17,15 @@ package com.android.credentialmanager.ui import android.content.Intent +import android.credentials.ui.GetCredentialProviderData import android.credentials.ui.RequestInfo import com.android.credentialmanager.ui.ktx.cancelUiRequest +import com.android.credentialmanager.ui.ktx.getCredentialProviderDataList import com.android.credentialmanager.ui.ktx.requestInfo import com.android.credentialmanager.ui.mapper.toCancel import com.android.credentialmanager.ui.model.Request +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap fun Intent.parse(): Request { cancelUiRequest?.let { @@ -32,9 +36,23 @@ fun Intent.parse(): Request { RequestInfo.TYPE_CREATE -> { Request.Create } + RequestInfo.TYPE_GET -> { - Request.Get + Request.Get( + providers = ImmutableMap.copyOf( + getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName } + ), + entries = ImmutableList.copyOf( + getCredentialProviderDataList.map { providerData -> + check(providerData is GetCredentialProviderData) { + "Invalid provider data type for GetCredentialRequest" + } + providerData + }.flatMap { it.credentialEntries } + ) + ) } + else -> { throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}") } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt new file mode 100644 index 000000000000..f01fdedc9137 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/factory/CredentialEntryFactory.kt @@ -0,0 +1,25 @@ +package com.android.credentialmanager.ui.factory + +import android.app.slice.Slice +import android.credentials.Credential +import androidx.credentials.PublicKeyCredential +import androidx.credentials.provider.CredentialEntry +import androidx.credentials.provider.CustomCredentialEntry +import androidx.credentials.provider.PasswordCredentialEntry +import androidx.credentials.provider.PublicKeyCredentialEntry + +fun fromSlice(slice: Slice): CredentialEntry? = + try { + when (slice.spec?.type) { + Credential.TYPE_PASSWORD_CREDENTIAL -> PasswordCredentialEntry.fromSlice(slice)!! + + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> + PublicKeyCredentialEntry.fromSlice(slice)!! + + else -> CustomCredentialEntry.fromSlice(slice)!! + } + } catch (e: Exception) { + // Try CustomCredentialEntry.fromSlice one last time in case the cause was a failed + // password / passkey parsing attempt. + CustomCredentialEntry.fromSlice(slice) + }
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt index a646851bf570..b9895a0ab52a 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/ktx/IntentKtx.kt @@ -18,6 +18,9 @@ package com.android.credentialmanager.ui.ktx import android.content.Intent import android.credentials.ui.CancelUiRequest +import android.credentials.ui.CreateCredentialProviderData +import android.credentials.ui.GetCredentialProviderData +import android.credentials.ui.ProviderData import android.credentials.ui.RequestInfo val Intent.cancelUiRequest: CancelUiRequest? @@ -30,4 +33,16 @@ val Intent.requestInfo: RequestInfo? get() = this.extras?.getParcelable( RequestInfo.EXTRA_REQUEST_INFO, RequestInfo::class.java - )
\ No newline at end of file + ) + +val Intent.getCredentialProviderDataList: List<ProviderData> + get() = this.extras?.getParcelableArrayList( + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, + GetCredentialProviderData::class.java + ) ?: emptyList() + +val Intent.createCredentialProviderDataList: List<ProviderData> + get() = this.extras?.getParcelableArrayList( + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, + CreateCredentialProviderData::class.java + ) ?: emptyList() diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt index 3d835bebc06b..cffa2b2586b1 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ui/model/Request.kt @@ -16,6 +16,11 @@ package com.android.credentialmanager.ui.model +import android.credentials.ui.Entry +import android.credentials.ui.ProviderData +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap + /** * Represents the request made by the CredentialManager API. */ @@ -25,7 +30,10 @@ sealed class Request { val appPackageName: String? ) : Request() - data object Get : Request() + data class Get( + val providers: ImmutableMap<String, ProviderData>, + val entries: ImmutableList<Entry>, + ) : Request() data object Create : Request() } diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp index 36340fac1760..c0dff168969d 100644 --- a/packages/CredentialManager/wear/Android.bp +++ b/packages/CredentialManager/wear/Android.bp @@ -32,6 +32,7 @@ android_app { "androidx.compose.material_material-icons-extended", "androidx.compose.runtime_runtime", "androidx.compose.ui_ui", + "androidx.compose.ui_ui-tooling", "androidx.core_core-ktx", "androidx.lifecycle_lifecycle-extensions", "androidx.lifecycle_lifecycle-livedata", diff --git a/packages/CredentialManager/wear/res/drawable/passkey_icon.xml b/packages/CredentialManager/wear/res/drawable/passkey_icon.xml new file mode 100644 index 000000000000..be366bf2a255 --- /dev/null +++ b/packages/CredentialManager/wear/res/drawable/passkey_icon.xml @@ -0,0 +1,21 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M23,10.5H17V13.5H23V10.5Z" + android:fillColor="#188038"/> + <path + android:pathData="M6.5,17.5C3.5,17.5 1,15 1,12C1,9 3.5,6.5 6.5,6.5C9.5,6.5 12,9 12,12C12,15 9.5,17.5 6.5,17.5ZM6.5,9.5C5.1,9.5 4,10.6 4,12C4,13.4 5.1,14.5 6.5,14.5C7.9,14.5 9,13.4 9,12C9,10.6 7.9,9.5 6.5,9.5Z" + android:fillColor="#4285F4"/> + <path + android:pathData="M21,13.5H19H17V16.5H19V15.5C19,14.9 19.4,14.5 20,14.5C20.6,14.5 21,14.9 21,15.5V16.5H23V13.5H21Z" + android:fillColor="#34A853"/> + <path + android:pathData="M11.8,10.5H8.5C8.8,10.9 9,11.4 9,12C9,12.6 8.8,13.1 8.5,13.5H11.8C11.9,13 12,12.5 12,12C12,11.5 11.9,11 11.8,10.5Z" + android:fillColor="#EA4335"/> + <path + android:pathData="M17,10.5H11.8C11.9,11 12,11.5 12,12C12,12.5 11.9,13 11.8,13.5H17V10.5Z" + android:fillColor="#FBBC04"/> +</vector> diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml index 10ea9186ca85..109644f46b10 100644 --- a/packages/CredentialManager/wear/res/values/strings.xml +++ b/packages/CredentialManager/wear/res/values/strings.xml @@ -18,4 +18,14 @@ <!-- The name of this application. Credential Manager is a service that centralizes and provides access to a user's credentials used to sign in to various apps. [CHAR LIMIT=80] --> <string name="app_name">Credential Manager</string> + <!-- Title of a screen prompting if the user would like to use their saved passkey. + [CHAR LIMIT=80] --> + <string name="use_passkey_title">Use passkey?</string> + <!-- Title of a screen prompting if the user would like to use their saved password. + [CHAR LIMIT=80] --> + <string name="use_password_title">Use password?</string> + <!-- Content description for the cancel button of a screen. [CHAR LIMIT=NONE] --> + <string name="dialog_cancel_button_cd">Cancel</string> + <!-- Content description for the OK button of a screen. [CHAR LIMIT=NONE] --> + <string name="dialog_ok_button_cd">OK</string> </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt index 2c0575547162..a93fa81bb85b 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt @@ -47,7 +47,7 @@ class CredentialSelectorActivity : ComponentActivity() { // to the user. } - CredentialSelectorUiState.Get -> { + is CredentialSelectorUiState.Get -> { // TODO: b/301206470 - Implement get flow setContent { MaterialTheme { diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt index e46fcae78f6a..c61bb2e0f949 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt @@ -23,6 +23,9 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.android.credentialmanager.ui.ktx.appLabel import com.android.credentialmanager.ui.ktx.requestInfo +import com.android.credentialmanager.ui.mapper.toGet +import com.android.credentialmanager.ui.model.PasskeyUiModel +import com.android.credentialmanager.ui.model.PasswordUiModel import com.android.credentialmanager.ui.model.Request import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -62,8 +65,8 @@ class CredentialSelectorViewModel( _uiState.value = CredentialSelectorUiState.Create } - Request.Get -> { - _uiState.value = CredentialSelectorUiState.Get + is Request.Get -> { + _uiState.value = request.toGet() } } } @@ -103,9 +106,15 @@ class CredentialSelectorViewModel( } sealed class CredentialSelectorUiState { - object Idle : CredentialSelectorUiState() - object Get : CredentialSelectorUiState() - object Create : CredentialSelectorUiState() + data object Idle : CredentialSelectorUiState() + sealed class Get : CredentialSelectorUiState() { + data class SingleProviderSinglePasskey(val passkeyUiModel: PasskeyUiModel) : Get() + data class SingleProviderSinglePassword(val passwordUiModel: PasswordUiModel) : Get() + + // TODO: b/301206470 add the remaining states + } + + data object Create : CredentialSelectorUiState() data class Cancel(val appName: String) : CredentialSelectorUiState() - object Finish : CredentialSelectorUiState() + data object Finish : CredentialSelectorUiState() } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt new file mode 100644 index 000000000000..c20ee0c22ad6 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt @@ -0,0 +1,63 @@ +/* + * Copyright 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 + * + * https://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.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +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.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun AccountRow( + name: String, + email: String, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = name, + color = Color(0xFFE6FF7B), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.title2 + ) + Text( + text = email, + modifier = Modifier.padding(top = 7.dp), + color = Color(0xFFCAC5BC), + overflow = TextOverflow.Ellipsis, + maxLines = 2, + style = MaterialTheme.typography.body1, + ) + } +} + +@WearPreview +@Composable +fun AccountRowPreview() { + AccountRow( + name = "Elisa Beckett", + email = "beckett_bakery@gmail.com", + ) +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt new file mode 100644 index 000000000000..5cb3c1590dfd --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/DialogButtonsRow.kt @@ -0,0 +1,71 @@ +/* + * Copyright 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 + * + * https://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.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.ButtonDefaults +import com.google.android.horologist.compose.material.Button +import com.google.android.horologist.compose.tools.WearPreview +import com.android.credentialmanager.R +import com.google.android.horologist.annotations.ExperimentalHorologistApi + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun DialogButtonsRow( + onCancelClick: () -> Unit, + onOKClick: () -> Unit, + modifier: Modifier = Modifier, + cancelButtonIcon: ImageVector = Icons.Default.Close, + okButtonIcon: ImageVector = Icons.Default.Check, + cancelButtonContentDescription: String = stringResource(R.string.dialog_cancel_button_cd), + okButtonContentDescription: String = stringResource(R.string.dialog_ok_button_cd), +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.Center, + ) { + Button( + imageVector = cancelButtonIcon, + contentDescription = cancelButtonContentDescription, + onClick = onCancelClick, + colors = ButtonDefaults.secondaryButtonColors(), + ) + Button( + imageVector = okButtonIcon, + contentDescription = okButtonContentDescription, + onClick = onOKClick, + modifier = Modifier.padding(start = 20.dp) + ) + } +} + +@WearPreview +@Composable +fun DialogButtonsRowPreview() { + DialogButtonsRow(onCancelClick = {}, onOKClick = {}) +} + diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/PasswordRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/PasswordRow.kt new file mode 100644 index 000000000000..97900b723bc3 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/PasswordRow.kt @@ -0,0 +1,62 @@ +/* + * Copyright 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 + * + * https://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.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +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.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun PasswordRow( + email: String, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = email, + color = Color(0xFFE6FF7B), + overflow = TextOverflow.Ellipsis, + maxLines = 2, + style = MaterialTheme.typography.title2 + ) + Text( + text = "••••••••••••••", + modifier = Modifier.padding(top = 7.dp), + color = Color(0xFFCAC5BC), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.body1, + ) + } +} + +@WearPreview +@Composable +fun PasswordRowPreview() { + PasswordRow( + email = "beckett_bakery@gmail.com", + ) +} + diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt new file mode 100644 index 000000000000..956c56b2c7b1 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt @@ -0,0 +1,83 @@ +/* + * Copyright 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 + * + * https://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.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.Text +import com.android.credentialmanager.R +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.material.Icon +import com.google.android.horologist.compose.material.util.DECORATIVE_ELEMENT_CONTENT_DESCRIPTION +import com.google.android.horologist.compose.tools.WearPreview + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun SignInHeader( + @DrawableRes icon: Int, + title: String, + modifier: Modifier = Modifier, +) { + SignInHeader( + iconContent = { + Icon( + id = icon, + contentDescription = DECORATIVE_ELEMENT_CONTENT_DESCRIPTION + ) + }, + title = title, + modifier = modifier, + ) +} + +@Composable +fun SignInHeader( + iconContent: @Composable ColumnScope.() -> Unit, + title: String, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + iconContent() + Text( + text = title, + modifier = Modifier + .padding(top = 6.dp) + .padding(horizontal = 10.dp), + style = MaterialTheme.typography.title3 + ) + } +} + +@WearPreview +@Composable +fun SignInHeaderPreview() { + SignInHeader( + icon = R.drawable.passkey_icon, + title = stringResource(R.string.use_passkey_title) + ) +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt new file mode 100644 index 000000000000..1fe1e5596805 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt @@ -0,0 +1,51 @@ +package com.android.credentialmanager.ui.mapper + +import androidx.credentials.provider.CustomCredentialEntry +import androidx.credentials.provider.PasswordCredentialEntry +import androidx.credentials.provider.PublicKeyCredentialEntry +import com.android.credentialmanager.ui.CredentialSelectorUiState +import com.android.credentialmanager.ui.factory.fromSlice +import com.android.credentialmanager.ui.model.PasswordUiModel +import com.android.credentialmanager.ui.model.Request + +fun Request.Get.toGet(): CredentialSelectorUiState.Get { + if (this.providers.isEmpty()) { + throw IllegalStateException("Invalid GetCredential request with empty list of providers.") + } + + if (this.entries.isEmpty()) { + throw IllegalStateException("Invalid GetCredential request with empty list of entries.") + } + + if (this.providers.size == 1) { + if (this.entries.size == 1) { + val slice = this.entries.first().slice + when (val credentialEntry = fromSlice(slice)) { + is PasswordCredentialEntry -> { + return CredentialSelectorUiState.Get.SingleProviderSinglePassword( + PasswordUiModel(credentialEntry.displayName.toString()) + ) + } + + is PublicKeyCredentialEntry -> { + TODO("b/301206470 - to be implemented") + } + + is CustomCredentialEntry -> { + TODO("b/301206470 - to be implemented") + } + + else -> { + throw IllegalStateException( + "Encountered unrecognized credential entry (${slice.spec?.type}) for " + + "GetCredential request with single account" + ) + } + } + } else { + TODO("b/301206470 - to be implemented") + } + } else { + TODO("b/301206470 - to be implemented") + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt new file mode 100644 index 000000000000..a368de27867a --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt @@ -0,0 +1,22 @@ +/* + * 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.0N + * + * 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.model + +data class PasskeyUiModel( + val name: String, + val email: String, +) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt new file mode 100644 index 000000000000..514dca8244f1 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt @@ -0,0 +1,19 @@ +/* + * 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.0N + * + * 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.model + +data class PasswordUiModel(val name: String) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt new file mode 100644 index 000000000000..f344ad0bd22d --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt @@ -0,0 +1,83 @@ +/* + * Copyright 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 + * + * https://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(ExperimentalHorologistApi::class) + +package com.android.credentialmanager.ui.screens + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.wear.compose.foundation.lazy.ScalingLazyListScope +import com.android.credentialmanager.R +import com.android.credentialmanager.ui.components.AccountRow +import com.android.credentialmanager.ui.components.DialogButtonsRow +import com.android.credentialmanager.ui.components.SignInHeader +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumn +import com.google.android.horologist.compose.layout.ScalingLazyColumnState +import com.google.android.horologist.compose.layout.belowTimeTextPreview +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun SingleAccountScreen( + headerContent: @Composable () -> Unit, + accountContent: @Composable () -> Unit, + columnState: ScalingLazyColumnState, + modifier: Modifier = Modifier, + content: ScalingLazyListScope.() -> Unit, +) { + ScalingLazyColumn( + columnState = columnState, + modifier = modifier.fillMaxSize(), + ) { + item { headerContent() } + item { accountContent() } + content() + } +} + +@WearPreview +@Composable +fun SingleAccountScreenPreview() { + SingleAccountScreen( + headerContent = { + SignInHeader( + icon = R.drawable.passkey_icon, + title = stringResource(R.string.use_passkey_title), + ) + }, + accountContent = { + AccountRow( + name = "Elisa Beckett", + email = "beckett_bakery@gmail.com", + modifier = Modifier.padding(top = 10.dp) + ) + }, + columnState = belowTimeTextPreview(), + ) { + item { + DialogButtonsRow( + onCancelClick = {}, + onOKClick = {}, + modifier = Modifier.padding(top = 10.dp) + ) + } + } +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt new file mode 100644 index 000000000000..c8f871e46c83 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt @@ -0,0 +1,82 @@ +/* + * Copyright 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 + * + * https://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(ExperimentalHorologistApi::class) + +package com.android.credentialmanager.ui.screens + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.android.credentialmanager.R +import com.android.credentialmanager.ui.components.AccountRow +import com.android.credentialmanager.ui.components.DialogButtonsRow +import com.android.credentialmanager.ui.components.SignInHeader +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumnState +import com.google.android.horologist.compose.layout.belowTimeTextPreview +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun SinglePasskeyScreen( + name: String, + email: String, + onCancelClick: () -> Unit, + onOKClick: () -> Unit, + columnState: ScalingLazyColumnState, + modifier: Modifier = Modifier, +) { + SingleAccountScreen( + headerContent = { + SignInHeader( + icon = R.drawable.passkey_icon, + title = stringResource(R.string.use_passkey_title), + ) + }, + accountContent = { + AccountRow( + name = name, + email = email, + modifier = Modifier.padding(top = 10.dp), + ) + }, + columnState = columnState, + modifier = modifier.padding(horizontal = 10.dp) + ) { + item { + DialogButtonsRow( + onCancelClick = onCancelClick, + onOKClick = onOKClick, + modifier = Modifier.padding(top = 10.dp) + ) + } + } +} + +@WearPreview +@Composable +fun SinglePasskeyScreenPreview() { + SinglePasskeyScreen( + name = "Elisa Beckett", + email = "beckett_bakery@gmail.com", + onCancelClick = {}, + onOKClick = {}, + columnState = belowTimeTextPreview(), + ) +} + diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt new file mode 100644 index 000000000000..d863d3c68ceb --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt @@ -0,0 +1,79 @@ +/* + * Copyright 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 + * + * https://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(ExperimentalHorologistApi::class) + +package com.android.credentialmanager.ui.screens + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.android.credentialmanager.R +import com.android.credentialmanager.ui.components.DialogButtonsRow +import com.android.credentialmanager.ui.components.PasswordRow +import com.android.credentialmanager.ui.components.SignInHeader +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.ScalingLazyColumnState +import com.google.android.horologist.compose.layout.belowTimeTextPreview +import com.google.android.horologist.compose.tools.WearPreview + +@Composable +fun SinglePasswordScreen( + email: String, + onCancelClick: () -> Unit, + onOKClick: () -> Unit, + columnState: ScalingLazyColumnState, + modifier: Modifier = Modifier, +) { + SingleAccountScreen( + headerContent = { + SignInHeader( + icon = R.drawable.passkey_icon, + title = stringResource(R.string.use_password_title), + ) + }, + accountContent = { + PasswordRow( + email = email, + modifier = Modifier.padding(top = 10.dp), + ) + }, + columnState = columnState, + modifier = modifier.padding(horizontal = 10.dp) + ) { + item { + DialogButtonsRow( + onCancelClick = onCancelClick, + onOKClick = onOKClick, + modifier = Modifier.padding(top = 10.dp) + ) + } + } +} + +@WearPreview +@Composable +fun SinglePasswordScreenPreview() { + SinglePasswordScreen( + email = "beckett_bakery@gmail.com", + onCancelClick = {}, + onOKClick = {}, + columnState = belowTimeTextPreview(), + ) +} + |