Merge "UX impovements: removed navigation & smoothen dialog transition."
This commit is contained in:
@@ -26,7 +26,6 @@ android_app {
|
||||
"androidx.lifecycle_lifecycle-livedata",
|
||||
"androidx.lifecycle_lifecycle-runtime-ktx",
|
||||
"androidx.lifecycle_lifecycle-viewmodel-compose",
|
||||
"androidx.navigation_navigation-compose",
|
||||
"androidx.recyclerview_recyclerview",
|
||||
],
|
||||
|
||||
|
||||
@@ -2,28 +2,86 @@ package com.android.credentialmanager
|
||||
|
||||
import android.content.Context
|
||||
import com.android.credentialmanager.createflow.CreateOptionInfo
|
||||
import com.android.credentialmanager.createflow.CreatePasskeyUiState
|
||||
import com.android.credentialmanager.createflow.CreateScreenState
|
||||
import com.android.credentialmanager.createflow.ProviderInfo
|
||||
import com.android.credentialmanager.createflow.ProviderList
|
||||
import com.android.credentialmanager.getflow.CredentialOptionInfo
|
||||
import com.android.credentialmanager.getflow.GetCredentialUiState
|
||||
import com.android.credentialmanager.getflow.GetScreenState
|
||||
|
||||
// Consider repo per screen, similar to view model?
|
||||
class CredentialManagerRepo(
|
||||
private val context: Context
|
||||
) {
|
||||
fun getCredentialProviderList(): List<com.android.credentialmanager.getflow.ProviderInfo> {
|
||||
private fun getCredentialProviderList():
|
||||
List<com.android.credentialmanager.getflow.ProviderInfo> {
|
||||
return listOf(
|
||||
com.android.credentialmanager.getflow.ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Google Password Manager",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
credentialOptions = listOf(
|
||||
CredentialOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@gmail.com",
|
||||
id = "id-1",
|
||||
),
|
||||
CredentialOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett Work",
|
||||
subtitle = "elisa.beckett.work@google.com",
|
||||
id = "id-2",
|
||||
),
|
||||
)
|
||||
),
|
||||
com.android.credentialmanager.getflow.ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Lastpass",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
credentialOptions = listOf(
|
||||
CredentialOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@lastpass.com",
|
||||
id = "id-1",
|
||||
),
|
||||
)
|
||||
),
|
||||
com.android.credentialmanager.getflow.ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Dashlane",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
credentialOptions = listOf(
|
||||
CredentialOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@dashlane.com",
|
||||
id = "id-1",
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createCredentialProviderList(): List<ProviderInfo> {
|
||||
return listOf(
|
||||
com.android.credentialmanager.getflow.ProviderInfo(
|
||||
ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Google Password Manager",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
credentialOptions = listOf(
|
||||
CredentialOptionInfo(
|
||||
createOptions = listOf(
|
||||
CreateOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@gmail.com",
|
||||
id = "id-1",
|
||||
),
|
||||
CredentialOptionInfo(
|
||||
CreateOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett Work",
|
||||
subtitle = "elisa.beckett.work@google.com",
|
||||
@@ -31,13 +89,13 @@ class CredentialManagerRepo(
|
||||
),
|
||||
)
|
||||
),
|
||||
com.android.credentialmanager.getflow.ProviderInfo(
|
||||
ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Lastpass",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
credentialOptions = listOf(
|
||||
CredentialOptionInfo(
|
||||
createOptions = listOf(
|
||||
CreateOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@lastpass.com",
|
||||
@@ -45,13 +103,13 @@ class CredentialManagerRepo(
|
||||
),
|
||||
)
|
||||
),
|
||||
com.android.credentialmanager.getflow.ProviderInfo(
|
||||
ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Dashlane",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
credentialOptions = listOf(
|
||||
CredentialOptionInfo(
|
||||
createOptions = listOf(
|
||||
CreateOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@dashlane.com",
|
||||
@@ -62,58 +120,20 @@ class CredentialManagerRepo(
|
||||
)
|
||||
}
|
||||
|
||||
fun createCredentialProviderList(): ProviderList {
|
||||
return ProviderList(
|
||||
listOf(
|
||||
ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Google Password Manager",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
createOptions = listOf(
|
||||
CreateOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@gmail.com",
|
||||
id = "id-1",
|
||||
),
|
||||
CreateOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett Work",
|
||||
subtitle = "elisa.beckett.work@google.com",
|
||||
id = "id-2",
|
||||
),
|
||||
)
|
||||
),
|
||||
ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Lastpass",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
createOptions = listOf(
|
||||
CreateOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@lastpass.com",
|
||||
id = "id-1",
|
||||
),
|
||||
)
|
||||
),
|
||||
ProviderInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
name = "Dashlane",
|
||||
appDomainName = "tribank.us",
|
||||
credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
createOptions = listOf(
|
||||
CreateOptionInfo(
|
||||
icon = context.getDrawable(R.drawable.ic_passkey)!!,
|
||||
title = "Elisa Backett",
|
||||
subtitle = "elisa.beckett@dashlane.com",
|
||||
id = "id-1",
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
fun getCredentialInitialUiState(): GetCredentialUiState {
|
||||
val providerList = getCredentialProviderList()
|
||||
return GetCredentialUiState(
|
||||
providerList,
|
||||
GetScreenState.CREDENTIAL_SELECTION,
|
||||
providerList.first()
|
||||
)
|
||||
}
|
||||
|
||||
fun createPasskeyInitialUiState(): CreatePasskeyUiState {
|
||||
val providerList = createCredentialProviderList()
|
||||
return CreatePasskeyUiState(
|
||||
providers = providerList,
|
||||
currentScreenState = CreateScreenState.PASSKEY_INTRO,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
package com.android.credentialmanager
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.android.credentialmanager.createflow.CreatePasskeyViewModel
|
||||
import com.android.credentialmanager.createflow.createPasskeyGraph
|
||||
import com.android.credentialmanager.getflow.GetCredentialViewModel
|
||||
import com.android.credentialmanager.getflow.getCredentialsGraph
|
||||
import com.android.credentialmanager.common.DialogType
|
||||
import com.android.credentialmanager.createflow.CreatePasskeyScreen
|
||||
import com.android.credentialmanager.getflow.GetCredentialScreen
|
||||
import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@@ -22,42 +18,35 @@ class CredentialSelectorActivity : ComponentActivity() {
|
||||
CredentialManagerRepo.setup(this)
|
||||
val startDestination = intent.extras?.getString(
|
||||
"start_destination",
|
||||
"getCredentials"
|
||||
) ?: "getCredentials"
|
||||
"CREATE_PASSKEY"
|
||||
) ?: "CREATE_PASSKEY"
|
||||
|
||||
setContent {
|
||||
CredentialSelectorTheme {
|
||||
AppNavHost(
|
||||
startDestination = startDestination,
|
||||
onCancel = {this.finish()}
|
||||
)
|
||||
CredentialManagerBottomSheet(startDestination)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@Composable
|
||||
fun AppNavHost(
|
||||
modifier: Modifier = Modifier,
|
||||
navController: NavHostController = rememberNavController(),
|
||||
startDestination: String,
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
NavHost(
|
||||
modifier = modifier,
|
||||
navController = navController,
|
||||
startDestination = startDestination
|
||||
) {
|
||||
createPasskeyGraph(
|
||||
navController = navController,
|
||||
viewModel = CreatePasskeyViewModel(CredentialManagerRepo.repo),
|
||||
onCancel = onCancel
|
||||
)
|
||||
getCredentialsGraph(
|
||||
navController = navController,
|
||||
viewModel = GetCredentialViewModel(CredentialManagerRepo.repo),
|
||||
onCancel = onCancel
|
||||
)
|
||||
fun CredentialManagerBottomSheet(operationType: String) {
|
||||
val dialogType = DialogType.toDialogType(operationType)
|
||||
when (dialogType) {
|
||||
DialogType.CREATE_PASSKEY -> {
|
||||
CreatePasskeyScreen(cancelActivity = onCancel)
|
||||
}
|
||||
DialogType.GET_CREDENTIALS -> {
|
||||
GetCredentialScreen(cancelActivity = onCancel)
|
||||
}
|
||||
else -> {
|
||||
Log.w("AccountSelector", "Unknown type, not rendering any UI")
|
||||
this.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val onCancel = {
|
||||
this@CredentialSelectorActivity.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.android.credentialmanager.common
|
||||
|
||||
enum class DialogType {
|
||||
CREATE_PASSKEY,
|
||||
GET_CREDENTIALS,
|
||||
CREATE_PASSWORD,
|
||||
UNKNOWN;
|
||||
|
||||
companion object {
|
||||
fun toDialogType(value: String): DialogType {
|
||||
return try {
|
||||
valueOf(value)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,16 @@ data class ProviderInfo(
|
||||
val createOptions: List<CreateOptionInfo>,
|
||||
)
|
||||
|
||||
data class ProviderList(
|
||||
val providers: List<ProviderInfo>,
|
||||
)
|
||||
|
||||
data class CreateOptionInfo(
|
||||
val icon: Drawable,
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val id: String,
|
||||
)
|
||||
|
||||
/** The name of the current screen. */
|
||||
enum class CreateScreenState {
|
||||
PASSKEY_INTRO,
|
||||
PROVIDER_SELECTION,
|
||||
CREATION_OPTION_SELECTION,
|
||||
}
|
||||
|
||||
@@ -25,22 +25,15 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import androidx.navigation.navigation
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.android.credentialmanager.R
|
||||
import com.android.credentialmanager.ui.theme.Grey100
|
||||
import com.android.credentialmanager.ui.theme.Shapes
|
||||
@@ -49,54 +42,11 @@ import com.android.credentialmanager.ui.theme.lightBackgroundColor
|
||||
import com.android.credentialmanager.ui.theme.lightColorAccentSecondary
|
||||
import com.android.credentialmanager.ui.theme.lightSurface1
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
fun NavGraphBuilder.createPasskeyGraph(
|
||||
navController: NavController,
|
||||
viewModel: CreatePasskeyViewModel,
|
||||
onCancel: () -> Unit,
|
||||
startDestination: String = "intro", // TODO: get this from view model
|
||||
) {
|
||||
navigation(startDestination = startDestination, route = "createPasskey") {
|
||||
composable("intro") {
|
||||
CreatePasskeyIntroDialog(
|
||||
onCancel = onCancel,
|
||||
onConfirm = {viewModel.onConfirm(navController)}
|
||||
)
|
||||
}
|
||||
composable("providerSelection") {
|
||||
ProviderSelectionDialog(
|
||||
providerList = viewModel.uiState.collectAsState().value.providerList,
|
||||
onProviderSelected = {viewModel.onProviderSelected(it, navController)},
|
||||
onCancel = onCancel
|
||||
)
|
||||
}
|
||||
composable(
|
||||
"createCredentialSelection/{providerName}",
|
||||
arguments = listOf(navArgument("providerName") {type = NavType.StringType})
|
||||
) {
|
||||
val arguments = it.arguments
|
||||
if (arguments == null) {
|
||||
throw java.lang.IllegalStateException("createCredentialSelection without a provider name")
|
||||
}
|
||||
CreationSelectionDialog(
|
||||
providerInfo = viewModel.getProviderInfoByName(arguments.getString("providerName")!!),
|
||||
onOptionSelected = {viewModel.onCreateOptionSelected(it)},
|
||||
onCancel = onCancel,
|
||||
multiProvider = viewModel.uiState.collectAsState().value.providerList.providers.size > 1,
|
||||
onMoreOptionSelected = {viewModel.onMoreOptionSelected(navController)},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BEGIN INTRO CONTENT
|
||||
*/
|
||||
@ExperimentalMaterialApi
|
||||
@Composable
|
||||
fun CreatePasskeyIntroDialog(
|
||||
onCancel: () -> Unit = {},
|
||||
onConfirm: () -> Unit = {},
|
||||
fun CreatePasskeyScreen(
|
||||
viewModel: CreatePasskeyViewModel = viewModel(),
|
||||
cancelActivity: () -> Unit,
|
||||
) {
|
||||
val state = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Expanded,
|
||||
@@ -105,9 +55,25 @@ fun CreatePasskeyIntroDialog(
|
||||
ModalBottomSheetLayout(
|
||||
sheetState = state,
|
||||
sheetContent = {
|
||||
ConfirmationCard(
|
||||
onCancel = onCancel, onConfirm = onConfirm
|
||||
)
|
||||
val uiState = viewModel.uiState
|
||||
when (uiState.currentScreenState) {
|
||||
CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
|
||||
onConfirm = {viewModel.onConfirmIntro()},
|
||||
onCancel = cancelActivity,
|
||||
)
|
||||
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
|
||||
providerList = uiState.providers,
|
||||
onCancel = cancelActivity,
|
||||
onProviderSelected = {viewModel.onProviderSelected(it)}
|
||||
)
|
||||
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
|
||||
providerInfo = uiState.selectedProvider!!,
|
||||
onOptionSelected = {viewModel.onCreateOptionSelected(it)},
|
||||
onCancel = cancelActivity,
|
||||
multiProvider = uiState.providers.size > 1,
|
||||
onMoreOptionSelected = {viewModel.onMoreOptionSelected()}
|
||||
)
|
||||
}
|
||||
},
|
||||
scrimColor = Color.Transparent,
|
||||
sheetShape = Shapes.medium,
|
||||
@@ -115,7 +81,7 @@ fun CreatePasskeyIntroDialog(
|
||||
LaunchedEffect(state.currentValue) {
|
||||
when (state.currentValue) {
|
||||
ModalBottomSheetValue.Hidden -> {
|
||||
onCancel()
|
||||
cancelActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,49 +144,10 @@ fun ConfirmationCard(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* END INTRO CONTENT
|
||||
*/
|
||||
|
||||
/**
|
||||
* BEGIN PROVIDER SELECTION CONTENT
|
||||
*/
|
||||
@ExperimentalMaterialApi
|
||||
@Composable
|
||||
fun ProviderSelectionDialog(
|
||||
providerList: ProviderList,
|
||||
onProviderSelected: (String) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
val state = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Expanded,
|
||||
skipHalfExpanded = true
|
||||
)
|
||||
ModalBottomSheetLayout(
|
||||
sheetState = state,
|
||||
sheetContent = {
|
||||
ProviderSelectionCard(
|
||||
providerList = providerList,
|
||||
onCancel = onCancel,
|
||||
onProviderSelected = onProviderSelected
|
||||
)
|
||||
},
|
||||
scrimColor = Color.Transparent,
|
||||
sheetShape = Shapes.medium,
|
||||
) {}
|
||||
LaunchedEffect(state.currentValue) {
|
||||
when (state.currentValue) {
|
||||
ModalBottomSheetValue.Hidden -> {
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@Composable
|
||||
fun ProviderSelectionCard(
|
||||
providerList: ProviderList,
|
||||
providerList: List<ProviderInfo>,
|
||||
onProviderSelected: (String) -> Unit,
|
||||
onCancel: () -> Unit
|
||||
) {
|
||||
@@ -251,7 +178,7 @@ fun ProviderSelectionCard(
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
providerList.providers.forEach {
|
||||
providerList.forEach {
|
||||
item {
|
||||
ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
|
||||
}
|
||||
@@ -304,14 +231,6 @@ fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* END PROVIDER SELECTION CONTENT
|
||||
*/
|
||||
|
||||
/**
|
||||
* BEGIN COMMON COMPONENTS
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun CancelButton(text: String, onclick: () -> Unit) {
|
||||
val colors = ButtonDefaults.buttonColors(
|
||||
@@ -352,45 +271,6 @@ fun NavigationButton(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BEGIN CREATE OPTION SELECTION CONTENT
|
||||
*/
|
||||
@ExperimentalMaterialApi
|
||||
@Composable
|
||||
fun CreationSelectionDialog(
|
||||
providerInfo: ProviderInfo,
|
||||
onOptionSelected: (String) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
multiProvider: Boolean,
|
||||
onMoreOptionSelected: () -> Unit,
|
||||
) {
|
||||
val state = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Expanded,
|
||||
skipHalfExpanded = true
|
||||
)
|
||||
ModalBottomSheetLayout(
|
||||
sheetState = state,
|
||||
sheetContent = {
|
||||
CreationSelectionCard(
|
||||
providerInfo = providerInfo,
|
||||
onCancel = onCancel,
|
||||
onOptionSelected = onOptionSelected,
|
||||
multiProvider = multiProvider,
|
||||
onMoreOptionSelected = onMoreOptionSelected,
|
||||
)
|
||||
},
|
||||
scrimColor = Color.Transparent,
|
||||
sheetShape = Shapes.medium,
|
||||
) {}
|
||||
LaunchedEffect(state.currentValue) {
|
||||
when (state.currentValue) {
|
||||
ModalBottomSheetValue.Hidden -> {
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@Composable
|
||||
fun CreationSelectionCard(
|
||||
@@ -516,26 +396,3 @@ fun MoreOptionRow(onSelect: () -> Unit) {
|
||||
)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* END CREATE OPTION SELECTION CONTENT
|
||||
*/
|
||||
|
||||
/**
|
||||
* END COMMON COMPONENTS
|
||||
*/
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun CreatePasskeyEntryScreenPreview() {
|
||||
// val providers = ProviderList(
|
||||
// listOf(
|
||||
// ProviderInfo(null),
|
||||
// ProviderInfo(null, "Dashlane"),
|
||||
// ProviderInfo(null, "LastPass")
|
||||
// )
|
||||
// )
|
||||
// TatiAccountSelectorTheme {
|
||||
// ConfirmationCard({}, {})
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,38 +1,45 @@
|
||||
package com.android.credentialmanager.createflow
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.android.credentialmanager.CredentialManagerRepo
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
data class CreatePasskeyUiState(
|
||||
val providerList: ProviderList,
|
||||
val providers: List<ProviderInfo>,
|
||||
val currentScreenState: CreateScreenState,
|
||||
val selectedProvider: ProviderInfo? = null,
|
||||
)
|
||||
|
||||
class CreatePasskeyViewModel(
|
||||
credManRepo: CredentialManagerRepo
|
||||
credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(
|
||||
CreatePasskeyUiState(credManRepo.createCredentialProviderList())
|
||||
)
|
||||
val uiState: StateFlow<CreatePasskeyUiState> = _uiState.asStateFlow()
|
||||
var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState())
|
||||
private set
|
||||
|
||||
fun onConfirm(navController: NavController) {
|
||||
if (uiState.value.providerList.providers.size > 1) {
|
||||
navController.navigate("providerSelection")
|
||||
} else if (uiState.value.providerList.providers.size == 1) {
|
||||
onProviderSelected(uiState.value.providerList.providers[0].name, navController)
|
||||
fun onConfirmIntro() {
|
||||
if (uiState.providers.size > 1) {
|
||||
uiState = uiState.copy(
|
||||
currentScreenState = CreateScreenState.PROVIDER_SELECTION
|
||||
)
|
||||
} else if (uiState.providers.size == 1){
|
||||
uiState = uiState.copy(
|
||||
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
|
||||
selectedProvider = uiState.providers.first()
|
||||
)
|
||||
} else {
|
||||
throw java.lang.IllegalStateException("Empty provider list.")
|
||||
}
|
||||
}
|
||||
|
||||
fun onProviderSelected(providerName: String, navController: NavController) {
|
||||
return navController.navigate("createCredentialSelection/$providerName")
|
||||
fun onProviderSelected(providerName: String) {
|
||||
uiState = uiState.copy(
|
||||
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
|
||||
selectedProvider = getProviderInfoByName(providerName)
|
||||
)
|
||||
}
|
||||
|
||||
fun onCreateOptionSelected(createOptionId: String) {
|
||||
@@ -40,12 +47,12 @@ class CreatePasskeyViewModel(
|
||||
}
|
||||
|
||||
fun getProviderInfoByName(providerName: String): ProviderInfo {
|
||||
return uiState.value.providerList.providers.single {
|
||||
return uiState.providers.single {
|
||||
it.name.equals(providerName)
|
||||
}
|
||||
}
|
||||
|
||||
fun onMoreOptionSelected(navController: NavController) {
|
||||
navController.navigate("moreOption")
|
||||
fun onMoreOptionSelected() {
|
||||
Log.d("Account Selector", "On more option selected")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -29,10 +28,7 @@ import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navigation
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.android.credentialmanager.R
|
||||
import com.android.credentialmanager.createflow.CancelButton
|
||||
import com.android.credentialmanager.ui.theme.Grey100
|
||||
@@ -40,37 +36,11 @@ import com.android.credentialmanager.ui.theme.Shapes
|
||||
import com.android.credentialmanager.ui.theme.Typography
|
||||
import com.android.credentialmanager.ui.theme.lightBackgroundColor
|
||||
|
||||
@ExperimentalMaterialApi
|
||||
fun NavGraphBuilder.getCredentialsGraph(
|
||||
navController: NavController,
|
||||
viewModel: GetCredentialViewModel,
|
||||
onCancel: () -> Unit,
|
||||
startDestination: String = "credentialSelection", // TODO: get this from view model
|
||||
) {
|
||||
navigation(startDestination = startDestination, route = "getCredentials") {
|
||||
composable("credentialSelection") {
|
||||
CredentialSelectionDialog(
|
||||
providerInfo = viewModel.getDefaultProviderInfo(),
|
||||
onOptionSelected = {viewModel.onCredentailSelected(it, navController)},
|
||||
onCancel = onCancel,
|
||||
multiProvider = viewModel.uiState.collectAsState().value.providers.size > 1,
|
||||
onMoreOptionSelected = {viewModel.onMoreOptionSelected(navController)}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BEGIN CREATE OPTION SELECTION CONTENT
|
||||
*/
|
||||
@ExperimentalMaterialApi
|
||||
@Composable
|
||||
fun CredentialSelectionDialog(
|
||||
providerInfo: ProviderInfo,
|
||||
onOptionSelected: (String) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
multiProvider: Boolean,
|
||||
onMoreOptionSelected: () -> Unit,
|
||||
fun GetCredentialScreen(
|
||||
viewModel: GetCredentialViewModel = viewModel(),
|
||||
cancelActivity: () -> Unit,
|
||||
) {
|
||||
val state = rememberModalBottomSheetState(
|
||||
initialValue = ModalBottomSheetValue.Expanded,
|
||||
@@ -79,13 +49,16 @@ fun CredentialSelectionDialog(
|
||||
ModalBottomSheetLayout(
|
||||
sheetState = state,
|
||||
sheetContent = {
|
||||
CredentialSelectionCard(
|
||||
providerInfo = providerInfo,
|
||||
onCancel = onCancel,
|
||||
onOptionSelected = onOptionSelected,
|
||||
multiProvider = multiProvider,
|
||||
onMoreOptionSelected = onMoreOptionSelected,
|
||||
)
|
||||
val uiState = viewModel.uiState
|
||||
when (uiState.currentScreenState) {
|
||||
GetScreenState.CREDENTIAL_SELECTION -> CredentialSelectionCard(
|
||||
providerInfo = uiState.selectedProvider!!,
|
||||
onCancel = cancelActivity,
|
||||
onOptionSelected = {viewModel.onCredentailSelected(it)},
|
||||
multiProvider = uiState.providers.size > 1,
|
||||
onMoreOptionSelected = {viewModel.onMoreOptionSelected()},
|
||||
)
|
||||
}
|
||||
},
|
||||
scrimColor = Color.Transparent,
|
||||
sheetShape = Shapes.medium,
|
||||
@@ -93,7 +66,7 @@ fun CredentialSelectionDialog(
|
||||
LaunchedEffect(state.currentValue) {
|
||||
when (state.currentValue) {
|
||||
ModalBottomSheetValue.Hidden -> {
|
||||
onCancel()
|
||||
cancelActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +94,9 @@ fun CredentialSelectionCard(
|
||||
Text(
|
||||
text = stringResource(R.string.choose_sign_in_title),
|
||||
style = Typography.subtitle1,
|
||||
modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
|
||||
modifier = Modifier
|
||||
.padding(all = 24.dp)
|
||||
.align(alignment = Alignment.CenterHorizontally)
|
||||
)
|
||||
Text(
|
||||
text = providerInfo.appDomainName,
|
||||
@@ -184,7 +159,6 @@ fun CredentialOptionRow(
|
||||
leadingIcon = {
|
||||
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
|
||||
bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
|
||||
// painter = painterResource(R.drawable.ic_passkey),
|
||||
// TODO: add description.
|
||||
contentDescription = "")
|
||||
},
|
||||
@@ -227,6 +201,3 @@ fun MoreOptionRow(onSelect: () -> Unit) {
|
||||
)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* END CREATE OPTION SELECTION CONTENT
|
||||
*/
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
package com.android.credentialmanager.getflow
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.android.credentialmanager.CredentialManagerRepo
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
data class GetCredentialUiState(
|
||||
val providers: List<ProviderInfo>
|
||||
val providers: List<ProviderInfo>,
|
||||
val currentScreenState: GetScreenState,
|
||||
val selectedProvider: ProviderInfo? = null,
|
||||
)
|
||||
|
||||
class GetCredentialViewModel(
|
||||
credManRepo: CredentialManagerRepo
|
||||
credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(
|
||||
GetCredentialUiState(credManRepo.getCredentialProviderList())
|
||||
)
|
||||
val uiState: StateFlow<GetCredentialUiState> = _uiState.asStateFlow()
|
||||
var uiState by mutableStateOf(credManRepo.getCredentialInitialUiState())
|
||||
private set
|
||||
|
||||
fun getDefaultProviderInfo(): ProviderInfo {
|
||||
// TODO: correctly get the default provider.
|
||||
return uiState.value.providers.first()
|
||||
}
|
||||
|
||||
fun onCredentailSelected(credentialId: String, navController: NavController) {
|
||||
fun onCredentailSelected(credentialId: String) {
|
||||
Log.d("Account Selector", "credential selected: $credentialId")
|
||||
}
|
||||
|
||||
fun onMoreOptionSelected(navController: NavController) {
|
||||
fun onMoreOptionSelected() {
|
||||
Log.d("Account Selector", "More Option selected")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,3 +16,8 @@ data class CredentialOptionInfo(
|
||||
val subtitle: String,
|
||||
val id: String,
|
||||
)
|
||||
|
||||
/** The name of the current screen. */
|
||||
enum class GetScreenState {
|
||||
CREDENTIAL_SELECTION,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user