Merge "Update UX according to the new design"
diff --git a/packages/CredentialManager/res/drawable/ic_profile.xml b/packages/CredentialManager/res/drawable/ic_profile.xml
deleted file mode 100644
index ae65940..0000000
--- a/packages/CredentialManager/res/drawable/ic_profile.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
- android:viewportWidth="46"
- android:viewportHeight="46"
- android:width="46dp"
- android:height="46dp">
- <path
- android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
- android:strokeColor="#202124"
- android:strokeAlpha="0.13"
- android:strokeWidth="1" />
-</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index a3ebf1e..91ffc44 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -9,6 +9,8 @@
<string name="string_cancel">Cancel</string>
<!-- Button label to confirm choosing the default dialog information and continue. [CHAR LIMIT=40] -->
<string name="string_continue">Continue</string>
+ <!-- Button label to create this credential in other available places. [CHAR LIMIT=40] -->
+ <string name="string_more_options">More options</string>
<!-- This appears as a text button where users can click to create this passkey in other available places. [CHAR LIMIT=80] -->
<string name="string_create_in_another_place">Create in another place</string>
<!-- This appears as a text button where users can click to create this password or other credential types in other available places. [CHAR LIMIT=80] -->
@@ -20,11 +22,11 @@
<!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_title">Safer with passkeys</string>
<!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the passwords side. [CHAR LIMIT=200] -->
- <string name="passkey_creation_intro_body_password">No need to create or remember complex passwords</string>
+ <string name="passkey_creation_intro_body_password">With passkeys, you don’t need to create or remember complex passwords</string>
<!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the safety side. [CHAR LIMIT=200] -->
- <string name="passkey_creation_intro_body_fingerprint">Use your fingerprint, face, or screen lock to create a unique passkey</string>
+ <string name="passkey_creation_intro_body_fingerprint">Passkeys are encrypted digital keys you create using your fingerprint, face, or screen lock</string>
<!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the using other devices side. [CHAR LIMIT=200] -->
- <string name="passkey_creation_intro_body_device">Passkeys are saved to a password manager, so you can sign in on other devices</string>
+ <string name="passkey_creation_intro_body_device">They are saved to a password manager, so you can sign in on other devices</string>
<!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
<string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
<!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
@@ -33,26 +35,23 @@
<string name="save_your_sign_in_info">save your sign-in info</string>
<!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
- <string name="choose_provider_body">Set a default password manager to save your passwords and passkeys and sign in faster next time.</string>
+ <string name="choose_provider_body">Select a password manager to save your info and sign in faster next time.</string>
<!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] -->
- <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+ <string name="choose_create_option_passkey_title">Create passkey for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
<!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] -->
- <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+ <string name="choose_create_option_password_title">Save password for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
<!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
- <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+ <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
<!-- This appears as the description body of the modal bottom sheet for users to choose the create option inside a provider. [CHAR LIMIT=200] -->
- <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="type" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g></string>
- <!-- Types which are inserted as a placeholder for string choose_create_option_description. [CHAR LIMIT=200] -->
+ <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="credentialTypes" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g>.</string>
+ <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
<string name="passkey">passkey</string>
<string name="password">password</string>
<string name="sign_ins">sign-ins</string>
+ <string name="sign_in_info">sign-in info</string>
- <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created passkey can be created to. [CHAR LIMIT=200] -->
- <string name="create_passkey_in_title">Create passkey in</string>
<!-- This appears as the title of the modal bottom sheet for users to choose other available places the created password can be saved to. [CHAR LIMIT=200] -->
- <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>
+ <string name="save_credential_to_title">Save <xliff:g id="credentialTypes" example="passkey">%1$s</xliff:g> 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] -->
@@ -65,11 +64,13 @@
<!-- Button label to set the selected provider on the modal bottom sheet not as default but just use once. [CHAR LIMIT=40] -->
<string name="use_once">Use once</string>
<!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] -->
- <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
+ <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords • <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
<!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passwords. [CHAR LIMIT=80] -->
<string name="more_options_usage_passwords"><xliff:g id="passwordsNumber" example="3">%1$s</xliff:g> passwords</string>
<!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passkeys. [CHAR LIMIT=80] -->
<string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber" example="4">%1$s</xliff:g> passkeys</string>
+ <!-- Appears as an option row subtitle to show how many total credentials are saved in this option when the request type is other sign-ins. [CHAR LIMIT=80] -->
+ <string name="more_options_usage_credentials"><xliff:g id="totalCredentialsNumber" example="5">%1$s</xliff:g> credentials</string>
<!-- Appears before a request display name when the credential type is passkey . [CHAR LIMIT=80] -->
<string name="passkey_before_subtitle">Passkey</string>
<!-- Appears as an option row title that users can choose to use another device for this creation. [CHAR LIMIT=80] -->
@@ -92,8 +93,8 @@
<string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- Appears as an option row for viewing all the available sign-in options. [CHAR LIMIT=80] -->
<string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
- <!-- Button label to close the dialog when the user does not want to use any sign-in. [CHAR LIMIT=40] -->
- <string name="get_dialog_button_label_no_thanks">No thanks</string>
+ <!-- Appears as a text button in the snackbar for users to click to view all options. [CHAR LIMIT=80] -->
+ <string name="snackbar_action">View options</string>
<!-- Button label to continue with the selected sign-in. [CHAR LIMIT=40] -->
<string name="get_dialog_button_label_continue">Continue</string>
<!-- Separator for sign-in type and username in a sign-in entry. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index d7ce532..6a6a3c5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -36,13 +36,14 @@
import android.credentials.ui.BaseDialogResult
import android.credentials.ui.ProviderPendingIntentResponse
import android.credentials.ui.UserSelectionDialogResult
-import android.graphics.drawable.Icon
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
import android.service.credentials.CredentialProviderService
import android.util.ArraySet
-import com.android.credentialmanager.createflow.CreateCredentialUiState
+import com.android.credentialmanager.createflow.RequestDisplayInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
@@ -67,7 +68,7 @@
requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
- ) ?: testGetRequestInfo()
+ ) ?: testCreatePasskeyRequestInfo()
providerEnabledList = when (requestInfo.type) {
RequestInfo.TYPE_CREATE ->
@@ -134,19 +135,24 @@
)
}
- fun createCredentialInitialUiState(): CreateCredentialUiState {
- val requestDisplayInfo = CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
+ fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
// Handle runtime cast error
- providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
- val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
- // Handle runtime cast error
- providerDisabledList, context)
+ providerEnabledList as List<CreateCredentialProviderData>, context)
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
}
- return CreateFlowUtils.toCreateCredentialUiState(
- providerEnabledList, providerDisabledList, requestDisplayInfo, false)
+ return providerEnabledList
+ }
+
+ fun getCreateProviderDisableListInitialUiState(): List<DisabledProviderInfo>? {
+ return CreateFlowUtils.toDisabledProviderList(
+ // Handle runtime cast error
+ providerDisabledList, context)
+ }
+
+ fun getCreateRequestDisplayInfoInitialUiState(): RequestDisplayInfo {
+ return CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
}
companion object {
@@ -167,33 +173,33 @@
// TODO: below are prototype functionalities. To be removed for productionization.
private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
- return listOf(
- CreateCredentialProviderData
- .Builder("io.enpass.app")
- .setSaveEntries(
- listOf<Entry>(
- newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
- 20, 7, 27, 10000),
- newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
- 20, 7, 27, 11000),
- )
- )
- .setRemoteEntry(
- newRemoteEntry("key2", "subkey-1")
- )
- .build(),
- CreateCredentialProviderData
- .Builder("com.dashlane")
- .setSaveEntries(
- listOf<Entry>(
- newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
- 20, 7, 27, 30000),
- newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
- 20, 7, 27, 31000),
- )
- )
- .build(),
- )
+ return listOf(
+ CreateCredentialProviderData
+ .Builder("io.enpass.app")
+ .setSaveEntries(
+ listOf<Entry>(
+ newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+ 20, 7, 27, 10000),
+ newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
+ 20, 7, 27, 11000),
+ )
+ )
+ .setRemoteEntry(
+ newRemoteEntry("key2", "subkey-1")
+ )
+ .build(),
+ CreateCredentialProviderData
+ .Builder("com.dashlane")
+ .setSaveEntries(
+ listOf<Entry>(
+ newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+ 20, 7, 27, 30000),
+ newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+ 20, 7, 27, 31000),
+ )
+ )
+ .build(),
+ )
}
private fun testDisabledProviderList(): List<DisabledProviderData>? {
@@ -312,8 +318,7 @@
val credentialEntry = CredentialEntry(credentialType, credentialTypeDisplayName, userName,
userDisplayName, pendingIntent, lastUsedTimeMillis
- ?: 0L, Icon.createWithResource(context, R.drawable.ic_passkey),
- false)
+ ?: 0L, null, false)
return Entry(
key,
@@ -322,7 +327,7 @@
pendingIntent,
null
)
- }
+ }
private fun newCreateEntry(
key: String,
@@ -351,7 +356,7 @@
val createEntry = CreateEntry(
providerDisplayName, pendingIntent,
- Icon.createWithResource(context, R.drawable.ic_profile), lastUsedTimeMillis,
+ null, lastUsedTimeMillis,
listOf(
CredentialCountInformation.createPasswordCountInformation(passwordCount),
CredentialCountInformation.createPublicKeyCountInformation(passkeyCount),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 22b2be9..48aebec 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -123,8 +123,7 @@
userName = credentialEntry.username.toString(),
displayName = credentialEntry.displayName?.toString(),
// TODO: proper fallback
- icon = credentialEntry.icon?.loadDrawable(context)
- ?: context.getDrawable(R.drawable.ic_other_sign_in)!!,
+ icon = credentialEntry.icon?.loadDrawable(context),
lastUsedTimeMillis = credentialEntry.lastUsedTimeMillis,
)
}
@@ -196,9 +195,8 @@
fun toEnabledProviderList(
providerDataList: List<CreateCredentialProviderData>,
- requestDisplayInfo: RequestDisplayInfo,
context: Context,
- ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
+ ): List<EnabledProviderInfo> {
// TODO: get from the actual service info
val packageManager = context.packageManager
@@ -219,7 +217,7 @@
name = it.providerFlattenedComponentName,
displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
createOptions = toCreationOptionInfoList(
- it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
+ it.providerFlattenedComponentName, it.saveEntries, context),
remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
)
}
@@ -228,14 +226,14 @@
fun toDisabledProviderList(
providerDataList: List<DisabledProviderData>?,
context: Context,
- ): List<com.android.credentialmanager.createflow.DisabledProviderInfo>? {
+ ): List<DisabledProviderInfo>? {
// TODO: get from the actual service info
val packageManager = context.packageManager
return providerDataList?.map {
val pkgInfo = packageManager
.getPackageInfo(it.providerFlattenedComponentName,
PackageManager.PackageInfoFlags.of(0))
- com.android.credentialmanager.createflow.DisabledProviderInfo(
+ DisabledProviderInfo(
icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
name = it.providerFlattenedComponentName,
displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
@@ -295,14 +293,15 @@
fun toCreateCredentialUiState(
enabledProviders: List<EnabledProviderInfo>,
disabledProviders: List<DisabledProviderInfo>?,
+ defaultProviderId: String?,
requestDisplayInfo: RequestDisplayInfo,
isOnPasskeyIntroStateAlready: Boolean,
+ isPasskeyFirstUse: Boolean,
): CreateCredentialUiState {
var createOptionSize = 0
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
var defaultProvider: EnabledProviderInfo? = null
- val defaultProviderId = UserConfigRepo.getInstance().getDefaultProviderId()
enabledProviders.forEach {
enabledProvider ->
if (defaultProviderId != null) {
@@ -322,13 +321,18 @@
enabledProviders = enabledProviders,
disabledProviders = disabledProviders,
toCreateScreenState(
- createOptionSize, isOnPasskeyIntroStateAlready,
- requestDisplayInfo, defaultProvider, remoteEntry),
+ /*createOptionSize=*/createOptionSize,
+ /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready,
+ /*requestDisplayInfo=*/requestDisplayInfo,
+ /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
+ /*isPasskeyFirstUse=*/isPasskeyFirstUse),
requestDisplayInfo,
- isOnPasskeyIntroStateAlready,
+ defaultProvider != null,
toActiveEntry(
- /*defaultProvider=*/defaultProvider, createOptionSize,
- lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+ /*defaultProvider=*/defaultProvider,
+ /*createOptionSize=*/createOptionSize,
+ /*lastSeenProviderWithNonEmptyCreateOptions=*/lastSeenProviderWithNonEmptyCreateOptions,
+ /*remoteEntry=*/remoteEntry),
)
}
@@ -338,9 +342,10 @@
requestDisplayInfo: RequestDisplayInfo,
defaultProvider: EnabledProviderInfo?,
remoteEntry: RemoteInfo?,
+ isPasskeyFirstUse: Boolean,
): CreateScreenState {
return if (
- UserConfigRepo.getInstance().getIsFirstUse() && requestDisplayInfo
+ isPasskeyFirstUse && requestDisplayInfo
.type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
CreateScreenState.PASSKEY_INTRO
} else if (
@@ -382,7 +387,6 @@
private fun toCreationOptionInfoList(
providerId: String,
creationEntries: List<Entry>,
- requestDisplayInfo: RequestDisplayInfo,
context: Context,
): List<CreateOptionInfo> {
return creationEntries.map {
@@ -397,8 +401,7 @@
pendingIntent = it.pendingIntent,
fillInIntent = it.frameworkExtrasIntent,
userProviderDisplayName = createEntry.accountName.toString(),
- profileIcon = createEntry.icon?.loadDrawable(context)
- ?: requestDisplayInfo.typeIcon,
+ profileIcon = createEntry.icon?.loadDrawable(context),
passwordCount = CredentialCountInformation.getPasswordCount(
createEntry.credentialCountInformationList) ?: 0,
passkeyCount = CredentialCountInformation.getPasskeyCount(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
index 5e77663..021dcab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
@@ -32,7 +32,7 @@
}
}
- fun setIsFirstUse(
+ fun setIsPasskeyFirstUse(
isFirstUse: Boolean
) {
sharedPreferences.edit().apply {
@@ -45,7 +45,7 @@
return sharedPreferences.getString(DEFAULT_PROVIDER, null)
}
- fun getIsFirstUse(): Boolean {
+ fun getIsPasskeyFirstUse(): Boolean {
return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
similarity index 95%
rename from packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
index 80764b5..d0271ab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
@@ -23,7 +23,7 @@
import androidx.compose.runtime.Composable
@Composable
-fun CancelButton(text: String, onClick: () -> Unit) {
+fun ActionButton(text: String, onClick: () -> Unit) {
TextButton(
onClick = onClick,
colors = ButtonDefaults.textButtonColors(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 38e2caa..3d23613 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -14,14 +14,11 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material.icons.Icons
@@ -43,7 +40,7 @@
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
-import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ActionButton
import com.android.credentialmanager.common.ui.ConfirmButton
import com.android.credentialmanager.common.ui.Entry
import com.android.credentialmanager.common.ui.TextOnSurface
@@ -79,28 +76,30 @@
requestDisplayInfo = uiState.requestDisplayInfo,
enabledProviderList = uiState.enabledProviders,
disabledProviderList = uiState.disabledProviders,
- onCancel = viewModel::onCancel,
onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
onDisabledPasswordManagerSelected =
viewModel::onDisabledPasswordManagerSelected,
- onRemoteEntrySelected = viewModel::onEntrySelected,
+ onMoreOptionsSelected = viewModel::onMoreOptionsSelectedOnProviderSelection,
)
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
enabledProviderList = uiState.enabledProviders,
providerInfo = uiState.activeEntry?.activeProvider!!,
createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
- showActiveEntryOnly = uiState.showActiveEntryOnly,
onOptionSelected = viewModel::onEntrySelected,
onConfirm = viewModel::onConfirmEntrySelected,
- onCancel = viewModel::onCancel,
- onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
+ onMoreOptionsSelected = viewModel::onMoreOptionsSelectedOnCreationSelection,
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
enabledProviderList = uiState.enabledProviders,
disabledProviderList = uiState.disabledProviders,
- onBackButtonSelected = viewModel::onBackButtonSelected,
+ hasDefaultProvider = uiState.hasDefaultProvider,
+ isFromProviderSelection = uiState.isFromProviderSelection!!,
+ onBackProviderSelectionButtonSelected =
+ viewModel::onBackProviderSelectionButtonSelected,
+ onBackCreationSelectionButtonSelected =
+ viewModel::onBackCreationSelectionButtonSelected,
onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
onDisabledPasswordManagerSelected =
viewModel::onDisabledPasswordManagerSelected,
@@ -172,7 +171,7 @@
TextSecondary(
text = stringResource(R.string.passkey_creation_intro_body_password),
style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(start = 16.dp),
+ modifier = Modifier.padding(start = 16.dp, end = 4.dp),
)
}
Divider(
@@ -192,7 +191,7 @@
TextSecondary(
text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(start = 16.dp),
+ modifier = Modifier.padding(start = 16.dp, end = 4.dp),
)
}
Divider(
@@ -212,7 +211,7 @@
TextSecondary(
text = stringResource(R.string.passkey_creation_intro_body_device),
style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(start = 16.dp),
+ modifier = Modifier.padding(start = 16.dp, end = 4.dp),
)
}
Divider(
@@ -223,7 +222,7 @@
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- CancelButton(
+ ActionButton(
stringResource(R.string.string_cancel),
onClick = onCancel
)
@@ -249,8 +248,7 @@
disabledProviderList: List<DisabledProviderInfo>?,
onOptionSelected: (ActiveEntry) -> Unit,
onDisabledPasswordManagerSelected: () -> Unit,
- onCancel: () -> Unit,
- onRemoteEntrySelected: (EntryInfo) -> Unit,
+ onMoreOptionsSelected: () -> Unit,
) {
ContainerCard() {
Column() {
@@ -301,124 +299,7 @@
enabledProviderInfo.createOptions.forEach { createOptionInfo ->
item {
MoreOptionsInfoRow(
- providerInfo = enabledProviderInfo,
- createOptionInfo = createOptionInfo,
- onOptionSelected = {
- onOptionSelected(
- ActiveEntry(
- enabledProviderInfo,
- createOptionInfo
- )
- )
- })
- }
- }
- }
- if (disabledProviderList != null && disabledProviderList.isNotEmpty()) {
- item {
- MoreOptionsDisabledProvidersRow(
- disabledProviders = disabledProviderList,
- onDisabledPasswordManagerSelected =
- onDisabledPasswordManagerSelected,
- )
- }
- }
- }
- }
- // TODO: handle the error situation that if multiple remoteInfos exists
- enabledProviderList.forEach { enabledProvider ->
- if (enabledProvider.remoteEntry != null) {
- TextButton(
- onClick = {
- onRemoteEntrySelected(enabledProvider.remoteEntry!!)
- },
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- colors = ButtonDefaults.textButtonColors(
- contentColor = MaterialTheme.colorScheme.primary,
- )
- ) {
- Text(
- text = stringResource(R.string.string_save_to_another_device),
- textAlign = TextAlign.Center,
- )
- }
- }
- }
- 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)
- )
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MoreOptionsSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- disabledProviderList: List<DisabledProviderInfo>?,
- onBackButtonSelected: () -> Unit,
- onOptionSelected: (ActiveEntry) -> Unit,
- onDisabledPasswordManagerSelected: () -> Unit,
- onRemoteEntrySelected: (EntryInfo) -> Unit,
-) {
- ContainerCard() {
- Column() {
- TopAppBar(
- title = {
- TextOnSurface(
- text = when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL ->
- stringResource(R.string.create_passkey_in_title)
- TYPE_PASSWORD_CREDENTIAL ->
- stringResource(R.string.save_password_to_title)
- else -> stringResource(R.string.save_sign_in_to_title)
- },
- style = MaterialTheme.typography.titleMedium,
- )
- },
- navigationIcon = {
- IconButton(onClick = onBackButtonSelected) {
- Icon(
- Icons.Filled.ArrowBack,
- stringResource(R.string.accessibility_back_arrow_button)
- )
- }
- },
- colors = TopAppBarDefaults.smallTopAppBarColors
- (containerColor = Color.Transparent),
- )
- Divider(
- thickness = 8.dp,
- color = Color.Transparent
- )
- ContainerCard(
- shape = MaterialTheme.shapes.medium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
- ) {
- LazyColumn(
- verticalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- enabledProviderList.forEach { enabledProviderInfo ->
- enabledProviderInfo.createOptions.forEach { createOptionInfo ->
- item {
- MoreOptionsInfoRow(
+ requestDisplayInfo = requestDisplayInfo,
providerInfo = enabledProviderInfo,
createOptionInfo = createOptionInfo,
onOptionSelected = {
@@ -439,6 +320,124 @@
onDisabledPasswordManagerSelected,
)
}
+ }
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ // TODO: handle the error situation that if multiple remoteInfos exists
+ enabledProviderList.forEach { enabledProvider ->
+ if (enabledProvider.remoteEntry != null) {
+ Row(
+ horizontalArrangement = Arrangement.Start,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ ActionButton(
+ stringResource(R.string.string_more_options),
+ onMoreOptionsSelected
+ )
+ }
+ }
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsSelectionCard(
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ disabledProviderList: List<DisabledProviderInfo>?,
+ hasDefaultProvider: Boolean,
+ isFromProviderSelection: Boolean,
+ onBackProviderSelectionButtonSelected: () -> Unit,
+ onBackCreationSelectionButtonSelected: () -> Unit,
+ onOptionSelected: (ActiveEntry) -> Unit,
+ onDisabledPasswordManagerSelected: () -> Unit,
+ onRemoteEntrySelected: (EntryInfo) -> Unit,
+) {
+ ContainerCard() {
+ Column() {
+ TopAppBar(
+ title = {
+ TextOnSurface(
+ text =
+ stringResource(
+ R.string.save_credential_to_title,
+ when (requestDisplayInfo.type) {
+ TYPE_PUBLIC_KEY_CREDENTIAL ->
+ stringResource(R.string.passkey)
+ TYPE_PASSWORD_CREDENTIAL ->
+ stringResource(R.string.password)
+ else -> stringResource(R.string.sign_in_info)
+ }),
+ style = MaterialTheme.typography.titleMedium,
+ )
+ },
+ navigationIcon = {
+ IconButton(
+ onClick =
+ if (isFromProviderSelection)
+ onBackProviderSelectionButtonSelected
+ else onBackCreationSelectionButtonSelected
+ ) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ stringResource(R.string.accessibility_back_arrow_button)
+ )
+ }
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors
+ (containerColor = Color.Transparent),
+ modifier = Modifier.padding(top = 12.dp)
+ )
+ Divider(
+ thickness = 8.dp,
+ color = Color.Transparent
+ )
+ ContainerCard(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally)
+ ) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ if (hasDefaultProvider) {
+ enabledProviderList.forEach { enabledProviderInfo ->
+ enabledProviderInfo.createOptions.forEach { createOptionInfo ->
+ item {
+ MoreOptionsInfoRow(
+ requestDisplayInfo = requestDisplayInfo,
+ providerInfo = enabledProviderInfo,
+ createOptionInfo = createOptionInfo,
+ onOptionSelected = {
+ onOptionSelected(
+ ActiveEntry(
+ enabledProviderInfo,
+ createOptionInfo
+ )
+ )
+ })
+ }
+ }
+ }
+ item {
+ MoreOptionsDisabledProvidersRow(
+ disabledProviders = disabledProviderList,
+ onDisabledPasswordManagerSelected =
+ onDisabledPasswordManagerSelected,
+ )
+ }
+ }
// TODO: handle the error situation that if multiple remoteInfos exists
enabledProviderList.forEach {
if (it.remoteEntry != null) {
@@ -453,7 +452,7 @@
}
}
Divider(
- thickness = 18.dp,
+ thickness = 8.dp,
color = Color.Transparent,
modifier = Modifier.padding(bottom = 40.dp)
)
@@ -496,7 +495,7 @@
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- CancelButton(
+ ActionButton(
stringResource(R.string.use_once),
onClick = onUseOnceSelected
)
@@ -521,34 +520,42 @@
enabledProviderList: List<EnabledProviderInfo>,
providerInfo: EnabledProviderInfo,
createOptionInfo: CreateOptionInfo,
- showActiveEntryOnly: Boolean,
onOptionSelected: (EntryInfo) -> Unit,
onConfirm: () -> Unit,
- onCancel: () -> Unit,
onMoreOptionsSelected: () -> Unit,
) {
ContainerCard() {
Column() {
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
Icon(
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(all = 24.dp).size(32.dp)
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally).size(32.dp)
+ )
+ TextSecondary(
+ text = providerInfo.displayName,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(vertical = 10.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
)
TextOnSurface(
text = when (requestDisplayInfo.type) {
TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(
R.string.choose_create_option_passkey_title,
- providerInfo.displayName
+ requestDisplayInfo.appDomainName
)
TYPE_PASSWORD_CREDENTIAL -> stringResource(
R.string.choose_create_option_password_title,
- providerInfo.displayName
+ requestDisplayInfo.appDomainName
)
else -> stringResource(
R.string.choose_create_option_sign_in_title,
- providerInfo.displayName
+ requestDisplayInfo.appDomainName
)
},
style = MaterialTheme.typography.titleMedium,
@@ -556,6 +563,51 @@
.align(alignment = Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
)
+ ContainerCard(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(all = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ PrimaryCreateOptionRow(
+ requestDisplayInfo = requestDisplayInfo,
+ entryInfo = createOptionInfo,
+ onOptionSelected = onOptionSelected
+ )
+ }
+ var shouldShowMoreOptionsButton = false
+ var createOptionsSize = 0
+ var remoteEntry: RemoteInfo? = null
+ enabledProviderList.forEach { enabledProvider ->
+ if (enabledProvider.remoteEntry != null) {
+ remoteEntry = enabledProvider.remoteEntry
+ }
+ createOptionsSize += enabledProvider.createOptions.size
+ }
+ if (createOptionsSize > 1 || remoteEntry != null) {
+ shouldShowMoreOptionsButton = true
+ }
+ Row(
+ horizontalArrangement =
+ if (shouldShowMoreOptionsButton) Arrangement.SpaceBetween else Arrangement.End,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ if (shouldShowMoreOptionsButton) {
+ ActionButton(
+ stringResource(R.string.string_more_options),
+ onClick = onMoreOptionsSelected
+ )
+ }
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
+ Divider(
+ thickness = 1.dp,
+ color = Color.LightGray,
+ modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 18.dp)
+ )
if (createOptionInfo.userProviderDisplayName != null) {
TextSecondary(
text = stringResource(
@@ -570,88 +622,8 @@
createOptionInfo.userProviderDisplayName
),
style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(all = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- )
- }
- ContainerCard(
- shape = MaterialTheme.shapes.medium,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- ) {
- PrimaryCreateOptionRow(
- requestDisplayInfo = requestDisplayInfo,
- entryInfo = createOptionInfo,
- onOptionSelected = onOptionSelected
- )
- }
- if (!showActiveEntryOnly) {
- var createOptionsSize = 0
- enabledProviderList.forEach { enabledProvider ->
- createOptionsSize += enabledProvider.createOptions.size
- }
- if (createOptionsSize > 1) {
- TextButton(
- onClick = onMoreOptionsSelected,
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- colors = ButtonDefaults.textButtonColors(
- contentColor = MaterialTheme.colorScheme.primary,
- ),
- ) {
- Text(
- text =
- when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL ->
- stringResource(R.string.string_create_in_another_place)
- else -> stringResource(R.string.string_save_to_another_place)
- },
- textAlign = TextAlign.Center,
- )
- }
- } else if (
- requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
- ) {
- // TODO: handle the error situation that if multiple remoteInfos exists
- enabledProviderList.forEach { enabledProvider ->
- if (enabledProvider.remoteEntry != null) {
- TextButton(
- onClick = {
- onOptionSelected(enabledProvider.remoteEntry!!)
- },
- modifier = Modifier
- .padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally),
- colors = ButtonDefaults.textButtonColors(
- contentColor = MaterialTheme.colorScheme.primary,
- ),
- ) {
- Text(
- text = stringResource(R.string.string_use_another_device),
- textAlign = TextAlign.Center,
- )
- }
- }
- }
- }
- }
- 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
+ modifier = Modifier.padding(
+ start = 24.dp, top = 8.dp, bottom = 18.dp, end = 24.dp)
)
}
Divider(
@@ -712,7 +684,7 @@
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- CancelButton(
+ ActionButton(
stringResource(R.string.string_cancel),
onClick = onCancel
)
@@ -740,16 +712,20 @@
Entry(
onClick = { onOptionSelected(entryInfo) },
icon = {
- Icon(
- bitmap = if (entryInfo is CreateOptionInfo) {
- entryInfo.profileIcon.toBitmap().asImageBitmap()
- } else {
- requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()
- },
- contentDescription = null,
- tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- modifier = Modifier.padding(start = 10.dp).size(32.dp)
- )
+ if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) {
+ Image(
+ bitmap = entryInfo.profileIcon.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ )
+ } else {
+ Icon(
+ bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ )
+ }
},
label = {
Column() {
@@ -763,9 +739,9 @@
)
TextSecondary(
text = if (requestDisplayInfo.subtitle != null) {
- stringResource(
+ requestDisplayInfo.subtitle + " • " + stringResource(
R.string.passkey_before_subtitle
- ) + " - " + requestDisplayInfo.subtitle
+ )
} else {
stringResource(R.string.passkey_before_subtitle)
},
@@ -787,11 +763,25 @@
)
}
else -> {
- TextOnSurfaceVariant(
- text = requestDisplayInfo.title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp, bottom = 16.dp, start = 5.dp),
- )
+ if (requestDisplayInfo.subtitle != null) {
+ TextOnSurfaceVariant(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, start = 5.dp),
+ )
+ TextOnSurfaceVariant(
+ text = requestDisplayInfo.subtitle,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+ )
+ } else {
+ TextOnSurfaceVariant(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(
+ top = 16.dp, bottom = 16.dp, start = 5.dp),
+ )
+ }
}
}
}
@@ -802,6 +792,7 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsInfoRow(
+ requestDisplayInfo: RequestDisplayInfo,
providerInfo: EnabledProviderInfo,
createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
@@ -826,50 +817,67 @@
TextSecondary(
text = createOptionInfo.userProviderDisplayName,
style = MaterialTheme.typography.bodyMedium,
- // TODO: update the logic here for the case there is only total count
- modifier = if (
- createOptionInfo.passwordCount != null ||
- createOptionInfo.passkeyCount != null
- ) Modifier.padding(start = 5.dp) else Modifier
- .padding(bottom = 16.dp, start = 5.dp),
+ modifier = Modifier.padding(start = 5.dp),
)
}
- if (createOptionInfo.passwordCount != null &&
- createOptionInfo.passkeyCount != null
- ) {
- TextSecondary(
- text =
- stringResource(
- R.string.more_options_usage_passwords_passkeys,
- createOptionInfo.passwordCount,
- createOptionInfo.passkeyCount
- ),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
- )
- } else if (createOptionInfo.passwordCount != null) {
- TextSecondary(
- text =
- stringResource(
- R.string.more_options_usage_passwords,
- createOptionInfo.passwordCount
- ),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
- )
- } else if (createOptionInfo.passkeyCount != null) {
- TextSecondary(
- text =
- stringResource(
- R.string.more_options_usage_passkeys,
- createOptionInfo.passkeyCount
- ),
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
- )
- } else if (createOptionInfo.totalCredentialCount != null) {
- // TODO: Handle the case when there is total count
- // but no passwords and passkeys after design is set
+ if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
+ requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
+ if (createOptionInfo.passwordCount != null &&
+ createOptionInfo.passkeyCount != null
+ ) {
+ TextSecondary(
+ text =
+ stringResource(
+ R.string.more_options_usage_passwords_passkeys,
+ createOptionInfo.passwordCount,
+ createOptionInfo.passkeyCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+ )
+ } else if (createOptionInfo.passwordCount != null) {
+ TextSecondary(
+ text =
+ stringResource(
+ R.string.more_options_usage_passwords,
+ createOptionInfo.passwordCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+ )
+ } else if (createOptionInfo.passkeyCount != null) {
+ TextSecondary(
+ text =
+ stringResource(
+ R.string.more_options_usage_passkeys,
+ createOptionInfo.passkeyCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+ )
+ } else {
+ Divider(
+ thickness = 16.dp,
+ color = Color.Transparent,
+ )
+ }
+ } else {
+ if (createOptionInfo.totalCredentialCount != null) {
+ TextSecondary(
+ text =
+ stringResource(
+ R.string.more_options_usage_credentials,
+ createOptionInfo.totalCredentialCount
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+ )
+ } else {
+ Divider(
+ thickness = 16.dp,
+ color = Color.Transparent,
+ )
+ }
}
}
}
@@ -901,7 +909,7 @@
)
// TODO: Update the subtitle once design is confirmed
TextSecondary(
- text = disabledProviders.joinToString(separator = ", ") { it.displayName },
+ text = disabledProviders.joinToString(separator = " • ") { it.displayName },
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 9d029dff..7b9e113 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -39,18 +39,39 @@
val disabledProviders: List<DisabledProviderInfo>? = null,
val currentScreenState: CreateScreenState,
val requestDisplayInfo: RequestDisplayInfo,
- val showActiveEntryOnly: Boolean,
+ // Should not change with the real time update of default provider, only determine whether we're
+ // showing provider selection page at the beginning
+ val hasDefaultProvider: Boolean,
val activeEntry: ActiveEntry? = null,
val selectedEntry: EntryInfo? = null,
val hidden: Boolean = false,
val providerActivityPending: Boolean = false,
+ val isFromProviderSelection: Boolean? = null,
)
class CreateCredentialViewModel(
- credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
+ credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance(),
+ userConfigRepo: UserConfigRepo = UserConfigRepo.getInstance()
) : ViewModel() {
- var uiState by mutableStateOf(credManRepo.createCredentialInitialUiState())
+ var providerEnableListUiState = credManRepo.getCreateProviderEnableListInitialUiState()
+
+ var providerDisableListUiState = credManRepo.getCreateProviderDisableListInitialUiState()
+
+ var requestDisplayInfoUiState = credManRepo.getCreateRequestDisplayInfoInitialUiState()
+
+ var defaultProviderId = userConfigRepo.getDefaultProviderId()
+
+ var isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+
+ var uiState by mutableStateOf(
+ CreateFlowUtils.toCreateCredentialUiState(
+ providerEnableListUiState,
+ providerDisableListUiState,
+ defaultProviderId,
+ requestDisplayInfoUiState,
+ false,
+ isPasskeyFirstUse))
private set
val dialogResult: MutableLiveData<DialogResult> by lazy {
@@ -63,9 +84,9 @@
fun onConfirmIntro() {
uiState = CreateFlowUtils.toCreateCredentialUiState(
- uiState.enabledProviders, uiState.disabledProviders,
- uiState.requestDisplayInfo, true)
- UserConfigRepo.getInstance().setIsFirstUse(false)
+ providerEnableListUiState, providerDisableListUiState, defaultProviderId,
+ requestDisplayInfoUiState, true, isPasskeyFirstUse)
+ UserConfigRepo.getInstance().setIsPasskeyFirstUse(false)
}
fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
@@ -74,22 +95,35 @@
}
}
- fun onMoreOptionsSelected() {
+ fun onMoreOptionsSelectedOnProviderSelection() {
uiState = uiState.copy(
currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+ isFromProviderSelection = true
)
}
- fun onBackButtonSelected() {
+ fun onMoreOptionsSelectedOnCreationSelection() {
uiState = uiState.copy(
- currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+ currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+ isFromProviderSelection = false
+ )
+ }
+
+ fun onBackProviderSelectionButtonSelected() {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.PROVIDER_SELECTION,
+ )
+ }
+
+ fun onBackCreationSelectionButtonSelected() {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
)
}
fun onEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.MORE_OPTIONS_ROW_INTRO,
- showActiveEntryOnly = false,
activeEntry = activeEntry
)
}
@@ -97,7 +131,6 @@
fun onEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- showActiveEntryOnly = true,
activeEntry = activeEntry
)
val providerId = uiState.activeEntry?.activeProvider?.name
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index fda0b97..58db36c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -55,7 +55,7 @@
pendingIntent: PendingIntent?,
fillInIntent: Intent?,
val userProviderDisplayName: String?,
- val profileIcon: Drawable,
+ val profileIcon: Drawable?,
val passwordCount: Int?,
val passkeyCount: Int?,
val totalCredentialCount: Int?,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 619f5a3..5e7f1e0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -62,7 +62,7 @@
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
-import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ActionButton
import com.android.credentialmanager.common.ui.Entry
import com.android.credentialmanager.common.ui.TextOnSurface
import com.android.credentialmanager.common.ui.TextSecondary
@@ -96,7 +96,6 @@
requestDisplayInfo = uiState.requestDisplayInfo,
providerDisplayInfo = uiState.providerDisplayInfo,
onEntrySelected = viewModel::onEntrySelected,
- onCancel = viewModel::onCancel,
onMoreOptionSelected = viewModel::onMoreOptionSelected,
)
} else {
@@ -135,7 +134,6 @@
requestDisplayInfo: RequestDisplayInfo,
providerDisplayInfo: ProviderDisplayInfo,
onEntrySelected: (EntryInfo) -> Unit,
- onCancel: () -> Unit,
onMoreOptionSelected: () -> Unit,
) {
val sortedUserNameToCredentialEntryList =
@@ -181,9 +179,6 @@
onEntrySelected = onEntrySelected,
)
}
- item {
- SignInAnotherWayRow(onSelect = onMoreOptionSelected)
- }
}
}
Divider(
@@ -194,7 +189,9 @@
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
+ ActionButton(
+ stringResource(R.string.get_dialog_use_saved_passkey_for),
+ onMoreOptionSelected)
}
Divider(
thickness = 18.dp,
@@ -425,13 +422,22 @@
Entry(
onClick = { onEntrySelected(credentialEntryInfo) },
icon = {
- Icon(
- modifier = Modifier.padding(start = 10.dp).size(32.dp),
- bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
- // TODO: add description.
- contentDescription = "",
- tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- )
+ if (credentialEntryInfo.icon != null) {
+ Image(
+ modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
+ // TODO: add description.
+ contentDescription = "",
+ )
+ } else {
+ Icon(
+ modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ painter = painterResource(R.drawable.ic_other_sign_in),
+ // TODO: add description.
+ contentDescription = "",
+ tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant
+ )
+ }
},
label = {
Column() {
@@ -544,49 +550,35 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun SignInAnotherWayRow(onSelect: () -> Unit) {
- Entry(
- onClick = onSelect,
- label = {
- TextOnSurfaceVariant(
- text = stringResource(R.string.get_dialog_use_saved_passkey_for),
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(vertical = 16.dp)
- )
- }
- )
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
fun SnackBarScreen(
onClick: (Boolean) -> Unit,
onCancel: () -> Unit,
) {
// TODO: Change the height, width and position according to the design
- Snackbar (
- modifier = Modifier.padding(horizontal = 80.dp).padding(top = 700.dp),
+ Snackbar(
+ modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp),
shape = EntryShape.FullMediumRoundedCorner,
containerColor = LocalAndroidColorScheme.current.colorBackground,
contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- ) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- ) {
+ action = {
TextButton(
- onClick = {onClick(true)},
+ onClick = { onClick(true) },
) {
- Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
+ Text(text = stringResource(R.string.snackbar_action))
}
+ },
+ dismissAction = {
IconButton(onClick = onCancel) {
Icon(
Icons.Filled.Close,
contentDescription = stringResource(
R.string.accessibility_close_button
- )
+ ),
+ tint = LocalAndroidColorScheme.current.colorAccentTertiary
)
}
- }
+ },
+ ) {
+ Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
}
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 3a2a738..60939b5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -67,7 +67,7 @@
val credentialTypeDisplayName: String,
val userName: String,
val displayName: String?,
- val icon: Drawable,
+ val icon: Drawable?,
val lastUsedTimeMillis: Long?,
) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)