Update more options screen

screencast: https://screencast.googleplex.com/cast/NjI4NDMyNDAyNzQ5ODQ5Nnw5YzEzNzEyZC0xMg

Test: deployed locally

Bug: 253157211
Change-Id: I1b08ae3431eb1cab2f5a755bd218f02ab0b80a93
This commit is contained in:
Qinmei Du
2022-10-17 21:54:28 +00:00
parent 59522ce416
commit 75dc9f71f0
8 changed files with 121 additions and 36 deletions

View File

@@ -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)
}
}
}

View File

@@ -92,30 +92,38 @@ class CredentialManagerRepo(
ProviderData(
"com.google",
listOf<Entry>(
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<Entry>(
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<Entry>(
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<Entry>(
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<Entry>(
newEntry(8, "elisa.beckett@lastpass.com", "Elisa Backett"),
newEntry(8, "elisa.beckett@lastpass.com", "Elisa Backett",
"20 passwords and 7 passkeys saved"),
),
listOf<Entry>(),
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,

View File

@@ -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",
)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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,
}

View File

@@ -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<ProviderInfo>,
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,

View File

@@ -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)
)
}
}

View File

@@ -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. */