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

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

Merge "Single provider password screen changes" into main

parents 93e9efd0 3e352e4e
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()
}