Single credential sign in with provider screen changes

Bug: 308861375
Test: Manual. See go/credential-selector-ui
Change-Id: Icb2c5e7fe8405ce78cc7cb00bd404deb5c6f2983
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
index b2812d3..8b19e1b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
new file mode 100644
index 0000000..c33a863
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.single.signInWithProvider
+
+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
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+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.model.get.CredentialEntryInfo
+import com.android.credentialmanager.R
+import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.ui.components.AccountRow
+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.components.SignInOptionsChip
+import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.android.credentialmanager.ui.screens.single.UiState
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.layout.ScalingLazyColumnState
+
+/**
+ * Screen that shows sign in with provider credential.
+ *
+ * @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 to SingleAccountScreen
+ * @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 SignInWithProviderScreen(
+    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
+    screenIcon: Drawable?,
+    columnState: ScalingLazyColumnState,
+    modifier: Modifier = Modifier,
+    viewModel: SignInWithProviderViewModel = hiltViewModel(),
+    navController: NavHostController = rememberNavController(),
+) {
+    viewModel.initialize(credentialSelectorUiState.entry)
+
+    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+    when (uiState) {
+        UiState.CredentialScreen -> {
+            SignInWithProviderScreen(
+                credentialSelectorUiState.entry,
+                screenIcon,
+                columnState,
+                modifier,
+                viewModel
+            )
+        }
+
+        is UiState.CredentialSelected -> {
+            val launcher = rememberLauncherForActivityResult(
+                StartBalIntentSenderForResultContract()
+            ) {
+                viewModel.onInfoRetrieved(it.resultCode, null)
+            }
+
+            SideEffect {
+                (uiState as UiState.CredentialSelected).intentSenderRequest?.let {
+                    launcher.launch(it)
+                }
+            }
+        }
+
+        UiState.Cancel -> {
+            // TODO(b/322797032) add valid navigation path here for going back
+            navController.popBackStack()
+        }
+    }
+}
+
+@OptIn(ExperimentalHorologistApi::class)
+@Composable
+fun SignInWithProviderScreen(
+    entry: CredentialEntryInfo,
+    screenIcon: Drawable?,
+    columnState: ScalingLazyColumnState,
+    modifier: Modifier = Modifier,
+    viewModel: SignInWithProviderViewModel,
+) {
+    SingleAccountScreen(
+        headerContent = {
+            SignInHeader(
+                icon = screenIcon,
+                title = stringResource(R.string.use_sign_in_with_provider_title,
+                    entry.providerDisplayName),
+            )
+        },
+        accountContent = {
+            val displayName = entry.displayName
+            if (displayName != null) {
+                AccountRow(
+                    primaryText = displayName,
+                    secondaryText = entry.userName,
+                    modifier = Modifier.padding(top = 10.dp),
+                )
+            } else {
+                AccountRow(
+                    primaryText = entry.userName,
+                    modifier = Modifier.padding(top = 10.dp),
+                )
+            }
+        },
+        columnState = columnState,
+        modifier = modifier.padding(horizontal = 10.dp)
+    ) {
+       item {
+           Column {
+               ContinueChip(viewModel::onContinueClick)
+               SignInOptionsChip(viewModel::onSignInOptionsClick)
+               DismissChip(viewModel::onDismissClick)
+           }
+       }
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
new file mode 100644
index 0000000..2f9e2b1
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.single.signInWithProvider
+
+import android.content.Intent
+import android.credentials.selection.ProviderPendingIntentResponse
+import android.credentials.selection.UserSelectionDialogResult
+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 dagger.hilt.android.lifecycle.HiltViewModel
+import com.android.credentialmanager.ui.screens.single.UiState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+
+/** ViewModel for [SignInWithProviderScreen].*/
+@HiltViewModel
+class SignInWithProviderViewModel @Inject constructor(
+    private val credentialManagerClient: CredentialManagerClient,
+) : ViewModel() {
+
+    private val _uiState =
+        MutableStateFlow<UiState>(UiState.CredentialScreen)
+    val uiState: StateFlow<UiState> = _uiState
+
+    private lateinit var requestGet: Request.Get
+    private lateinit var entryInfo: CredentialEntryInfo
+
+    @MainThread
+    fun initialize(entry: CredentialEntryInfo) {
+        this.entryInfo = entry
+    }
+
+    fun onDismissClick() {
+        _uiState.value = UiState.Cancel
+    }
+
+    fun onContinueClick() {
+        _uiState.value = UiState.CredentialSelected(
+            intentSenderRequest = entryInfo.getIntentSenderRequest()
+        )
+    }
+
+    fun onSignInOptionsClick() {
+        // TODO(b/322797032) Implement navigation route for single credential screen to multiple
+        // credentials
+    }
+
+    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)
+    }
+}
+