Merge "Update get flow snackBar page"

This commit is contained in:
Qinmei Du
2022-12-09 23:48:12 +00:00
committed by Android (Google) Code Review
6 changed files with 126 additions and 38 deletions

View File

@@ -80,6 +80,8 @@
<string name="close_sheet">"Close sheet"</string>
<!-- Spoken content description of the back arrow button. -->
<string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
<!-- Spoken content description of the close button. -->
<string name="accessibility_close_button">"Close the Credential Manager action suggestion appearing at the bottom of the screen"</string>
<!-- Strings for the get flow. -->
<!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->

View File

@@ -44,7 +44,6 @@ import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
@@ -124,11 +123,9 @@ class CredentialManagerRepo(
val providerEnabledList = GetFlowUtils.toProviderList(
// TODO: handle runtime cast error
providerEnabledList as List<GetCredentialProviderData>, context)
// TODO: covert from real requestInfo
val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("the app")
val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo)
return GetCredentialUiState(
providerEnabledList,
GetScreenState.PRIMARY_SELECTION,
requestDisplayInfo,
)
}

View File

@@ -88,6 +88,14 @@ class GetFlowUtils {
}
}
fun toRequestDisplayInfo(
requestInfo: RequestInfo,
): com.android.credentialmanager.getflow.RequestDisplayInfo {
return com.android.credentialmanager.getflow.RequestDisplayInfo(
appDomainName = requestInfo.appPackageName
)
}
/* From service data structure to UI credential entry list representation. */
private fun getCredentialOptionInfoList(

View File

@@ -32,15 +32,19 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
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.Snackbar
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
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@@ -65,6 +69,7 @@ import com.android.credentialmanager.common.ui.ContainerCard
import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@Composable
fun GetCredentialScreen(
@@ -75,39 +80,50 @@ fun GetCredentialScreen(
initialValue = ModalBottomSheetValue.Expanded,
skipHalfExpanded = true
)
ModalBottomSheetLayout(
sheetBackgroundColor = MaterialTheme.colorScheme.surface,
modifier = Modifier.background(Color.Transparent),
sheetState = state,
sheetContent = {
val uiState = viewModel.uiState
if (!uiState.hidden) {
when (uiState.currentScreenState) {
GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerDisplayInfo = uiState.providerDisplayInfo,
onEntrySelected = viewModel::onEntrySelected,
onCancel = viewModel::onCancel,
onMoreOptionSelected = viewModel::onMoreOptionSelected,
)
GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
providerInfoList = uiState.providerInfoList,
providerDisplayInfo = uiState.providerDisplayInfo,
onEntrySelected = viewModel::onEntrySelected,
onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
)
val uiState = viewModel.uiState
if (uiState.currentScreenState != GetScreenState.REMOTE_ONLY) {
ModalBottomSheetLayout(
sheetBackgroundColor = MaterialTheme.colorScheme.surface,
modifier = Modifier.background(Color.Transparent),
sheetState = state,
sheetContent = {
// TODO: hide UI at top level
if (!uiState.hidden) {
if (uiState.currentScreenState == GetScreenState.PRIMARY_SELECTION) {
PrimarySelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerDisplayInfo = uiState.providerDisplayInfo,
onEntrySelected = viewModel::onEntrySelected,
onCancel = viewModel::onCancel,
onMoreOptionSelected = viewModel::onMoreOptionSelected,
)
} else {
AllSignInOptionCard(
providerInfoList = uiState.providerInfoList,
providerDisplayInfo = uiState.providerDisplayInfo,
onEntrySelected = viewModel::onEntrySelected,
onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
onCancel = viewModel::onCancel,
isNoAccount = uiState.isNoAccount,
)
}
} else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
viewModel.launchProviderUi(providerActivityLauncher)
}
} else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
viewModel.launchProviderUi(providerActivityLauncher)
},
scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
sheetShape = EntryShape.TopRoundedCorner,
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
viewModel.onCancel()
}
},
scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
sheetShape = EntryShape.TopRoundedCorner,
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
viewModel.onCancel()
}
} else {
SnackBarScreen(
onClick = viewModel::onMoreOptionOnSnackBarSelected,
onCancel = viewModel::onCancel,
)
}
}
@@ -195,6 +211,8 @@ fun AllSignInOptionCard(
providerDisplayInfo: ProviderDisplayInfo,
onEntrySelected: (EntryInfo) -> Unit,
onBackButtonClicked: () -> Unit,
onCancel: () -> Unit,
isNoAccount: Boolean,
) {
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
@@ -212,7 +230,7 @@ fun AllSignInOptionCard(
)
},
navigationIcon = {
IconButton(onClick = onBackButtonClicked) {
IconButton(onClick = if (isNoAccount) onCancel else onBackButtonClicked) {
Icon(
Icons.Filled.ArrowBack,
contentDescription = stringResource(
@@ -518,3 +536,37 @@ fun SignInAnotherWayRow(onSelect: () -> Unit) {
}
)
}
@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),
shape = EntryShape.FullMediumRoundedCorner,
containerColor = LocalAndroidColorScheme.current.colorBackground,
contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
TextButton(
onClick = {onClick(true)},
) {
Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
}
IconButton(onClick = onCancel) {
Icon(
Icons.Filled.Close,
contentDescription = stringResource(
R.string.accessibility_close_button
)
)
}
}
}
}

View File

@@ -36,12 +36,13 @@ import com.android.internal.util.Preconditions
data class GetCredentialUiState(
val providerInfoList: List<ProviderInfo>,
val currentScreenState: GetScreenState,
val requestDisplayInfo: RequestDisplayInfo,
val currentScreenState: GetScreenState = toGetScreenState(providerInfoList),
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
val selectedEntry: EntryInfo? = null,
val hidden: Boolean = false,
val providerActivityPending: Boolean = false,
val isNoAccount: Boolean = false,
)
class GetCredentialViewModel(
@@ -127,6 +128,14 @@ class GetCredentialViewModel(
)
}
fun onMoreOptionOnSnackBarSelected(isNoAccount: Boolean) {
Log.d("Account Selector", "More Option on snackBar selected")
uiState = uiState.copy(
currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS,
isNoAccount = isNoAccount,
)
}
fun onBackToPrimarySelectionScreen() {
uiState = uiState.copy(
currentScreenState = GetScreenState.PRIMARY_SELECTION
@@ -192,6 +201,24 @@ private fun toProviderDisplayInfo(
)
}
private fun toGetScreenState(
providerInfoList: List<ProviderInfo>
): GetScreenState {
var noLocalAccount = true
var remoteInfo: RemoteEntryInfo? = null
providerInfoList.forEach{providerInfo -> if (
providerInfo.credentialEntryList.isNotEmpty() || providerInfo.authenticationEntry != null
) { noLocalAccount = false }
// TODO: handle the error situation that if multiple remoteInfos exists
if (providerInfo.remoteEntry != null) {
remoteInfo = providerInfo.remoteEntry
}
}
return if (noLocalAccount && remoteInfo != null)
GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
}
internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
// First order by last used timestamp

View File

@@ -120,4 +120,6 @@ enum class GetScreenState {
PRIMARY_SELECTION,
/** The secondary credential selection page, where all sign-in options are listed. */
ALL_SIGN_IN_OPTIONS,
/** The snackbar only page when there's no account but only a remoteEntry. */
REMOTE_ONLY,
}