Merge "Defined new UI state and provide data within state." into main
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 2a7e9e1..59e6142 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -21,11 +21,14 @@
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 @@
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 @@
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
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 7e0ea30..790f5b1 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -26,6 +26,7 @@
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 @@
scrollable(Screen.SinglePasswordScreen.route) {
SinglePasswordScreen(
+ state = viewModel.uiState.value as SingleEntry,
columnState = it.columnState,
onCloseApp = onCloseApp,
)
@@ -100,7 +102,7 @@
onCloseApp: () -> Unit,
) {
when (state) {
- is CredentialSelectorUiState.Get.SingleProviderSinglePassword -> {
+ is SingleEntry -> {
navController.navigateToSinglePasswordScreen()
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 14b992a..44a838d 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -18,18 +18,45 @@
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 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index b64f581..9f971ae 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -29,6 +29,7 @@
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 @@
@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()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index 26bee1f..4f9fc46 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -19,12 +19,9 @@
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 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,32 +47,14 @@
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,
- )
- )
- }
- }
- }
+ _uiState.value = SinglePasswordScreenUiState.Loaded(
+ PasswordUiModel(
+ email = entryInfo.userName,
+ )
+ )
}
fun onCancelClick() {