Loading packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt +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. Loading Loading @@ -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, ) } } Loading packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt→packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt +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, Loading @@ -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 packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt +40 −32 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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), ) }, Loading @@ -117,6 +118,13 @@ fun SinglePasswordScreen( modifier = modifier.padding(horizontal = 10.dp) ) { item { Column { ContinueChip(viewModel::onContinueClick) SignInOptionsChip(viewModel::onSignInOptionsClick) DismissChip(viewModel::onDismissClick) } } } } packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt +12 −31 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -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() } Loading
packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt +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. Loading Loading @@ -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, ) } } Loading
packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt→packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt +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, Loading @@ -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
packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt +40 −32 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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), ) }, Loading @@ -117,6 +118,13 @@ fun SinglePasswordScreen( modifier = modifier.padding(horizontal = 10.dp) ) { item { Column { ContinueChip(viewModel::onContinueClick) SignInOptionsChip(viewModel::onSignInOptionsClick) DismissChip(viewModel::onDismissClick) } } } }
packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt +12 −31 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -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() }