Update more options screen
screencast: https://screencast.googleplex.com/cast/NjI4NDMyNDAyNzQ5ODQ5Nnw5YzEzNzEyZC0xMg Test: deployed locally Bug: 253157211 Change-Id: I1b08ae3431eb1cab2f5a755bd218f02ab0b80a93
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user