Merge "Add the screen when there's no create option for the default provider but there's remoteEntry"
This commit is contained in:
@@ -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. -->
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -155,7 +155,7 @@ class CreateCredentialViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun onConfirmCreationSelected(
|
||||
fun onConfirmEntrySelected(
|
||||
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
|
||||
) {
|
||||
val selectedEntry = uiState.activeEntry?.activeEntryInfo
|
||||
|
||||
@@ -95,4 +95,5 @@ enum class CreateScreenState {
|
||||
CREATION_OPTION_SELECTION,
|
||||
MORE_OPTIONS_SELECTION,
|
||||
MORE_OPTIONS_ROW_INTRO,
|
||||
EXTERNAL_ONLY_SELECTION,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user