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

Commit fe58cf93 authored by harinirajan's avatar harinirajan
Browse files

Single credential sign in with provider screen changes

Bug: 308861375
Test: Manual. See go/credential-selector-ui
Change-Id: Icb2c5e7fe8405ce78cc7cb00bd404deb5c6f2983
parent aa0f0364
Loading
Loading
Loading
Loading
+1 −1
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.
+146 −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.signInWithProvider

import android.graphics.drawable.Drawable
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.Column
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

/**
 * Screen that shows sign in with provider credential.
 *
 * @param credentialSelectorUiState The app bar view model.
 * @param screenIcon The view model corresponding to the home page.
 * @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
 * @param modifier styling for composable
 * @param viewModel ViewModel that updates ui state for this screen
 * @param navController handles navigation events from this screen
 */
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SignInWithProviderScreen(
    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
    screenIcon: Drawable?,
    columnState: ScalingLazyColumnState,
    modifier: Modifier = Modifier,
    viewModel: SignInWithProviderViewModel = hiltViewModel(),
    navController: NavHostController = rememberNavController(),
) {
    viewModel.initialize(credentialSelectorUiState.entry)

    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when (uiState) {
        UiState.CredentialScreen -> {
            SignInWithProviderScreen(
                credentialSelectorUiState.entry,
                screenIcon,
                columnState,
                modifier,
                viewModel
            )
        }

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

            SideEffect {
                (uiState as UiState.CredentialSelected).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 SignInWithProviderScreen(
    entry: CredentialEntryInfo,
    screenIcon: Drawable?,
    columnState: ScalingLazyColumnState,
    modifier: Modifier = Modifier,
    viewModel: SignInWithProviderViewModel,
) {
    SingleAccountScreen(
        headerContent = {
            SignInHeader(
                icon = screenIcon,
                title = stringResource(R.string.use_sign_in_with_provider_title,
                    entry.providerDisplayName),
            )
        },
        accountContent = {
            val displayName = entry.displayName
            if (displayName != null) {
                AccountRow(
                    primaryText = displayName,
                    secondaryText = entry.userName,
                    modifier = Modifier.padding(top = 10.dp),
                )
            } else {
                AccountRow(
                    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)
           }
       }
    }
}
 No newline at end of file
+81 −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.signInWithProvider

import android.content.Intent
import android.credentials.selection.ProviderPendingIntentResponse
import android.credentials.selection.UserSelectionDialogResult
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

/** ViewModel for [SignInWithProviderScreen].*/
@HiltViewModel
class SignInWithProviderViewModel @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() {
        // TODO(b/322797032) Implement navigation route for single credential screen to multiple
        // credentials
    }

    fun onInfoRetrieved(
        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)
    }
}