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

Commit 3e352e4e authored by harinirajan's avatar harinirajan
Browse files

Single provider password screen changes

Test: Manual. See go/credential-selector-ui
Bug: 322797031
Change-Id: I3a955f8e5d86bdb4c316daee8009db55fa28cc23
parent 6892184b
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 * 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.
@@ -58,9 +58,9 @@ fun WearApp(

        scrollable(Screen.SinglePasswordScreen.route) {
            SinglePasswordScreen(
                state = viewModel.uiState.value as SingleEntry,
                credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
                screenIcon = null,
                columnState = it.columnState,
                onCloseApp = onCloseApp,
            )
        }
    }
+14 −4
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 * 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.0N
 *      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,
@@ -14,6 +14,16 @@
 * limitations under the License.
 */

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

data class PasswordUiModel(val email: String)
import androidx.activity.result.IntentSenderRequest

sealed class UiState {
    data object CredentialScreen : UiState()

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

    data object Cancel : UiState()
}
 No newline at end of file
+40 −32
Original line number Diff line number Diff line
@@ -18,8 +18,9 @@

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

import android.util.Log
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
@@ -29,47 +30,52 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.R
import com.android.credentialmanager.TAG
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.PasswordRow
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.model.PasswordUiModel
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
import com.android.credentialmanager.model.get.CredentialEntryInfo
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 SinglePasswordScreen(
    state: SingleEntry,
    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
    screenIcon: Drawable?,
    columnState: ScalingLazyColumnState,
    onCloseApp: () -> Unit,
    modifier: Modifier = Modifier,
    viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
    navController: NavHostController = rememberNavController(),
) {
    viewModel.initialize(state.entry)
    viewModel.initialize(credentialSelectorUiState.entry)

    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when (val state = uiState) {
        SinglePasswordScreenUiState.Idle -> {
            // TODO: b/301206470 implement latency version of the screen
        }

        is SinglePasswordScreenUiState.Loaded -> {
        UiState.CredentialScreen -> {
            SinglePasswordScreen(
                passwordUiModel = state.passwordUiModel,
                columnState = columnState,
                modifier = modifier
                credentialSelectorUiState.entry,
                screenIcon,
                columnState,
                modifier,
                viewModel
            )
        }

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

            SideEffect {
@@ -79,37 +85,32 @@ fun SinglePasswordScreen(
            }
        }

        SinglePasswordScreenUiState.Cancel -> {
            // TODO: b/301206470 implement navigation for when user taps cancel
        }

        SinglePasswordScreenUiState.Error -> {
            // TODO: b/301206470 implement navigation for when there is an error to load screen
        }

        SinglePasswordScreenUiState.Completed -> {
            Log.d(TAG, "Received signal to finish the activity.")
            onCloseApp()
        UiState.Cancel -> {
            // TODO(b/322797032) add valid navigation path here for going back
            navController.popBackStack()
        }
    }
}

@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasswordScreen(
    passwordUiModel: PasswordUiModel,
private fun SinglePasswordScreen(
    entry: CredentialEntryInfo,
    screenIcon: Drawable?,
    columnState: ScalingLazyColumnState,
    modifier: Modifier = Modifier,
    viewModel: SinglePasswordScreenViewModel,
) {
    SingleAccountScreen(
        headerContent = {
            SignInHeader(
                icon = null,
                icon = screenIcon,
                title = stringResource(R.string.use_password_title),
            )
        },
        accountContent = {
            PasswordRow(
                email = passwordUiModel.email,
                email = entry.userName,
                modifier = Modifier.padding(top = 10.dp),
            )
        },
@@ -117,6 +118,13 @@ fun SinglePasswordScreen(
        modifier = modifier.padding(horizontal = 10.dp)
    ) {
        item {
            Column {
                ContinueChip(viewModel::onContinueClick)
                SignInOptionsChip(viewModel::onSignInOptionsClick)
                DismissChip(viewModel::onDismissClick)
            }
        }
    }
}

+12 −31
Original line number Diff line number Diff line
@@ -17,16 +17,15 @@
package com.android.credentialmanager.ui.screens.single.password

import android.content.Intent
import android.credentials.selection.ProviderPendingIntentResponse
import android.credentials.selection.UserSelectionDialogResult
import androidx.activity.result.IntentSenderRequest
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 com.android.credentialmanager.ui.model.PasswordUiModel
import com.android.credentialmanager.ui.screens.single.UiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -37,36 +36,31 @@ class SinglePasswordScreenViewModel @Inject constructor(
    private val credentialManagerClient: CredentialManagerClient,
) : ViewModel() {

    private var initializeCalled = false

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

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

    @MainThread
    fun initialize(entryInfo: CredentialEntryInfo) {
        if (initializeCalled) return
        initializeCalled = true
        _uiState.value = SinglePasswordScreenUiState.Loaded(
            PasswordUiModel(
                email = entryInfo.userName,
            )
        )
        this.entryInfo = entryInfo
    }

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

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

    fun onSignInOptionsClick() {
    }

    fun onPasswordInfoRetrieved(
        resultCode: Int? = null,
        resultData: Intent? = null,
@@ -79,18 +73,5 @@ class SinglePasswordScreenViewModel @Inject constructor(
            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
        )
        credentialManagerClient.sendResult(userSelectionDialogResult)
        _uiState.value = SinglePasswordScreenUiState.Completed
    }
    }

sealed class SinglePasswordScreenUiState {
    data object Idle : SinglePasswordScreenUiState()
    data class Loaded(val passwordUiModel: PasswordUiModel) : SinglePasswordScreenUiState()
    data class PasswordSelected(
        val intentSenderRequest: IntentSenderRequest?
    ) : SinglePasswordScreenUiState()

    data object Cancel : SinglePasswordScreenUiState()
    data object Error : SinglePasswordScreenUiState()
    data object Completed : SinglePasswordScreenUiState()
}