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

Commit 3ef608f3 authored by shuanghao's avatar shuanghao
Browse files

Defined new UI state and provide data within state.

BUG: 301206470
Test: Manual.
Change-Id: Iaba8f717b68b74c4f05b3b188af4b754eb6df6ca
parent 2e11589a
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