Add single provider and single account screens
Bug: 301206470
Test: N/A - feature not fully implemented yet
Change-Id: I25f54e2bcb8228b46a8ca7a89f63753c639a26ef
diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp
index 36340fa..c0dff16 100644
--- a/packages/CredentialManager/wear/Android.bp
+++ b/packages/CredentialManager/wear/Android.bp
@@ -32,6 +32,7 @@
"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 0000000..be366bf
--- /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 10ea918..109644f 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 2c05755..a93fa81 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 @@
// 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 e46fcae..c61bb2e 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.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 @@
_uiState.value = CredentialSelectorUiState.Create
}
- Request.Get -> {
- _uiState.value = CredentialSelectorUiState.Get
+ is Request.Get -> {
+ _uiState.value = request.toGet()
}
}
}
@@ -103,9 +106,15 @@
}
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 0000000..c20ee0c
--- /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 0000000..5cb3c15
--- /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 0000000..97900b7
--- /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 0000000..956c56b
--- /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 0000000..1fe1e55
--- /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 0000000..a368de2
--- /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 0000000..514dca8
--- /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 0000000..f344ad0
--- /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 0000000..c8f871e
--- /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 0000000..d863d3c
--- /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(),
+ )
+}
+