From 75dc9f71f07dded94f7e31b7ab90312577ca5916 Mon Sep 17 00:00:00 2001 From: Qinmei Du Date: Mon, 17 Oct 2022 21:54:28 +0000 Subject: [PATCH] Update more options screen screencast: https://screencast.googleplex.com/cast/NjI4NDMyNDAyNzQ5ODQ5Nnw5YzEzNzEyZC0xMg Test: deployed locally Bug: 253157211 Change-Id: I1b08ae3431eb1cab2f5a755bd218f02ab0b80a93 --- .../credentialmanager/CredentialEntryUi.kt | 8 +- .../CredentialManagerRepo.kt | 27 +++-- .../credentialmanager/DataConverter.kt | 2 + .../android/credentialmanager/SaveEntryUi.kt | 8 +- .../createflow/CreateModel.kt | 2 + .../createflow/CreatePasskeyComponents.kt | 100 ++++++++++++++---- .../createflow/CreatePasskeyViewModel.kt | 9 +- .../credentialmanager/getflow/GetModel.kt | 1 + 8 files changed, 121 insertions(+), 36 deletions(-) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt index 96cc02f943614..ee4f4ca9c832f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialEntryUi.kt @@ -29,6 +29,7 @@ class CredentialEntryUi( val userName: CharSequence, val displayName: CharSequence?, val icon: Icon?, + val usageData: CharSequence?, // TODO: add last used. ) { companion object { @@ -38,18 +39,21 @@ class CredentialEntryUi( var title: String? = null var subTitle: String? = null var icon: Icon? = null + var usageData: String? = null items.forEach { if (it.hasHint(Entry.HINT_ICON)) { icon = it.icon - } else if (it.hasHint(Entry.HINT_SUBTITLE)) { + } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) { subTitle = it.text.toString() } else if (it.hasHint(Entry.HINT_TITLE)) { title = it.text.toString() + } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) { + usageData = it.text.toString() } } // TODO: fail NPE more elegantly. - return CredentialEntryUi(title!!, subTitle, icon) + return CredentialEntryUi(title!!, subTitle, icon, usageData) } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 8db547a49c880..c575fbc6b7f89 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -92,30 +92,38 @@ class CredentialManagerRepo( ProviderData( "com.google", listOf( - newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett"), - newEntry(2, "elisa.work@google.com", "Elisa Backett Work"), + newEntry(1, "elisa.beckett@gmail.com", "Elisa Backett", + "20 passwords and 7 passkeys saved"), + newEntry(2, "elisa.work@google.com", "Elisa Backett Work", + "20 passwords and 7 passkeys saved"), ), listOf( - newEntry(3, "Go to Settings", ""), - newEntry(4, "Switch Account", ""), + newEntry(3, "Go to Settings", "", + "20 passwords and 7 passkeys saved"), + newEntry(4, "Switch Account", "", + "20 passwords and 7 passkeys saved"), ), null ), ProviderData( "com.dashlane", listOf( - newEntry(5, "elisa.beckett@dashlane.com", "Elisa Backett"), - newEntry(6, "elisa.work@dashlane.com", "Elisa Backett Work"), + newEntry(5, "elisa.beckett@dashlane.com", "Elisa Backett", + "20 passwords and 7 passkeys saved"), + newEntry(6, "elisa.work@dashlane.com", "Elisa Backett Work", + "20 passwords and 7 passkeys saved"), ), listOf( - newEntry(7, "Manage Accounts", "Manage your accounts in the dashlane app"), + newEntry(7, "Manage Accounts", "Manage your accounts in the dashlane app", + "20 passwords and 7 passkeys saved"), ), null ), ProviderData( "com.lastpass", listOf( - newEntry(8, "elisa.beckett@lastpass.com", "Elisa Backett"), + newEntry(8, "elisa.beckett@lastpass.com", "Elisa Backett", + "20 passwords and 7 passkeys saved"), ), listOf(), null @@ -124,7 +132,7 @@ class CredentialManagerRepo( ) } - private fun newEntry(id: Int, title: String, subtitle: String): Entry { + private fun newEntry(id: Int, title: String, subtitle: String, usageData: String): Entry { val slice = Slice.Builder( Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1) ) @@ -134,6 +142,7 @@ class CredentialManagerRepo( Icon.createWithResource(context, R.drawable.ic_passkey), null, listOf(Entry.HINT_ICON)) + .addText(usageData, Slice.SUBTYPE_MESSAGE, listOf(Entry.HINT_SUBTITLE)) .build() return Entry( id, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 7159ab97202b1..af27ce504641c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -60,6 +60,7 @@ class GetFlowUtils { title = credentialEntryUi.userName.toString(), subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name", id = it.entryId, + usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData", ) } } @@ -99,6 +100,7 @@ class CreateFlowUtils { title = saveEntryUi.title.toString(), subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle", id = it.entryId, + usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData", ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt index 2b63e1d1cd69c..cd52197f92574 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt @@ -29,6 +29,7 @@ class SaveEntryUi( val title: CharSequence, val subTitle: CharSequence?, val icon: Icon?, + val usageData: CharSequence?, // TODO: add ) { companion object { @@ -38,18 +39,21 @@ class SaveEntryUi( var title: String? = null var subTitle: String? = null var icon: Icon? = null + var usageData: String? = null items.forEach { if (it.hasHint(Entry.HINT_ICON)) { icon = it.icon - } else if (it.hasHint(Entry.HINT_SUBTITLE)) { + } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) { subTitle = it.text.toString() } else if (it.hasHint(Entry.HINT_TITLE)) { title = it.text.toString() + } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) { + usageData = it.text.toString() } } // TODO: fail NPE more elegantly. - return SaveEntryUi(title!!, subTitle, icon) + return SaveEntryUi(title!!, subTitle, icon, usageData) } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 12f18460ce241..19820d6cd98ca 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -31,6 +31,7 @@ data class CreateOptionInfo( val title: String, val subtitle: String, val id: Int, + val usageData: String ) /** The name of the current screen. */ @@ -39,4 +40,5 @@ enum class CreateScreenState { PROVIDER_SELECTION, CREATION_OPTION_SELECTION, MORE_OPTIONS_SELECTION, + MORE_OPTIONS_ROW_INTRO, } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt index b61f4652ad22a..6489d73285668 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import androidx.lifecycle.viewmodel.compose.viewModel @@ -75,13 +76,16 @@ fun CreatePasskeyScreen( onOptionSelected = {viewModel.onCreateOptionSelected(it)}, onCancel = cancelActivity, multiProvider = uiState.providers.size > 1, - onMoreOptionSelected = {viewModel.onMoreOptionSelected(it)} + onMoreOptionsSelected = {viewModel.onMoreOptionsSelected(it)} ) - CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionSelectionCard( + CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard( providerInfo = uiState.selectedProvider!!, - onCancel = cancelActivity, - onBackButtonSelected = {viewModel.onBackButtonSelected(it)} + providerList = uiState.providers, + onBackButtonSelected = {viewModel.onBackButtonSelected(it)}, + onOptionSelected = {viewModel.onMoreOptionsRowSelected(it)} ) + CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard( + ) } }, scrimColor = Color.Transparent, @@ -211,11 +215,13 @@ fun ProviderSelectionCard( } } +@ExperimentalMaterialApi @Composable -fun MoreOptionSelectionCard( +fun MoreOptionsSelectionCard( providerInfo: ProviderInfo, - onCancel: () -> Unit, - onBackButtonSelected: (String) -> Unit + providerList: List, + onBackButtonSelected: (String) -> Unit, + onOptionSelected: (String) -> Unit ) { Card( backgroundColor = lightBackgroundColor, @@ -242,7 +248,8 @@ fun MoreOptionSelectionCard( Text( text = stringResource(R.string.create_passkey_at), style = Typography.body1, - modifier = Modifier.padding(horizontal = 28.dp) + modifier = Modifier.padding(horizontal = 28.dp), + textAlign = TextAlign.Center ) Card( shape = Shapes.medium, @@ -253,27 +260,37 @@ fun MoreOptionSelectionCard( LazyColumn( verticalArrangement = Arrangement.spacedBy(2.dp) ) { + // TODO: change the order according to usage frequency + providerList.forEach { providerInfo -> + providerInfo.createOptions.forEach { createOptionInfo -> + item { + MoreOptionsInfoRow(providerInfo = providerInfo, + createOptionInfo = createOptionInfo, + onOptionSelected = onOptionSelected) + } + } + } } } - Divider( - thickness = 24.dp, - color = Color.Transparent - ) - Row( - horizontalArrangement = Arrangement.Start, - modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) - ) { - CancelButton(stringResource(R.string.string_cancel), onCancel) - } Divider( thickness = 18.dp, color = Color.Transparent, - modifier = Modifier.padding(bottom = 16.dp) + modifier = Modifier.padding(bottom = 40.dp) ) } } } +@ExperimentalMaterialApi +@Composable +fun MoreOptionsRowIntroCard( +) { + Card( + backgroundColor = lightBackgroundColor, + ) { + } +} + @ExperimentalMaterialApi @Composable fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) { @@ -348,7 +365,7 @@ fun CreationSelectionCard( onOptionSelected: (Int) -> Unit, onCancel: () -> Unit, multiProvider: Boolean, - onMoreOptionSelected: (String) -> Unit, + onMoreOptionsSelected: (String) -> Unit, ) { Card( backgroundColor = lightBackgroundColor, @@ -390,7 +407,7 @@ fun CreationSelectionCard( } if (multiProvider) { item { - MoreOptionRow(onSelect = { onMoreOptionSelected(providerInfo.name) }) + MoreOptionsRow(onSelect = { onMoreOptionsSelected(providerInfo.name) }) } } } @@ -450,7 +467,46 @@ fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (Int) @ExperimentalMaterialApi @Composable -fun MoreOptionRow(onSelect: () -> Unit) { +fun MoreOptionsInfoRow( + providerInfo: ProviderInfo, + createOptionInfo: CreateOptionInfo, + onOptionSelected: (String) -> Unit +) { + Chip( + modifier = Modifier.fillMaxWidth(), + onClick = { onOptionSelected(providerInfo.name) }, + leadingIcon = { + Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp), + bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(), + // painter = painterResource(R.drawable.ic_passkey), + // TODO: add description. + contentDescription = "") + }, + colors = ChipDefaults.chipColors( + backgroundColor = Grey100, + leadingIconContentColor = Grey100 + ), + shape = Shapes.large + ) { + Column() { + Text( + text = if (providerInfo.createOptions.size > 1) + {providerInfo.name + " for " + createOptionInfo.title} else { providerInfo.name}, + style = Typography.h6, + modifier = Modifier.padding(top = 16.dp) + ) + Text( + text = createOptionInfo.usageData, + style = Typography.body2, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + +@ExperimentalMaterialApi +@Composable +fun MoreOptionsRow(onSelect: () -> Unit) { Chip( modifier = Modifier.fillMaxWidth().height(52.dp), onClick = onSelect, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt index 5b70f9d91c541..85fe31d312abc 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt @@ -68,7 +68,7 @@ class CreatePasskeyViewModel( } } - fun onMoreOptionSelected(providerName: String) { + fun onMoreOptionsSelected(providerName: String) { uiState = uiState.copy( currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION, selectedProvider = getProviderInfoByName(providerName) @@ -81,4 +81,11 @@ class CreatePasskeyViewModel( selectedProvider = getProviderInfoByName(providerName) ) } + + fun onMoreOptionsRowSelected(providerName: String) { + uiState = uiState.copy( + currentScreenState = CreateScreenState.MORE_OPTIONS_ROW_INTRO, + selectedProvider = getProviderInfoByName(providerName) + ) + } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index acea8c9d6536f..a39b211b29cab 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -31,6 +31,7 @@ data class CredentialOptionInfo( val title: String, val subtitle: String, val id: Int, + val usageData: String ) /** The name of the current screen. */