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

Commit e4603ddc authored by Harini Rajan's avatar Harini Rajan Committed by Android (Google) Code Review
Browse files

Merge "Multi credential folded screen changes" into main

parents aaedac7a 647ff9ab
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -33,4 +33,6 @@
  <string name="dialog_continue_button">Continue</string>
  <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] -->
  <string name="dialog_sign_in_options_button">Sign-in Options</string>
  <!-- Title for multiple credentials flattened screen. [CHAR LIMIT=NONE] -->
  <string name="choose_sign_in_title">Choose a sign in</string>
</resources>
 No newline at end of file
+29 −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

import androidx.activity.result.IntentSenderRequest

sealed class UiState {
    data object CredentialScreen : UiState()

    data class CredentialSelected(
        val intentSenderRequest: IntentSenderRequest?
    ) : UiState()

    data object Cancel : UiState()
}
+140 −0
Original line number Diff line number Diff line
/*
 * 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.
 * 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.multiple

import com.android.credentialmanager.ui.screens.UiState
import android.graphics.drawable.Drawable
import androidx.activity.compose.rememberLauncherForActivityResult
import com.android.credentialmanager.R
import androidx.compose.ui.res.stringResource
import androidx.compose.foundation.layout.fillMaxSize
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.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.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.CredentialsScreenChip
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnState

/**
 * Screen that shows multiple credentials to select from.
 *
 * @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
 * @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 MultiCredentialsFoldScreen(
    credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntry,
    screenIcon: Drawable?,
    columnState: ScalingLazyColumnState,
    modifier: Modifier = Modifier,
    viewModel: MultiCredentialsFoldViewModel = hiltViewModel(),
    navController: NavHostController = rememberNavController(),
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when (val state = uiState) {
        UiState.CredentialScreen -> {
            MultiCredentialsFoldScreen(
                state = credentialSelectorUiState,
                onSignInOptionsClicked = viewModel::onSignInOptionsClicked,
                onCredentialClicked = viewModel::onCredentialClicked,
                onCancelClicked = viewModel::onCancelClicked,
                screenIcon = screenIcon,
                columnState = columnState,
                modifier = modifier
            )
        }

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

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

        UiState.Cancel -> {
            navController.popBackStack()
        }
    }
}

@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MultiCredentialsFoldScreen(
    state: CredentialSelectorUiState.Get.MultipleEntry,
    onSignInOptionsClicked: () -> Unit,
    onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
    onCancelClicked: () -> Unit,
    screenIcon: Drawable?,
    columnState: ScalingLazyColumnState,
    modifier: Modifier,
) {
    ScalingLazyColumn(
        columnState = columnState,
        modifier = modifier.fillMaxSize(),
    ) {
        item {
            SignInHeader(
                icon = screenIcon,
                title = stringResource(R.string.choose_sign_in_title),
                modifier = Modifier
                    .padding(top = 6.dp),
            )
        }

        state.accounts.forEach {
            it.sortedCredentialEntryList.forEach { credential: CredentialEntryInfo ->
                item {
                    CredentialsScreenChip(
                        label = credential.userName,
                        onClick = { onCredentialClicked(credential) },
                        secondaryLabel = credential.credentialTypeDisplayName,
                        icon = credential.icon,
                    )
                }
            }
        }
        item { SignInOptionsChip(onSignInOptionsClicked) }
        item { DismissChip(onCancelClicked) }
    }
}
+75 −0
Original line number Diff line number Diff line
/*
 * 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.
 * 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.multiple

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

/** ViewModel for [MultiCredentialsFoldScreen].*/
@HiltViewModel
class MultiCredentialsFoldViewModel @Inject constructor(
    private val credentialManagerClient: CredentialManagerClient,
) : ViewModel() {

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

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

    fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
        this.entryInfo = entryInfo
        _uiState.value = UiState.CredentialSelected(
            intentSenderRequest = entryInfo.getIntentSenderRequest()
        )
    }

    fun onSignInOptionsClicked() {
        // TODO(b/322797032) Implement navigation route for single credential screen to multiple
        // credentials
    }

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

    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)
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ 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.android.credentialmanager.ui.screens.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState

Loading