Loading packages/CredentialManager/wear/res/values/strings.xml +2 −0 Original line number Original line Diff line number Diff line Loading @@ -33,4 +33,6 @@ <string name="dialog_continue_button">Continue</string> <string name="dialog_continue_button">Continue</string> <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] --> <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] --> <string name="dialog_sign_in_options_button">Sign-in Options</string> <string name="dialog_sign_in_options_button">Sign-in Options</string> <!-- Title for multiple credentials flattened screen. [CHAR LIMIT=NONE] --> <string name="choose_sign_in_title">Choose a sign in</string> </resources> </resources> No newline at end of file packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt 0 → 100644 +29 −0 Original line number Original line Diff line number Diff line /* * 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.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.credentialmanager.ui.screens import androidx.activity.result.IntentSenderRequest sealed class UiState { data object CredentialScreen : UiState() data class CredentialSelected( val intentSenderRequest: IntentSenderRequest? ) : UiState() data object Cancel : UiState() } packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt 0 → 100644 +140 −0 Original line number Original line Diff line number Diff line /* * Copyright 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.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.credentialmanager.ui.screens.multiple import com.android.credentialmanager.ui.screens.UiState import android.graphics.drawable.Drawable import androidx.activity.compose.rememberLauncherForActivityResult import com.android.credentialmanager.R import androidx.compose.ui.res.stringResource import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import com.android.credentialmanager.CredentialSelectorUiState import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.ui.components.DismissChip import com.android.credentialmanager.ui.components.CredentialsScreenChip import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.ui.components.SignInOptionsChip import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumn import com.google.android.horologist.compose.layout.ScalingLazyColumnState /** * Screen that shows multiple credentials to select from. * * @param credentialSelectorUiState The app bar view model. * @param screenIcon The view model corresponding to the home page. * @param columnState ScalingLazyColumn configuration to be be applied * @param modifier styling for composable * @param viewModel ViewModel that updates ui state for this screen * @param navController handles navigation events from this screen */ @OptIn(ExperimentalHorologistApi::class) @Composable fun MultiCredentialsFoldScreen( credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntry, screenIcon: Drawable?, columnState: ScalingLazyColumnState, modifier: Modifier = Modifier, viewModel: MultiCredentialsFoldViewModel = hiltViewModel(), navController: NavHostController = rememberNavController(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() when (val state = uiState) { UiState.CredentialScreen -> { MultiCredentialsFoldScreen( state = credentialSelectorUiState, onSignInOptionsClicked = viewModel::onSignInOptionsClicked, onCredentialClicked = viewModel::onCredentialClicked, onCancelClicked = viewModel::onCancelClicked, screenIcon = screenIcon, columnState = columnState, modifier = modifier ) } is UiState.CredentialSelected -> { val launcher = rememberLauncherForActivityResult( StartBalIntentSenderForResultContract() ) { viewModel.onInfoRetrieved(it.resultCode, null) } SideEffect { state.intentSenderRequest?.let { launcher.launch(it) } } } UiState.Cancel -> { navController.popBackStack() } } } @OptIn(ExperimentalHorologistApi::class) @Composable fun MultiCredentialsFoldScreen( state: CredentialSelectorUiState.Get.MultipleEntry, onSignInOptionsClicked: () -> Unit, onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit, onCancelClicked: () -> Unit, screenIcon: Drawable?, columnState: ScalingLazyColumnState, modifier: Modifier, ) { ScalingLazyColumn( columnState = columnState, modifier = modifier.fillMaxSize(), ) { item { SignInHeader( icon = screenIcon, title = stringResource(R.string.choose_sign_in_title), modifier = Modifier .padding(top = 6.dp), ) } state.accounts.forEach { it.sortedCredentialEntryList.forEach { credential: CredentialEntryInfo -> item { CredentialsScreenChip( label = credential.userName, onClick = { onCredentialClicked(credential) }, secondaryLabel = credential.credentialTypeDisplayName, icon = credential.icon, ) } } } item { SignInOptionsChip(onSignInOptionsClicked) } item { DismissChip(onCancelClicked) } } } packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt 0 → 100644 +75 −0 Original line number Original line Diff line number Diff line /* * Copyright 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.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.credentialmanager.ui.screens.multiple import android.content.Intent import android.credentials.selection.ProviderPendingIntentResponse import android.credentials.selection.UserSelectionDialogResult import androidx.lifecycle.ViewModel import com.android.credentialmanager.client.CredentialManagerClient import com.android.credentialmanager.ktx.getIntentSenderRequest import com.android.credentialmanager.model.Request import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.ui.screens.UiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject /** ViewModel for [MultiCredentialsFoldScreen].*/ @HiltViewModel class MultiCredentialsFoldViewModel @Inject constructor( private val credentialManagerClient: CredentialManagerClient, ) : ViewModel() { private lateinit var requestGet: Request.Get private lateinit var entryInfo: CredentialEntryInfo private val _uiState = MutableStateFlow<UiState>(UiState.CredentialScreen) val uiState: StateFlow<UiState> = _uiState fun onCredentialClicked(entryInfo: CredentialEntryInfo) { this.entryInfo = entryInfo _uiState.value = UiState.CredentialSelected( intentSenderRequest = entryInfo.getIntentSenderRequest() ) } fun onSignInOptionsClicked() { // TODO(b/322797032) Implement navigation route for single credential screen to multiple // credentials } fun onCancelClicked() { _uiState.value = UiState.Cancel } fun onInfoRetrieved( resultCode: Int? = null, resultData: Intent? = null, ) { val userSelectionDialogResult = UserSelectionDialogResult( requestGet.token, entryInfo.providerId, entryInfo.entryKey, entryInfo.entrySubkey, if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null ) credentialManagerClient.sendResult(userSelectionDialogResult) } } packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -42,7 +42,7 @@ import com.android.credentialmanager.ui.components.DismissChip import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.ui.components.SignInOptionsChip import com.android.credentialmanager.ui.components.SignInOptionsChip import com.android.credentialmanager.ui.screens.single.SingleAccountScreen import com.android.credentialmanager.ui.screens.single.SingleAccountScreen import com.android.credentialmanager.ui.screens.single.UiState import com.android.credentialmanager.ui.screens.UiState import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumnState import com.google.android.horologist.compose.layout.ScalingLazyColumnState Loading Loading
packages/CredentialManager/wear/res/values/strings.xml +2 −0 Original line number Original line Diff line number Diff line Loading @@ -33,4 +33,6 @@ <string name="dialog_continue_button">Continue</string> <string name="dialog_continue_button">Continue</string> <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] --> <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] --> <string name="dialog_sign_in_options_button">Sign-in Options</string> <string name="dialog_sign_in_options_button">Sign-in Options</string> <!-- Title for multiple credentials flattened screen. [CHAR LIMIT=NONE] --> <string name="choose_sign_in_title">Choose a sign in</string> </resources> </resources> No newline at end of file
packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt 0 → 100644 +29 −0 Original line number Original line Diff line number Diff line /* * 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.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.credentialmanager.ui.screens import androidx.activity.result.IntentSenderRequest sealed class UiState { data object CredentialScreen : UiState() data class CredentialSelected( val intentSenderRequest: IntentSenderRequest? ) : UiState() data object Cancel : UiState() }
packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt 0 → 100644 +140 −0 Original line number Original line Diff line number Diff line /* * Copyright 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.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.credentialmanager.ui.screens.multiple import com.android.credentialmanager.ui.screens.UiState import android.graphics.drawable.Drawable import androidx.activity.compose.rememberLauncherForActivityResult import com.android.credentialmanager.R import androidx.compose.ui.res.stringResource import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import com.android.credentialmanager.CredentialSelectorUiState import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.ui.components.DismissChip import com.android.credentialmanager.ui.components.CredentialsScreenChip import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.ui.components.SignInOptionsChip import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumn import com.google.android.horologist.compose.layout.ScalingLazyColumnState /** * Screen that shows multiple credentials to select from. * * @param credentialSelectorUiState The app bar view model. * @param screenIcon The view model corresponding to the home page. * @param columnState ScalingLazyColumn configuration to be be applied * @param modifier styling for composable * @param viewModel ViewModel that updates ui state for this screen * @param navController handles navigation events from this screen */ @OptIn(ExperimentalHorologistApi::class) @Composable fun MultiCredentialsFoldScreen( credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntry, screenIcon: Drawable?, columnState: ScalingLazyColumnState, modifier: Modifier = Modifier, viewModel: MultiCredentialsFoldViewModel = hiltViewModel(), navController: NavHostController = rememberNavController(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() when (val state = uiState) { UiState.CredentialScreen -> { MultiCredentialsFoldScreen( state = credentialSelectorUiState, onSignInOptionsClicked = viewModel::onSignInOptionsClicked, onCredentialClicked = viewModel::onCredentialClicked, onCancelClicked = viewModel::onCancelClicked, screenIcon = screenIcon, columnState = columnState, modifier = modifier ) } is UiState.CredentialSelected -> { val launcher = rememberLauncherForActivityResult( StartBalIntentSenderForResultContract() ) { viewModel.onInfoRetrieved(it.resultCode, null) } SideEffect { state.intentSenderRequest?.let { launcher.launch(it) } } } UiState.Cancel -> { navController.popBackStack() } } } @OptIn(ExperimentalHorologistApi::class) @Composable fun MultiCredentialsFoldScreen( state: CredentialSelectorUiState.Get.MultipleEntry, onSignInOptionsClicked: () -> Unit, onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit, onCancelClicked: () -> Unit, screenIcon: Drawable?, columnState: ScalingLazyColumnState, modifier: Modifier, ) { ScalingLazyColumn( columnState = columnState, modifier = modifier.fillMaxSize(), ) { item { SignInHeader( icon = screenIcon, title = stringResource(R.string.choose_sign_in_title), modifier = Modifier .padding(top = 6.dp), ) } state.accounts.forEach { it.sortedCredentialEntryList.forEach { credential: CredentialEntryInfo -> item { CredentialsScreenChip( label = credential.userName, onClick = { onCredentialClicked(credential) }, secondaryLabel = credential.credentialTypeDisplayName, icon = credential.icon, ) } } } item { SignInOptionsChip(onSignInOptionsClicked) } item { DismissChip(onCancelClicked) } } }
packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt 0 → 100644 +75 −0 Original line number Original line Diff line number Diff line /* * Copyright 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.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.credentialmanager.ui.screens.multiple import android.content.Intent import android.credentials.selection.ProviderPendingIntentResponse import android.credentials.selection.UserSelectionDialogResult import androidx.lifecycle.ViewModel import com.android.credentialmanager.client.CredentialManagerClient import com.android.credentialmanager.ktx.getIntentSenderRequest import com.android.credentialmanager.model.Request import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.ui.screens.UiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject /** ViewModel for [MultiCredentialsFoldScreen].*/ @HiltViewModel class MultiCredentialsFoldViewModel @Inject constructor( private val credentialManagerClient: CredentialManagerClient, ) : ViewModel() { private lateinit var requestGet: Request.Get private lateinit var entryInfo: CredentialEntryInfo private val _uiState = MutableStateFlow<UiState>(UiState.CredentialScreen) val uiState: StateFlow<UiState> = _uiState fun onCredentialClicked(entryInfo: CredentialEntryInfo) { this.entryInfo = entryInfo _uiState.value = UiState.CredentialSelected( intentSenderRequest = entryInfo.getIntentSenderRequest() ) } fun onSignInOptionsClicked() { // TODO(b/322797032) Implement navigation route for single credential screen to multiple // credentials } fun onCancelClicked() { _uiState.value = UiState.Cancel } fun onInfoRetrieved( resultCode: Int? = null, resultData: Intent? = null, ) { val userSelectionDialogResult = UserSelectionDialogResult( requestGet.token, entryInfo.providerId, entryInfo.entryKey, entryInfo.entrySubkey, if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null ) credentialManagerClient.sendResult(userSelectionDialogResult) } }
packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -42,7 +42,7 @@ import com.android.credentialmanager.ui.components.DismissChip import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.ui.components.SignInHeader import com.android.credentialmanager.ui.components.SignInOptionsChip import com.android.credentialmanager.ui.components.SignInOptionsChip import com.android.credentialmanager.ui.screens.single.SingleAccountScreen import com.android.credentialmanager.ui.screens.single.SingleAccountScreen import com.android.credentialmanager.ui.screens.single.UiState import com.android.credentialmanager.ui.screens.UiState import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumnState import com.google.android.horologist.compose.layout.ScalingLazyColumnState Loading