Merge "Add the screen when there's no create option for the default provider but there's remoteEntry"

This commit is contained in:
Qinmei Du
2022-11-30 22:45:59 +00:00
committed by Android (Google) Code Review
5 changed files with 130 additions and 18 deletions

View File

@@ -49,6 +49,8 @@
<string name="save_password_to_title">Save password to</string>
<!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
<string name="save_sign_in_to_title">Save sign-in to</string>
<!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
<string name="create_passkey_in_other_device_title">Create a passkey in another device?</string>
<!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
<string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
<!-- TODO: Check the wording here. -->

View File

@@ -44,6 +44,8 @@ import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
@@ -142,25 +144,22 @@ class CredentialManagerRepo(
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
providerDisabledList as List<DisabledProviderData>, context)
var hasDefault = false
var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
var defaultProvider: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
if (providerInfo.isDefault) {defaultProvider = providerInfo}
if (providerInfo.remoteEntry != null) {
remoteEntry = providerInfo.remoteEntry!!
}
}
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
// TODO: Add the screen when defaultProvider has no createOption but
// there's remoteInfo under other providers
if (!hasDefault || defaultProvider.createOptions.isEmpty()) {
if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL)
{CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION}
} else {CreateScreenState.CREATION_OPTION_SELECTION},
toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
requestDisplayInfo,
false,
if (hasDefault) {
ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
} else null
toActiveEntry(defaultProvider, remoteEntry),
)
}
@@ -509,4 +508,38 @@ class CredentialManagerRepo(
"tribank.us"
)
}
private fun toCreateScreenState(
requestDisplayInfo: RequestDisplayInfo,
defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
): CreateScreenState {
return if (
defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
){
CreateScreenState.EXTERNAL_ONLY_SELECTION
} else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
CreateScreenState.PASSKEY_INTRO
} else {
CreateScreenState.PROVIDER_SELECTION
}
} else {
CreateScreenState.CREATION_OPTION_SELECTION
}
}
private fun toActiveEntry(
defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
): ActiveEntry? {
return if (
defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
) {
ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
} else if (
defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
ActiveEntry(defaultProvider, remoteEntry)
} else null
}
}

View File

@@ -60,7 +60,7 @@ fun CreateCredentialScreen(
viewModel.onEntrySelected(it, providerActivityLauncher)
}
val confirmEntryCallback: () -> Unit = {
viewModel.onConfirmCreationSelected(providerActivityLauncher)
viewModel.onConfirmEntrySelected(providerActivityLauncher)
}
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded,
@@ -108,6 +108,13 @@ fun CreateCredentialScreen(
providerInfo = uiState.activeEntry?.activeProvider!!,
onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
)
CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
onOptionSelected = selectEntryCallback,
onConfirm = confirmEntryCallback,
onCancel = viewModel::onCancel,
)
}
},
scrimColor = MaterialTheme.colorScheme.scrim,
@@ -489,7 +496,7 @@ fun CreationSelectionCard(
) {
PrimaryCreateOptionRow(
requestDisplayInfo = requestDisplayInfo,
createOptionInfo = createOptionInfo,
entryInfo = createOptionInfo,
onOptionSelected = onOptionSelected
)
}
@@ -560,18 +567,87 @@ fun CreationSelectionCard(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ExternalOnlySelectionCard(
requestDisplayInfo: RequestDisplayInfo,
activeRemoteEntry: EntryInfo,
onOptionSelected: (EntryInfo) -> Unit,
onConfirm: () -> Unit,
onCancel: () -> Unit,
) {
Card() {
Column() {
Icon(
painter = painterResource(R.drawable.ic_other_devices),
contentDescription = null,
tint = Color.Unspecified,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
.padding(all = 24.dp).size(32.dp)
)
Text(
text = stringResource(R.string.create_passkey_in_other_device_title),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
)
Divider(
thickness = 24.dp,
color = Color.Transparent
)
Card(
shape = MaterialTheme.shapes.medium,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally),
) {
PrimaryCreateOptionRow(
requestDisplayInfo = requestDisplayInfo,
entryInfo = activeRemoteEntry,
onOptionSelected = onOptionSelected
)
}
Divider(
thickness = 24.dp,
color = Color.Transparent
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
CancelButton(
stringResource(R.string.string_cancel),
onClick = onCancel
)
ConfirmButton(
stringResource(R.string.string_continue),
onClick = onConfirm
)
}
Divider(
thickness = 18.dp,
color = Color.Transparent,
modifier = Modifier.padding(bottom = 16.dp)
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
createOptionInfo: CreateOptionInfo,
entryInfo: EntryInfo,
onOptionSelected: (EntryInfo) -> Unit
) {
Entry(
onClick = {onOptionSelected(createOptionInfo)},
onClick = {onOptionSelected(entryInfo)},
icon = {
Icon(
bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
bitmap = if (entryInfo is CreateOptionInfo) {
entryInfo.profileIcon.toBitmap().asImageBitmap()
} else {requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()},
contentDescription = null,
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
modifier = Modifier.padding(start = 18.dp).size(32.dp)

View File

@@ -155,7 +155,7 @@ class CreateCredentialViewModel(
}
}
fun onConfirmCreationSelected(
fun onConfirmEntrySelected(
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
val selectedEntry = uiState.activeEntry?.activeEntryInfo

View File

@@ -95,4 +95,5 @@ enum class CreateScreenState {
CREATION_OPTION_SELECTION,
MORE_OPTIONS_SELECTION,
MORE_OPTIONS_ROW_INTRO,
EXTERNAL_ONLY_SELECTION,
}