Merge "Update get flow snackBar page"
This commit is contained in:
@@ -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] -->
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user