Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 222e2c4c authored by harinirajan's avatar harinirajan
Browse files

Single credential passkey screen changes

Bug: 322799831
Test: Manual. See go/credential-selector-ui
Change-Id: I3f03352595174c64ae294edca44208cba9f6eea2
parent 3e352e4e
Loading
Loading
Loading
Loading
+11 −9
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@ import com.google.android.horologist.compose.tools.WearPreview
@Composable
fun AccountRow(
    primaryText: String,
    secondaryText: String,
    secondaryText: String? = null,
    modifier: Modifier = Modifier,
) {
    Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
@@ -42,6 +42,7 @@ fun AccountRow(
            maxLines = 1,
            style = MaterialTheme.typography.title2
        )
        if (secondaryText != null) {
            Text(
                text = secondaryText,
                modifier = Modifier.padding(top = 7.dp),
@@ -52,6 +53,7 @@ fun AccountRow(
            )
        }
    }
}

@WearPreview
@Composable
+0 −22
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0N
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.credentialmanager.ui.model

data class PasskeyUiModel(
    val name: String,
    val email: String,
)
+84 −9
Original line number Diff line number Diff line
/*
 * Copyright 2023 The Android Open Source Project
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -18,44 +18,119 @@

package com.android.credentialmanager.ui.screens.single.passkey

import android.graphics.drawable.Drawable
import androidx.compose.foundation.layout.Column
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.R
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.AccountRow
import com.android.credentialmanager.ui.components.ContinueChip
import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
import com.android.credentialmanager.ui.screens.single.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState

@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasskeyScreen(
    name: String,
    email: String,
    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
    screenIcon: Drawable?,
    columnState: ScalingLazyColumnState,
    modifier: Modifier = Modifier,
    viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
    navController: NavHostController = rememberNavController(),
) {
    viewModel.initialize(credentialSelectorUiState.entry)

    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when (val state = uiState) {
        UiState.CredentialScreen -> {
            SinglePasskeyScreen(
                credentialSelectorUiState.entry,
                screenIcon,
                columnState,
                modifier,
                viewModel
            )
        }

        is UiState.CredentialSelected -> {
            val launcher = rememberLauncherForActivityResult(
                StartBalIntentSenderForResultContract()
            ) {
                viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
            }

            SideEffect {
                state.intentSenderRequest?.let {
                    launcher.launch(it)
                }
            }
        }

        UiState.Cancel -> {
            // TODO(b/322797032) add valid navigation path here for going back
            navController.popBackStack()
        }
    }
}

@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasskeyScreen(
    entry: CredentialEntryInfo,
    screenIcon: Drawable?,
    columnState: ScalingLazyColumnState,
    modifier: Modifier = Modifier,
    viewModel: SinglePasskeyScreenViewModel,
) {
    SingleAccountScreen(
        headerContent = {
            SignInHeader(
                icon = null,
                icon = screenIcon,
                title = stringResource(R.string.use_passkey_title),
            )
        },
        accountContent = {
            if (entry.displayName != null) {
                AccountRow(
                    primaryText = checkNotNull(entry.displayName),
                    secondaryText = entry.userName,
                    modifier = Modifier.padding(top = 10.dp),
                )
            } else {
                AccountRow(
                primaryText = name,
                secondaryText = email,
                    primaryText = entry.userName,
                    modifier = Modifier.padding(top = 10.dp),
                )
            }
        },
        columnState = columnState,
        modifier = modifier.padding(horizontal = 10.dp)
    ) {
        item {
            Column {
                ContinueChip(viewModel::onContinueClick)
                SignInOptionsChip(viewModel::onSignInOptionsClick)
                DismissChip(viewModel::onDismissClick)
            }
        }
    }
}
+79 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.credentialmanager.ui.screens.single.passkey

import android.content.Intent
import android.credentials.selection.UserSelectionDialogResult
import android.credentials.selection.ProviderPendingIntentResponse
import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
import com.android.credentialmanager.ktx.getIntentSenderRequest
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
import com.android.credentialmanager.model.get.CredentialEntryInfo
import dagger.hilt.android.lifecycle.HiltViewModel
import com.android.credentialmanager.ui.screens.single.UiState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject

@HiltViewModel
class SinglePasskeyScreenViewModel @Inject constructor(
    private val credentialManagerClient: CredentialManagerClient,
) : ViewModel() {

    private val _uiState =
        MutableStateFlow<UiState>(UiState.CredentialScreen)
    val uiState: StateFlow<UiState> = _uiState

    private lateinit var requestGet: Request.Get
    private lateinit var entryInfo: CredentialEntryInfo


    @MainThread
    fun initialize(entry: CredentialEntryInfo) {
        this.entryInfo = entry
    }

    fun onDismissClick() {
        _uiState.value = UiState.Cancel
    }

    fun onContinueClick() {
        _uiState.value = UiState.CredentialSelected(
            intentSenderRequest = entryInfo.getIntentSenderRequest()
        )
    }

    fun onSignInOptionsClick() {
    }

    fun onPasskeyInfoRetrieved(
        resultCode: Int? = null,
        resultData: Intent? = null,
    ) {
        val userSelectionDialogResult = UserSelectionDialogResult(
            requestGet.token,
            entryInfo.providerId,
            entryInfo.entryKey,
            entryInfo.entrySubkey,
            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
        )
        credentialManagerClient.sendResult(userSelectionDialogResult)
    }
}