From 9af10d2b9ccb921f98ec063b50fbed5ef9817348 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Tue, 30 Sep 2025 00:55:06 +0100 Subject: [PATCH 1/6] feat: AuthMethodPicker, logo and provider theme style --- .../ui/auth/compose/AuthMethodPicker.kt | 178 ++++++++++++++++++ .../ui/auth/compose/AuthProviderButton.kt | 26 ++- 2 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/AuthMethodPicker.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/AuthMethodPicker.kt b/auth/src/main/java/com/firebase/ui/auth/compose/AuthMethodPicker.kt new file mode 100644 index 000000000..7553dfdcd --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/AuthMethodPicker.kt @@ -0,0 +1,178 @@ +package com.firebase.ui.auth.compose + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Star +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.firebase.ui.auth.R +import com.firebase.ui.auth.compose.AuthProviderButton +import com.firebase.ui.auth.compose.configuration.AuthProvider +import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset + +//AuthMethodPicker( +// providers = listOf(GoogleAuthProvider(), EmailAuthProvider()), +// onProviderSelected = { provider -> /* ... */ } +//) + +/** + * Renders the provider selection screen. + * + * **Example usage:** + * ```kotlin + * AuthMethodPicker( + * providers = listOf( + * AuthProvider.Google(), + * AuthProvider.Email(), + * ), + * onProviderSelected = { provider -> /* ... */ } + * ) + * ``` + * + * @param modifier A modifier for the screen layout. + * @param providers The list of providers to display. + * @param logo An optional logo to display. + * @param onProviderSelected A callback when a provider is selected. + * @param customLayout An optional custom layout composable for the provider buttons. + * + * @since 10.0.0 + */ +@Composable +fun AuthMethodPicker( + modifier: Modifier = Modifier, + providers: List, + logo: AuthUIAsset? = null, + onProviderSelected: (AuthProvider) -> Unit, + customLayout: @Composable ((List, (AuthProvider) -> Unit) -> Unit)? = null, +) { + val context = LocalContext.current + + Column( + modifier = modifier + .fillMaxSize() + .safeDrawingPadding(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + logo?.let { + Image( + modifier = Modifier + .weight(0.4f), + painter = it.painter, + contentDescription = "AuthMethodPicker logo", + ) + } + if (customLayout != null) { + customLayout(providers, onProviderSelected) + } else { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + contentPadding = PaddingValues(bottom = 64.dp) // Space for text + ) { + items(providers.size) { index -> + val provider = providers[index] + Box( + modifier = Modifier + .padding(bottom = 16.dp) + ) { + AuthProviderButton( + onClick = { + onProviderSelected(provider) + }, + provider = provider, + stringProvider = DefaultAuthUIStringProvider(context) + ) + } + } + } + Text( + "By continuing, you are indicating that you accept our " + + "Terms of Service and Privacy Policy.", + textAlign = TextAlign.Center, + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 16.dp) + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewAuthMethodPicker() { + AuthMethodPicker( + providers = listOf( + AuthProvider.Email( + actionCodeSettings = null, + passwordValidationRules = emptyList() + ), + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null, + ), + AuthProvider.Google( + scopes = emptyList(), + serverClientId = null + ), + AuthProvider.Facebook(), + AuthProvider.Twitter( + customParameters = emptyMap() + ), + AuthProvider.Github( + customParameters = emptyMap() + ), + AuthProvider.Microsoft( + tenant = null, + customParameters = emptyMap() + ), + AuthProvider.Yahoo( + customParameters = emptyMap() + ), + AuthProvider.Apple( + locale = null, + customParameters = emptyMap() + ), + AuthProvider.Anonymous, + AuthProvider.GenericOAuth( + providerId = "google.com", + scopes = emptyList(), + customParameters = emptyMap(), + buttonLabel = "Generic Provider", + buttonIcon = AuthUIAsset.Vector(Icons.Default.Star), + buttonColor = Color.Gray, + contentColor = Color.White + ) + ), + logo = AuthUIAsset.Resource(R.drawable.fui_ic_check_circle_black_128dp), + onProviderSelected = { provider -> + + }, + ) +} \ No newline at end of file diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/AuthProviderButton.kt b/auth/src/main/java/com/firebase/ui/auth/compose/AuthProviderButton.kt index da6b2bbd9..213b5be8f 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/AuthProviderButton.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/AuthProviderButton.kt @@ -4,10 +4,13 @@ import androidx.compose.foundation.Image import androidx.compose.material3.Icon import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Star @@ -19,6 +22,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.firebase.ui.auth.compose.configuration.AuthProvider @@ -63,10 +68,12 @@ fun AuthProviderButton( stringProvider: AuthUIStringProvider, ) { val providerStyle = resolveProviderStyle(provider, style) - val providerText = resolveProviderLabel(provider, stringProvider) + val providerLabel = resolveProviderLabel(provider, stringProvider) Button( - modifier = modifier, + modifier = modifier + .width(208.dp), + contentPadding = PaddingValues(horizontal = 12.dp), colors = ButtonDefaults.buttonColors( containerColor = providerStyle.backgroundColor, contentColor = providerStyle.contentColor, @@ -79,7 +86,10 @@ fun AuthProviderButton( enabled = enabled, ) { Row( - verticalAlignment = Alignment.CenterVertically + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start ) { val providerIcon = providerStyle.icon if (providerIcon != null) { @@ -87,19 +97,21 @@ fun AuthProviderButton( if (iconTint != null) { Icon( painter = providerIcon.painter, - contentDescription = providerText, + contentDescription = providerLabel, tint = iconTint ) } else { Image( painter = providerIcon.painter, - contentDescription = providerText + contentDescription = providerLabel ) } - Spacer(modifier = Modifier.width(8.dp)) + Spacer(modifier = Modifier.width(12.dp)) } Text( - text = providerText + text = providerLabel, + overflow = TextOverflow.Ellipsis, + maxLines = 1, ) } } From b6f7bdc028a8907c80118c9f4c284b3c51214a2f Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Tue, 30 Sep 2025 12:57:41 +0100 Subject: [PATCH 2/6] chore: organize folder structure --- .../ui/auth/compose/configuration/AuthUIConfiguration.kt | 4 ++-- .../ui/auth/compose/configuration/PasswordRule.kt | 2 +- .../AuthUIStringProvider.kt | 2 +- .../AuthUIStringProviderSample.kt | 2 +- .../DefaultAuthUIStringProvider.kt | 2 +- .../compose/configuration/validators/EmailValidator.kt | 2 +- .../compose/configuration/validators/FieldValidator.kt | 2 +- .../compose/configuration/validators/PasswordValidator.kt | 2 +- .../compose/{ => ui/components}/AuthProviderButton.kt | 8 +++----- .../compose/{ => ui/components}/ErrorRecoveryDialog.kt | 5 +++-- .../firebase/ui/auth/compose/AuthProviderButtonTest.kt | 6 ++++-- .../ui/auth/compose/ErrorRecoveryDialogLogicTest.kt | 4 ++-- .../auth/compose/configuration/AuthUIConfigurationTest.kt | 4 ++-- .../ui/auth/compose/configuration/PasswordRuleTest.kt | 4 ++-- .../configuration/validators/EmailValidatorTest.kt | 4 ++-- .../configuration/validators/PasswordValidatorTest.kt | 4 ++-- 16 files changed, 29 insertions(+), 28 deletions(-) rename auth/src/main/java/com/firebase/ui/auth/compose/configuration/{stringprovider => string_provider}/AuthUIStringProvider.kt (99%) rename auth/src/main/java/com/firebase/ui/auth/compose/configuration/{stringprovider => string_provider}/AuthUIStringProviderSample.kt (97%) rename auth/src/main/java/com/firebase/ui/auth/compose/configuration/{stringprovider => string_provider}/DefaultAuthUIStringProvider.kt (99%) rename auth/src/main/java/com/firebase/ui/auth/compose/{ => ui/components}/AuthProviderButton.kt (97%) rename auth/src/main/java/com/firebase/ui/auth/compose/{ => ui/components}/ErrorRecoveryDialog.kt (97%) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt index 4d9bc280d..6fb0202de 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt @@ -18,8 +18,8 @@ import android.content.Context import java.util.Locale import com.google.firebase.auth.ActionCodeSettings import androidx.compose.ui.graphics.vector.ImageVector -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider -import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme fun actionCodeSettings(block: ActionCodeSettings.Builder.() -> Unit) = diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt index 5c5d5b125..7073fbe6e 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt @@ -14,7 +14,7 @@ package com.firebase.ui.auth.compose.configuration -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider /** * An abstract class representing a set of validation rules that can be applied to a password field, diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt similarity index 99% rename from auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProvider.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt index ea88485e3..f81ead323 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt @@ -12,7 +12,7 @@ * limitations under the License. */ -package com.firebase.ui.auth.compose.configuration.stringprovider +package com.firebase.ui.auth.compose.configuration.string_provider /** * An interface for providing localized string resources. This interface defines methods for all diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProviderSample.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProviderSample.kt similarity index 97% rename from auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProviderSample.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProviderSample.kt index 7ddf64522..af0c830cc 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/AuthUIStringProviderSample.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProviderSample.kt @@ -12,7 +12,7 @@ * limitations under the License. */ -package com.firebase.ui.auth.compose.configuration.stringprovider +package com.firebase.ui.auth.compose.configuration.string_provider import android.content.Context import com.firebase.ui.auth.compose.configuration.AuthProvider diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt similarity index 99% rename from auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt index 7df16d9ca..5eba036af 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/stringprovider/DefaultAuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt @@ -12,7 +12,7 @@ * limitations under the License. */ -package com.firebase.ui.auth.compose.configuration.stringprovider +package com.firebase.ui.auth.compose.configuration.string_provider import android.content.Context import android.content.res.Configuration diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidator.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidator.kt index 7acfc8bc1..30582a309 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidator.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidator.kt @@ -14,7 +14,7 @@ package com.firebase.ui.auth.compose.configuration.validators -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider internal class EmailValidator(override val stringProvider: AuthUIStringProvider) : FieldValidator { private var _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/FieldValidator.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/FieldValidator.kt index efa72188f..88cf98875 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/FieldValidator.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/FieldValidator.kt @@ -14,7 +14,7 @@ package com.firebase.ui.auth.compose.configuration.validators -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider /** * An interface for validating input fields. diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidator.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidator.kt index 67cb7d376..2d9efafc1 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidator.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidator.kt @@ -14,7 +14,7 @@ package com.firebase.ui.auth.compose.configuration.validators -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider import com.firebase.ui.auth.compose.configuration.PasswordRule internal class PasswordValidator( diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/AuthProviderButton.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButton.kt similarity index 97% rename from auth/src/main/java/com/firebase/ui/auth/compose/AuthProviderButton.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButton.kt index 213b5be8f..e23d98fc7 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/AuthProviderButton.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButton.kt @@ -1,4 +1,4 @@ -package com.firebase.ui.auth.compose +package com.firebase.ui.auth.compose.ui.components import androidx.compose.foundation.Image import androidx.compose.material3.Icon @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Star @@ -22,14 +21,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.firebase.ui.auth.compose.configuration.AuthProvider import com.firebase.ui.auth.compose.configuration.Provider -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider -import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ErrorRecoveryDialog.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/ErrorRecoveryDialog.kt similarity index 97% rename from auth/src/main/java/com/firebase/ui/auth/compose/ErrorRecoveryDialog.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/ui/components/ErrorRecoveryDialog.kt index 3b9cc0b57..732a48662 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ErrorRecoveryDialog.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/ErrorRecoveryDialog.kt @@ -12,7 +12,7 @@ * limitations under the License. */ -package com.firebase.ui.auth.compose +package com.firebase.ui.auth.compose.ui.components import androidx.compose.material3.AlertDialog import androidx.compose.material3.MaterialTheme @@ -22,7 +22,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.window.DialogProperties -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.AuthException +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider /** * A composable dialog for displaying authentication errors with recovery options. diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/AuthProviderButtonTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/AuthProviderButtonTest.kt index 255fd53af..1bd889fc5 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/AuthProviderButtonTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/AuthProviderButtonTest.kt @@ -29,8 +29,8 @@ import androidx.compose.ui.test.performClick import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.compose.configuration.AuthProvider import com.firebase.ui.auth.compose.configuration.Provider -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider -import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme import com.google.common.truth.Truth.assertThat @@ -41,6 +41,8 @@ import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import com.firebase.ui.auth.R +import com.firebase.ui.auth.compose.ui.components.AuthProviderButton +import com.firebase.ui.auth.compose.ui.components.resolveProviderStyle /** * Unit tests for [AuthProviderButton] covering UI interactions, styling, diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/ErrorRecoveryDialogLogicTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/ErrorRecoveryDialogLogicTest.kt index f3c730b05..480e4ea14 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/ErrorRecoveryDialogLogicTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/ErrorRecoveryDialogLogicTest.kt @@ -14,7 +14,7 @@ package com.firebase.ui.auth.compose -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -24,7 +24,7 @@ import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config /** - * Unit tests for [ErrorRecoveryDialog] logic functions. + * Unit tests for [com.firebase.ui.auth.compose.ui.components.ErrorRecoveryDialog] logic functions. */ @RunWith(RobolectricTestRunner::class) @Config(manifest = Config.NONE) diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt index e8bf0fd4a..f08be227f 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt @@ -20,8 +20,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider -import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme import com.google.common.truth.Truth.assertThat import com.google.firebase.auth.actionCodeSettings diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/PasswordRuleTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/PasswordRuleTest.kt index a4d5139a6..dbaccbcfb 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/PasswordRuleTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/PasswordRuleTest.kt @@ -17,8 +17,8 @@ package com.firebase.ui.auth.compose.configuration import android.content.Context import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider -import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidatorTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidatorTest.kt index 3253cfb1c..3715d63ec 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidatorTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/EmailValidatorTest.kt @@ -17,8 +17,8 @@ package com.firebase.ui.auth.compose.configuration.validators import android.content.Context import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider -import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidatorTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidatorTest.kt index a3993bd36..27d34b6a6 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidatorTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/validators/PasswordValidatorTest.kt @@ -17,8 +17,8 @@ package com.firebase.ui.auth.compose.configuration.validators import android.content.Context import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R -import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider -import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.PasswordRule import com.google.common.truth.Truth.assertThat import org.junit.Before From 96ffa30021de2da3760c650cc68103b797d5c701 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Tue, 30 Sep 2025 12:59:56 +0100 Subject: [PATCH 3/6] feat: TOS and PP footer, ui tests for AuthMethodPicker --- .../method_picker/AnnotatedStringResource.kt | 76 +++++ .../method_picker}/AuthMethodPicker.kt | 71 ++-- auth/src/main/res/values/strings.xml | 1 + .../ui/auth/compose/AuthMethodPickerTest.kt | 323 ++++++++++++++++++ 4 files changed, 428 insertions(+), 43 deletions(-) create mode 100644 auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AnnotatedStringResource.kt rename auth/src/main/java/com/firebase/ui/auth/compose/{ => ui/method_picker}/AuthMethodPicker.kt (68%) create mode 100644 auth/src/test/java/com/firebase/ui/auth/compose/AuthMethodPickerTest.kt diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AnnotatedStringResource.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AnnotatedStringResource.kt new file mode 100644 index 000000000..4c98be9ac --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AnnotatedStringResource.kt @@ -0,0 +1,76 @@ +package com.firebase.ui.auth.compose.ui.method_picker + +import android.content.Context +import android.content.Intent +import androidx.annotation.StringRes +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withLink +import androidx.core.net.toUri + +@Composable +internal fun AnnotatedStringResource( + context: Context, + modifier: Modifier = Modifier, + @StringRes id: Int, + vararg links: Pair, + inPreview: Boolean = false, + previewText: String? = null, +) { + val labels = links.map { it.first }.toTypedArray() + + val template = if (inPreview && previewText != null) { + previewText + } else { + stringResource(id = id, *labels) + } + + val annotated = buildAnnotatedString { + var currentIndex = 0 + + links.forEach { (label, url) -> + val start = template.indexOf(label, currentIndex).takeIf { it >= 0 } ?: return@forEach + + append(template.substring(currentIndex, start)) + + withLink( + LinkAnnotation.Url( + url, + styles = TextLinkStyles( + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + ) + ) + ) { + val intent = Intent(Intent.ACTION_VIEW, url.toUri()) + context.startActivity(intent) + } + ) { + append(label) + } + + currentIndex = start + label.length + } + + if (currentIndex < template.length) { + append(template.substring(currentIndex)) + } + } + + Text( + modifier = modifier, + text = annotated, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center + ) +} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/AuthMethodPicker.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt similarity index 68% rename from auth/src/main/java/com/firebase/ui/auth/compose/AuthMethodPicker.kt rename to auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt index 7553dfdcd..91aedc2fd 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/AuthMethodPicker.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt @@ -1,44 +1,26 @@ -package com.firebase.ui.auth.compose +package com.firebase.ui.auth.compose.ui.method_picker import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.foundation.layout.safeDrawingPadding -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.filled.Star -import androidx.compose.material3.Icon -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.firebase.ui.auth.R -import com.firebase.ui.auth.compose.AuthProviderButton import com.firebase.ui.auth.compose.configuration.AuthProvider -import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset - -//AuthMethodPicker( -// providers = listOf(GoogleAuthProvider(), EmailAuthProvider()), -// onProviderSelected = { provider -> /* ... */ } -//) +import com.firebase.ui.auth.compose.ui.components.AuthProviderButton /** * Renders the provider selection screen. @@ -59,6 +41,8 @@ import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset * @param logo An optional logo to display. * @param onProviderSelected A callback when a provider is selected. * @param customLayout An optional custom layout composable for the provider buttons. + * @param termsOfServiceUrl The URL for the Terms of Service. + * @param privacyPolicyUrl The URL for the Privacy Policy. * * @since 10.0.0 */ @@ -69,8 +53,11 @@ fun AuthMethodPicker( logo: AuthUIAsset? = null, onProviderSelected: (AuthProvider) -> Unit, customLayout: @Composable ((List, (AuthProvider) -> Unit) -> Unit)? = null, + termsOfServiceUrl: String? = null, + privacyPolicyUrl: String? = null, ) { val context = LocalContext.current + val inPreview = LocalInspectionMode.current Column( modifier = modifier @@ -83,7 +70,8 @@ fun AuthMethodPicker( modifier = Modifier .weight(0.4f), painter = it.painter, - contentDescription = "AuthMethodPicker logo", + contentDescription = if (inPreview) "" + else stringResource(R.string.fui_auth_method_picker_logo) ) } if (customLayout != null) { @@ -92,9 +80,9 @@ fun AuthMethodPicker( LazyColumn( modifier = Modifier .fillMaxSize() - .weight(1f), + .weight(1f) + .testTag("AuthMethodPicker LazyColumn"), horizontalAlignment = Alignment.CenterHorizontally, - contentPadding = PaddingValues(bottom = 64.dp) // Space for text ) { items(providers.size) { index -> val provider = providers[index] @@ -112,14 +100,18 @@ fun AuthMethodPicker( } } } - Text( - "By continuing, you are indicating that you accept our " + - "Terms of Service and Privacy Policy.", - textAlign = TextAlign.Center, - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 16.dp) - ) } + AnnotatedStringResource( + context = context, + inPreview = inPreview, + previewText = "By continuing, you accept our Terms of Service and Privacy Policy.", + modifier = Modifier.padding(vertical = 16.dp), + id = R.string.fui_tos_and_pp, + links = arrayOf( + "Terms of Service" to (termsOfServiceUrl ?: ""), + "Privacy Policy" to (privacyPolicyUrl ?: "") + ) + ) } } @@ -160,19 +152,12 @@ fun PreviewAuthMethodPicker() { customParameters = emptyMap() ), AuthProvider.Anonymous, - AuthProvider.GenericOAuth( - providerId = "google.com", - scopes = emptyList(), - customParameters = emptyMap(), - buttonLabel = "Generic Provider", - buttonIcon = AuthUIAsset.Vector(Icons.Default.Star), - buttonColor = Color.Gray, - contentColor = Color.White - ) ), logo = AuthUIAsset.Resource(R.drawable.fui_ic_check_circle_black_128dp), onProviderSelected = { provider -> }, + termsOfServiceUrl = "https://example.com/terms", + privacyPolicyUrl = "https://example.com/privacy" ) } \ No newline at end of file diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index 75060012d..5790ca8d2 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -19,6 +19,7 @@ Email + "Auth method picker logo" Sign in with Google Sign in with Facebook Sign in with Twitter diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/AuthMethodPickerTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/AuthMethodPickerTest.kt new file mode 100644 index 000000000..3c9f383bc --- /dev/null +++ b/auth/src/test/java/com/firebase/ui/auth/compose/AuthMethodPickerTest.kt @@ -0,0 +1,323 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * 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.firebase.ui.auth.compose + +import android.content.Context +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollToNode +import androidx.test.core.app.ApplicationProvider +import com.firebase.ui.auth.R +import com.firebase.ui.auth.compose.configuration.AuthProvider +import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset +import com.firebase.ui.auth.compose.ui.method_picker.AuthMethodPicker +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** + * Unit tests for [AuthMethodPicker] covering UI interactions, provider selection, + * scroll tests, logo display, and custom layouts. + * + * @suppress Internal test class + */ +@Config(sdk = [34]) +@RunWith(RobolectricTestRunner::class) +class AuthMethodPickerTest { + + @get:Rule + val composeTestRule = createComposeRule() + + private lateinit var context: Context + private var selectedProvider: AuthProvider? = null + + @Before + fun setUp() { + context = ApplicationProvider.getApplicationContext() + selectedProvider = null + } + + // ============================================================================================= + // Basic UI Tests + // ============================================================================================= + + @Test + fun `AuthMethodPicker displays all providers`() { + val providers = listOf( + AuthProvider.Google(scopes = emptyList(), serverClientId = null), + AuthProvider.Facebook(), + AuthProvider.Email(actionCodeSettings = null, passwordValidationRules = emptyList()) + ) + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + onProviderSelected = { selectedProvider = it } + ) + } + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_sign_in_with_google)) + .assertIsDisplayed() + .assertHasClickAction() + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_sign_in_with_facebook)) + .assertIsDisplayed() + .assertHasClickAction() + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_sign_in_with_email)) + .assertIsDisplayed() + .assertHasClickAction() + } + + @Test + fun `AuthMethodPicker displays terms of service text`() { + val context = ApplicationProvider.getApplicationContext() + val links = arrayOf("Terms of Service" to "", "Privacy Policy" to "") + val labels = links.map { it.first }.toTypedArray() + val providers = listOf( + AuthProvider.Google(scopes = emptyList(), serverClientId = null) + ) + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + onProviderSelected = { selectedProvider = it } + ) + } + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_tos_and_pp, *labels)) + .assertIsDisplayed() + } + + @Test + fun `AuthMethodPicker displays logo when provided`() { + val providers = listOf( + AuthProvider.Google(scopes = emptyList(), serverClientId = null) + ) + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + logo = AuthUIAsset.Resource(R.drawable.fui_ic_check_circle_black_128dp), + onProviderSelected = { selectedProvider = it } + ) + } + + composeTestRule + .onNodeWithContentDescription(context.getString(R.string.fui_auth_method_picker_logo)) + .assertIsDisplayed() + } + + @Test + fun `AuthMethodPicker does not display logo when null`() { + val providers = listOf( + AuthProvider.Google(scopes = emptyList(), serverClientId = null) + ) + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + logo = null, + onProviderSelected = { selectedProvider = it } + ) + } + + composeTestRule + .onNodeWithContentDescription(context.getString(R.string.fui_auth_method_picker_logo)) + .assertIsNotDisplayed() + } + + @Test + fun `AuthMethodPicker displays logo and providers together`() { + val context = ApplicationProvider.getApplicationContext() + val links = arrayOf("Terms of Service" to "", "Privacy Policy" to "") + val labels = links.map { it.first }.toTypedArray() + val providers = listOf( + AuthProvider.Google(scopes = emptyList(), serverClientId = null) + ) + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + logo = AuthUIAsset.Resource(R.drawable.fui_ic_check_circle_black_128dp), + onProviderSelected = { selectedProvider = it } + ) + } + + composeTestRule + .onNodeWithContentDescription(context.getString(R.string.fui_auth_method_picker_logo)) + .assertIsDisplayed() + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_sign_in_with_google)) + .assertIsDisplayed() + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_tos_and_pp, *labels)) + .assertIsDisplayed() + } + + @Test + fun `AuthMethodPicker calls onProviderSelected when Provider is clicked`() { + val googleProvider = AuthProvider.Google(scopes = emptyList(), serverClientId = null) + val providers = listOf(googleProvider) + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + onProviderSelected = { selectedProvider = it } + ) + } + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_sign_in_with_google)) + .performClick() + + assertThat(selectedProvider).isEqualTo(googleProvider) + } + + // ============================================================================================= + // Custom Layout Tests + // ============================================================================================= + + @Test + fun `AuthMethodPicker uses custom layout when provided`() { + val providers = listOf( + AuthProvider.Google(scopes = emptyList(), serverClientId = null) + ) + var customLayoutCalled = false + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + onProviderSelected = { selectedProvider = it }, + customLayout = { _, _ -> + customLayoutCalled = true + Text("Custom Layout") + } + ) + } + + assertThat(customLayoutCalled).isTrue() + composeTestRule + .onNodeWithText("Custom Layout") + .assertIsDisplayed() + } + + @Test + fun `AuthMethodPicker custom layout receives providers list`() { + val providers = listOf( + AuthProvider.Google(scopes = emptyList(), serverClientId = null), + AuthProvider.Facebook() + ) + var receivedProviders: List? = null + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + onProviderSelected = { selectedProvider = it }, + customLayout = { providersList, _ -> + receivedProviders = providersList + } + ) + } + + assertThat(receivedProviders).isEqualTo(providers) + } + + @Test + fun `AuthMethodPicker custom layout can trigger provider selection`() { + val googleProvider = AuthProvider.Google(scopes = emptyList(), serverClientId = null) + val providers = listOf(googleProvider) + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + onProviderSelected = { selectedProvider = it }, + customLayout = { providersList, onSelected -> + Button(onClick = { onSelected(providersList[0]) }) { + Text("Custom Button") + } + } + ) + } + + composeTestRule + .onNodeWithText("Custom Button") + .performClick() + + assertThat(selectedProvider).isEqualTo(googleProvider) + } + + // ============================================================================================= + // Scrolling Tests + // ============================================================================================= + + @Test + fun `AuthMethodPicker allows scrolling through many providers`() { + val providers = listOf( + AuthProvider.Google(scopes = emptyList(), serverClientId = null), + AuthProvider.Facebook(), + AuthProvider.Twitter(customParameters = emptyMap()), + AuthProvider.Github(customParameters = emptyMap()), + AuthProvider.Microsoft(tenant = null, customParameters = emptyMap()), + AuthProvider.Yahoo(customParameters = emptyMap()), + AuthProvider.Apple(locale = null, customParameters = emptyMap()), + AuthProvider.Email(actionCodeSettings = null, passwordValidationRules = emptyList()), + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null + ), + AuthProvider.Anonymous + ) + + composeTestRule.setContent { + AuthMethodPicker( + providers = providers, + onProviderSelected = { selectedProvider = it } + ) + } + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_sign_in_with_google)) + .assertIsDisplayed() + + composeTestRule + .onNodeWithTag("AuthMethodPicker LazyColumn") + .performScrollToNode(hasText(context.getString(R.string.fui_sign_in_anonymously))) + + composeTestRule + .onNodeWithText(context.getString(R.string.fui_sign_in_anonymously)) + .assertIsDisplayed() + } +} \ No newline at end of file From bfd056c43b64a2ad0cf82a777481d6f5fc0c3c28 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Tue, 30 Sep 2025 13:06:39 +0100 Subject: [PATCH 4/6] chore: tests folder structure --- .../components}/AuthProviderButtonTest.kt | 4 +- .../ErrorRecoveryDialogLogicTest.kt | 86 ++++++++----------- .../method_picker}/AuthMethodPickerTest.kt | 27 ++---- 3 files changed, 43 insertions(+), 74 deletions(-) rename auth/src/test/java/com/firebase/ui/auth/compose/{ => ui/components}/AuthProviderButtonTest.kt (99%) rename auth/src/test/java/com/firebase/ui/auth/compose/{ => ui/components}/ErrorRecoveryDialogLogicTest.kt (72%) rename auth/src/test/java/com/firebase/ui/auth/compose/{ => ui/method_picker}/AuthMethodPickerTest.kt (91%) diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/AuthProviderButtonTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButtonTest.kt similarity index 99% rename from auth/src/test/java/com/firebase/ui/auth/compose/AuthProviderButtonTest.kt rename to auth/src/test/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButtonTest.kt index 1bd889fc5..faae2cf48 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/AuthProviderButtonTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButtonTest.kt @@ -12,7 +12,7 @@ * limitations under the License. */ -package com.firebase.ui.auth.compose +package com.firebase.ui.auth.compose.ui.components import android.content.Context import androidx.compose.material.icons.Icons @@ -41,8 +41,6 @@ import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import com.firebase.ui.auth.R -import com.firebase.ui.auth.compose.ui.components.AuthProviderButton -import com.firebase.ui.auth.compose.ui.components.resolveProviderStyle /** * Unit tests for [AuthProviderButton] covering UI interactions, styling, diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/ErrorRecoveryDialogLogicTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/ui/components/ErrorRecoveryDialogLogicTest.kt similarity index 72% rename from auth/src/test/java/com/firebase/ui/auth/compose/ErrorRecoveryDialogLogicTest.kt rename to auth/src/test/java/com/firebase/ui/auth/compose/ui/components/ErrorRecoveryDialogLogicTest.kt index 480e4ea14..6a4f5df2f 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/ErrorRecoveryDialogLogicTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/ui/components/ErrorRecoveryDialogLogicTest.kt @@ -1,49 +1,35 @@ -/* - * Copyright 2025 Google Inc. All Rights Reserved. - * - * 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.firebase.ui.auth.compose +package com.firebase.ui.auth.compose.ui.components +import com.firebase.ui.auth.compose.AuthException import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.mock -import org.mockito.Mockito.`when` +import org.mockito.Mockito import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config /** - * Unit tests for [com.firebase.ui.auth.compose.ui.components.ErrorRecoveryDialog] logic functions. + * Unit tests for [ErrorRecoveryDialog] logic functions. */ @RunWith(RobolectricTestRunner::class) @Config(manifest = Config.NONE) class ErrorRecoveryDialogLogicTest { - private val mockStringProvider = mock(AuthUIStringProvider::class.java).apply { - `when`(retryAction).thenReturn("Try again") - `when`(continueText).thenReturn("Continue") - `when`(signInDefault).thenReturn("Sign in") - `when`(networkErrorRecoveryMessage).thenReturn("Network error, check your internet connection.") - `when`(invalidCredentialsRecoveryMessage).thenReturn("Incorrect password.") - `when`(userNotFoundRecoveryMessage).thenReturn("That email address doesn't match an existing account") - `when`(weakPasswordRecoveryMessage).thenReturn("Password not strong enough. Use at least 6 characters and a mix of letters and numbers") - `when`(emailAlreadyInUseRecoveryMessage).thenReturn("Email account registration unsuccessful") - `when`(tooManyRequestsRecoveryMessage).thenReturn("This phone number has been used too many times") - `when`(mfaRequiredRecoveryMessage).thenReturn("Additional verification required. Please complete multi-factor authentication.") - `when`(accountLinkingRequiredRecoveryMessage).thenReturn("Account needs to be linked. Please try a different sign-in method.") - `when`(authCancelledRecoveryMessage).thenReturn("Authentication was cancelled. Please try again when ready.") - `when`(unknownErrorRecoveryMessage).thenReturn("An unknown error occurred.") + private val mockStringProvider = Mockito.mock(AuthUIStringProvider::class.java).apply { + Mockito.`when`(retryAction).thenReturn("Try again") + Mockito.`when`(continueText).thenReturn("Continue") + Mockito.`when`(signInDefault).thenReturn("Sign in") + Mockito.`when`(networkErrorRecoveryMessage).thenReturn("Network error, check your internet connection.") + Mockito.`when`(invalidCredentialsRecoveryMessage).thenReturn("Incorrect password.") + Mockito.`when`(userNotFoundRecoveryMessage).thenReturn("That email address doesn't match an existing account") + Mockito.`when`(weakPasswordRecoveryMessage).thenReturn("Password not strong enough. Use at least 6 characters and a mix of letters and numbers") + Mockito.`when`(emailAlreadyInUseRecoveryMessage).thenReturn("Email account registration unsuccessful") + Mockito.`when`(tooManyRequestsRecoveryMessage).thenReturn("This phone number has been used too many times") + Mockito.`when`(mfaRequiredRecoveryMessage).thenReturn("Additional verification required. Please complete multi-factor authentication.") + Mockito.`when`(accountLinkingRequiredRecoveryMessage).thenReturn("Account needs to be linked. Please try a different sign-in method.") + Mockito.`when`(authCancelledRecoveryMessage).thenReturn("Authentication was cancelled. Please try again when ready.") + Mockito.`when`(unknownErrorRecoveryMessage).thenReturn("An unknown error occurred.") } // ============================================================================================= @@ -59,7 +45,7 @@ class ErrorRecoveryDialogLogicTest { val message = getRecoveryMessage(error, mockStringProvider) // Assert - assertThat(message).isEqualTo("Network error, check your internet connection.") + Truth.assertThat(message).isEqualTo("Network error, check your internet connection.") } @Test @@ -71,7 +57,7 @@ class ErrorRecoveryDialogLogicTest { val message = getRecoveryMessage(error, mockStringProvider) // Assert - assertThat(message).isEqualTo("Incorrect password.") + Truth.assertThat(message).isEqualTo("Incorrect password.") } @Test @@ -83,7 +69,7 @@ class ErrorRecoveryDialogLogicTest { val message = getRecoveryMessage(error, mockStringProvider) // Assert - assertThat(message).isEqualTo("That email address doesn't match an existing account") + Truth.assertThat(message).isEqualTo("That email address doesn't match an existing account") } @Test @@ -99,7 +85,7 @@ class ErrorRecoveryDialogLogicTest { val message = getRecoveryMessage(error, mockStringProvider) // Assert - assertThat(message).isEqualTo("Password not strong enough. Use at least 6 characters and a mix of letters and numbers\n\nReason: Password should be at least 8 characters") + Truth.assertThat(message).isEqualTo("Password not strong enough. Use at least 6 characters and a mix of letters and numbers\n\nReason: Password should be at least 8 characters") } @Test @@ -111,7 +97,7 @@ class ErrorRecoveryDialogLogicTest { val message = getRecoveryMessage(error, mockStringProvider) // Assert - assertThat(message).isEqualTo("Password not strong enough. Use at least 6 characters and a mix of letters and numbers") + Truth.assertThat(message).isEqualTo("Password not strong enough. Use at least 6 characters and a mix of letters and numbers") } @Test @@ -127,7 +113,7 @@ class ErrorRecoveryDialogLogicTest { val message = getRecoveryMessage(error, mockStringProvider) // Assert - assertThat(message).isEqualTo("Email account registration unsuccessful (test@example.com)") + Truth.assertThat(message).isEqualTo("Email account registration unsuccessful (test@example.com)") } @Test @@ -139,7 +125,7 @@ class ErrorRecoveryDialogLogicTest { val message = getRecoveryMessage(error, mockStringProvider) // Assert - assertThat(message).isEqualTo("Email account registration unsuccessful") + Truth.assertThat(message).isEqualTo("Email account registration unsuccessful") } // ============================================================================================= @@ -155,7 +141,7 @@ class ErrorRecoveryDialogLogicTest { val actionText = getRecoveryActionText(error, mockStringProvider) // Assert - assertThat(actionText).isEqualTo("Try again") + Truth.assertThat(actionText).isEqualTo("Try again") } @Test @@ -167,7 +153,7 @@ class ErrorRecoveryDialogLogicTest { val actionText = getRecoveryActionText(error, mockStringProvider) // Assert - assertThat(actionText).isEqualTo("Continue") + Truth.assertThat(actionText).isEqualTo("Continue") } @Test @@ -179,7 +165,7 @@ class ErrorRecoveryDialogLogicTest { val actionText = getRecoveryActionText(error, mockStringProvider) // Assert - assertThat(actionText).isEqualTo("Sign in") + Truth.assertThat(actionText).isEqualTo("Sign in") } @Test @@ -191,7 +177,7 @@ class ErrorRecoveryDialogLogicTest { val actionText = getRecoveryActionText(error, mockStringProvider) // Assert - assertThat(actionText).isEqualTo("Continue") + Truth.assertThat(actionText).isEqualTo("Continue") } @Test @@ -203,7 +189,7 @@ class ErrorRecoveryDialogLogicTest { val actionText = getRecoveryActionText(error, mockStringProvider) // Assert - assertThat(actionText).isEqualTo("Continue") + Truth.assertThat(actionText).isEqualTo("Continue") } // ============================================================================================= @@ -216,7 +202,7 @@ class ErrorRecoveryDialogLogicTest { val error = AuthException.NetworkException("Network error") // Act & Assert - assertThat(isRecoverable(error)).isTrue() + Truth.assertThat(isRecoverable(error)).isTrue() } @Test @@ -225,7 +211,7 @@ class ErrorRecoveryDialogLogicTest { val error = AuthException.InvalidCredentialsException("Invalid credentials") // Act & Assert - assertThat(isRecoverable(error)).isTrue() + Truth.assertThat(isRecoverable(error)).isTrue() } @Test @@ -234,7 +220,7 @@ class ErrorRecoveryDialogLogicTest { val error = AuthException.TooManyRequestsException("Too many requests") // Act & Assert - assertThat(isRecoverable(error)).isFalse() + Truth.assertThat(isRecoverable(error)).isFalse() } @Test @@ -243,7 +229,7 @@ class ErrorRecoveryDialogLogicTest { val error = AuthException.MfaRequiredException("MFA required") // Act & Assert - assertThat(isRecoverable(error)).isTrue() + Truth.assertThat(isRecoverable(error)).isTrue() } @Test @@ -252,7 +238,7 @@ class ErrorRecoveryDialogLogicTest { val error = AuthException.UnknownException("Unknown error") // Act & Assert - assertThat(isRecoverable(error)).isTrue() + Truth.assertThat(isRecoverable(error)).isTrue() } // Helper functions to test the private functions - we need to make them internal for testing diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/AuthMethodPickerTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPickerTest.kt similarity index 91% rename from auth/src/test/java/com/firebase/ui/auth/compose/AuthMethodPickerTest.kt rename to auth/src/test/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPickerTest.kt index 3c9f383bc..17d736ca7 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/AuthMethodPickerTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPickerTest.kt @@ -1,18 +1,4 @@ -/* - * Copyright 2025 Google Inc. All Rights Reserved. - * - * 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.firebase.ui.auth.compose +package com.firebase.ui.auth.compose.ui.method_picker import android.content.Context import androidx.compose.material3.Button @@ -31,8 +17,7 @@ import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R import com.firebase.ui.auth.compose.configuration.AuthProvider import com.firebase.ui.auth.compose.configuration.theme.AuthUIAsset -import com.firebase.ui.auth.compose.ui.method_picker.AuthMethodPicker -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth import org.junit.Before import org.junit.Rule import org.junit.Test @@ -202,7 +187,7 @@ class AuthMethodPickerTest { .onNodeWithText(context.getString(R.string.fui_sign_in_with_google)) .performClick() - assertThat(selectedProvider).isEqualTo(googleProvider) + Truth.assertThat(selectedProvider).isEqualTo(googleProvider) } // ============================================================================================= @@ -227,7 +212,7 @@ class AuthMethodPickerTest { ) } - assertThat(customLayoutCalled).isTrue() + Truth.assertThat(customLayoutCalled).isTrue() composeTestRule .onNodeWithText("Custom Layout") .assertIsDisplayed() @@ -251,7 +236,7 @@ class AuthMethodPickerTest { ) } - assertThat(receivedProviders).isEqualTo(providers) + Truth.assertThat(receivedProviders).isEqualTo(providers) } @Test @@ -275,7 +260,7 @@ class AuthMethodPickerTest { .onNodeWithText("Custom Button") .performClick() - assertThat(selectedProvider).isEqualTo(googleProvider) + Truth.assertThat(selectedProvider).isEqualTo(googleProvider) } // ============================================================================================= From 93ba05ceb994cbb6ba14f14206e010f91c390d0f Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Thu, 2 Oct 2025 13:27:27 +0100 Subject: [PATCH 5/6] fix: use screen width for adaptive padding values --- .../ui/components/AuthProviderButton.kt | 7 +- .../ui/method_picker/AuthMethodPicker.kt | 137 ++++++++++-------- 2 files changed, 78 insertions(+), 66 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButton.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButton.kt index e23d98fc7..255d6c59e 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButton.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthProviderButton.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -69,8 +70,7 @@ fun AuthProviderButton( val providerLabel = resolveProviderLabel(provider, stringProvider) Button( - modifier = modifier - .width(208.dp), + modifier = modifier, contentPadding = PaddingValues(horizontal = 12.dp), colors = ButtonDefaults.buttonColors( containerColor = providerStyle.backgroundColor, @@ -84,8 +84,7 @@ fun AuthProviderButton( enabled = enabled, ) { Row( - modifier = Modifier - .fillMaxWidth(), + modifier = modifier, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start ) { diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt index 91aedc2fd..279eec4e1 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt @@ -2,11 +2,14 @@ package com.firebase.ui.auth.compose.ui.method_picker import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -61,14 +64,12 @@ fun AuthMethodPicker( Column( modifier = modifier - .fillMaxSize() - .safeDrawingPadding(), - horizontalAlignment = Alignment.CenterHorizontally ) { logo?.let { Image( modifier = Modifier - .weight(0.4f), + .weight(0.4f) + .align(Alignment.CenterHorizontally), painter = it.painter, contentDescription = if (inPreview) "" else stringResource(R.string.fui_auth_method_picker_logo) @@ -77,26 +78,33 @@ fun AuthMethodPicker( if (customLayout != null) { customLayout(providers, onProviderSelected) } else { - LazyColumn( + BoxWithConstraints( modifier = Modifier - .fillMaxSize() - .weight(1f) - .testTag("AuthMethodPicker LazyColumn"), - horizontalAlignment = Alignment.CenterHorizontally, + .weight(1f), ) { - items(providers.size) { index -> - val provider = providers[index] - Box( - modifier = Modifier - .padding(bottom = 16.dp) - ) { - AuthProviderButton( - onClick = { - onProviderSelected(provider) - }, - provider = provider, - stringProvider = DefaultAuthUIStringProvider(context) - ) + val paddingWidth = maxWidth.value * 0.23 + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = paddingWidth.dp) + .testTag("AuthMethodPicker LazyColumn"), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + itemsIndexed(providers) { index, provider -> + Box( + modifier = Modifier + .padding(bottom = if (index < providers.lastIndex) 16.dp else 0.dp) + ) { + AuthProviderButton( + modifier = Modifier + .fillMaxWidth(), + onClick = { + onProviderSelected(provider) + }, + provider = provider, + stringProvider = DefaultAuthUIStringProvider(context) + ) + } } } } @@ -118,46 +126,51 @@ fun AuthMethodPicker( @Preview(showBackground = true) @Composable fun PreviewAuthMethodPicker() { - AuthMethodPicker( - providers = listOf( - AuthProvider.Email( - actionCodeSettings = null, - passwordValidationRules = emptyList() - ), - AuthProvider.Phone( - defaultNumber = null, - defaultCountryCode = null, - allowedCountries = null, - ), - AuthProvider.Google( - scopes = emptyList(), - serverClientId = null - ), - AuthProvider.Facebook(), - AuthProvider.Twitter( - customParameters = emptyMap() - ), - AuthProvider.Github( - customParameters = emptyMap() - ), - AuthProvider.Microsoft( - tenant = null, - customParameters = emptyMap() - ), - AuthProvider.Yahoo( - customParameters = emptyMap() - ), - AuthProvider.Apple( - locale = null, - customParameters = emptyMap() + Column( + modifier = Modifier + .fillMaxSize() + ) { + AuthMethodPicker( + providers = listOf( + AuthProvider.Email( + actionCodeSettings = null, + passwordValidationRules = emptyList() + ), + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = null, + ), + AuthProvider.Google( + scopes = emptyList(), + serverClientId = null + ), + AuthProvider.Facebook(), + AuthProvider.Twitter( + customParameters = emptyMap() + ), + AuthProvider.Github( + customParameters = emptyMap() + ), + AuthProvider.Microsoft( + tenant = null, + customParameters = emptyMap() + ), + AuthProvider.Yahoo( + customParameters = emptyMap() + ), + AuthProvider.Apple( + locale = null, + customParameters = emptyMap() + ), + AuthProvider.Anonymous, ), - AuthProvider.Anonymous, - ), - logo = AuthUIAsset.Resource(R.drawable.fui_ic_check_circle_black_128dp), - onProviderSelected = { provider -> + logo = AuthUIAsset.Resource(R.drawable.fui_ic_check_circle_black_128dp), + onProviderSelected = { provider -> - }, - termsOfServiceUrl = "https://example.com/terms", - privacyPolicyUrl = "https://example.com/privacy" - ) + }, + termsOfServiceUrl = "https://example.com/terms", + privacyPolicyUrl = "https://example.com/privacy" + ) + } } \ No newline at end of file From 287bc8b5abf189ddb4505f850fdba15ef7c125bf Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Thu, 2 Oct 2025 13:38:04 +0100 Subject: [PATCH 6/6] chore: remove unused modifier --- .../ui/auth/compose/ui/method_picker/AuthMethodPicker.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt index 279eec4e1..855e3d6b3 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/method_picker/AuthMethodPicker.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable @@ -85,7 +84,6 @@ fun AuthMethodPicker( val paddingWidth = maxWidth.value * 0.23 LazyColumn( modifier = Modifier - .fillMaxSize() .padding(horizontal = paddingWidth.dp) .testTag("AuthMethodPicker LazyColumn"), horizontalAlignment = Alignment.CenterHorizontally,