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

Commit b7456308 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Defined new UI state and provide data within state." into main

parents c920be5e 3ef608f3
Loading
Loading
Loading
Loading
+19 −7
Original line number Diff line number Diff line
@@ -21,11 +21,14 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
import com.android.credentialmanager.model.get.ActionEntryInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.mappers.toGet
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject

@@ -33,15 +36,15 @@ import javax.inject.Inject
class CredentialSelectorViewModel @Inject constructor(
    private val credentialManagerClient: CredentialManagerClient,
) : ViewModel() {

    private val isPrimaryScreen = MutableStateFlow(false)
    val uiState: StateFlow<CredentialSelectorUiState> = credentialManagerClient.requests
        .map { request ->
        .combine(isPrimaryScreen) { request, isPrimary ->
            when (request) {
                null -> CredentialSelectorUiState.Idle
                is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
                is Request.Close -> CredentialSelectorUiState.Close
                is Request.Create -> CredentialSelectorUiState.Create
                is Request.Get -> request.toGet()
                is Request.Get -> request.toGet(isPrimary)
            }
        }
        .stateIn(
@@ -57,9 +60,18 @@ class CredentialSelectorViewModel @Inject constructor(

sealed class CredentialSelectorUiState {
    data object Idle : CredentialSelectorUiState()
    sealed class Get : CredentialSelectorUiState() {
        data object SingleProviderSinglePasskey : Get()
        data object SingleProviderSinglePassword : Get()
    sealed class Get() : CredentialSelectorUiState() {
        data class SingleEntry(val entry: CredentialEntryInfo) : Get()
        data class SingleEntryPerAccount(val sortedEntries: List<CredentialEntryInfo>) : Get()
        data class MultipleEntry(
            val accounts: List<PerUserNameEntries>,
            val actionEntryList: List<ActionEntryInfo>,
        ) : Get() {
            data class PerUserNameEntries(
                val userName: String,
                val sortedCredentialEntryList: List<CredentialEntryInfo>,
            )
        }

        // TODO: b/301206470 add the remaining states
    }
+3 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.ui.screens.LoadingScreen
import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
@@ -57,6 +58,7 @@ fun WearApp(

        scrollable(Screen.SinglePasswordScreen.route) {
            SinglePasswordScreen(
                state = viewModel.uiState.value as SingleEntry,
                columnState = it.columnState,
                onCloseApp = onCloseApp,
            )
@@ -100,7 +102,7 @@ private fun handleGetNavigation(
    onCloseApp: () -> Unit,
) {
    when (state) {
        is CredentialSelectorUiState.Get.SingleProviderSinglePassword -> {
        is SingleEntry -> {
            navController.navigateToSinglePasswordScreen()
        }

+35 −8
Original line number Diff line number Diff line
@@ -18,18 +18,45 @@ package com.android.credentialmanager.ui.mappers

import com.android.credentialmanager.model.Request
import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.CredentialEntryInfo

fun Request.Get.toGet(): CredentialSelectorUiState.Get {
fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get {
    // TODO: b/301206470 returning a hard coded state for MVP
    if (true) return CredentialSelectorUiState.Get.SingleProviderSinglePassword

    return if (providerInfos.size == 1) {
        if (providerInfos.first().credentialEntryList.size == 1) {
            CredentialSelectorUiState.Get.SingleProviderSinglePassword
    if (true) return CredentialSelectorUiState.Get.SingleEntry(
        providerInfos
            .flatMap { it.credentialEntryList }
            .first { it.credentialType == CredentialType.PASSWORD }
    )
    val accounts = providerInfos
        .flatMap { it.credentialEntryList }
        .groupBy { it.userName}
        .entries
        .toList()
    return if (isPrimary) {
        if (accounts.size == 1) {
            CredentialSelectorUiState.Get.SingleEntry(
                accounts[0].value.minWith(comparator)
            )
        } else {
            TODO() // b/301206470 - Implement other get flows
            CredentialSelectorUiState.Get.SingleEntryPerAccount(
                accounts.map { it.value.minWith(comparator) }.sortedWith(comparator)
            )
        }
    } else {
        TODO() // b/301206470 - Implement other get flows
        CredentialSelectorUiState.Get.MultipleEntry(
            accounts = accounts.map { PerUserNameEntries(
                it.key,
                it.value.sortedWith(comparator)
            )
            },
            actionEntryList = providerInfos.flatMap { it.actionEntryList },
        )
    }
}
val comparator = compareBy<CredentialEntryInfo> { entryInfo ->
    // Passkey type always go first
    entryInfo.credentialType.let{ if (it == CredentialType.PASSKEY) 0 else 1 }
}
    .thenByDescending{ it.lastUsedTimeMillis }
+3 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ 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 com.android.credentialmanager.R
import com.android.credentialmanager.TAG
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
@@ -44,12 +45,13 @@ import com.google.android.horologist.compose.tools.WearPreview

@Composable
fun SinglePasswordScreen(
    state: SingleEntry,
    columnState: ScalingLazyColumnState,
    onCloseApp: () -> Unit,
    modifier: Modifier = Modifier,
    viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
) {
    viewModel.initialize()
    viewModel.initialize(state.entry)

    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

+6 −28
Original line number Diff line number Diff line
@@ -19,12 +19,9 @@ package com.android.credentialmanager.ui.screens.single.password
import android.content.Intent
import android.credentials.ui.ProviderPendingIntentResponse
import android.credentials.ui.UserSelectionDialogResult
import android.util.Log
import androidx.activity.result.IntentSenderRequest
import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.credentialmanager.TAG
import com.android.credentialmanager.ktx.getIntentSenderRequest
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
@@ -33,7 +30,6 @@ import com.android.credentialmanager.ui.model.PasswordUiModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
@@ -51,33 +47,15 @@ class SinglePasswordScreenViewModel @Inject constructor(
    val uiState: StateFlow<SinglePasswordScreenUiState> = _uiState

    @MainThread
    fun initialize() {
    fun initialize(entryInfo: CredentialEntryInfo) {
        if (initializeCalled) return
        initializeCalled = true

        viewModelScope.launch {
            val request = credentialManagerClient.requests.value
            Log.d(TAG, "request: $request, client instance: $credentialManagerClient")

            if (request !is Request.Get) {
                _uiState.value = SinglePasswordScreenUiState.Error
            } else {
                requestGet = request

                if (requestGet.providerInfos.all { it.credentialEntryList.isEmpty() }) {
                    Log.d(TAG, "Empty passwordEntries")
                    _uiState.value = SinglePasswordScreenUiState.Error
                } else {
                    entryInfo = requestGet.providerInfos.first().credentialEntryList.first()
        _uiState.value = SinglePasswordScreenUiState.Loaded(
            PasswordUiModel(
                email = entryInfo.userName,
            )
        )
    }
            }
        }
    }

    fun onCancelClick() {
        _uiState.value = SinglePasswordScreenUiState.Cancel