Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
android:label="Custom Method Picker Layout"
android:exported="false"
android:theme="@style/Theme.FirebaseUIAndroid" />

</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -20,13 +21,18 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand All @@ -45,6 +51,7 @@ import com.firebase.ui.auth.configuration.theme.AuthUIAsset
import com.firebase.ui.auth.configuration.theme.AuthUITheme
import com.firebase.ui.auth.configuration.theme.ProviderStyleDefaults
import com.firebase.ui.auth.ui.components.AuthProviderButton
import com.firebase.ui.auth.ui.method_picker.MethodPickerTermsConfiguration
import com.firebase.ui.auth.ui.screens.FirebaseAuthScreen

class CustomMethodPickerDemoActivity : ComponentActivity() {
Expand Down Expand Up @@ -119,6 +126,8 @@ class CustomMethodPickerDemoActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
var termsAccepted by remember { mutableStateOf(false) }

FirebaseAuthScreen(
configuration = configuration,
authUI = authUI,
Expand All @@ -134,9 +143,30 @@ class CustomMethodPickerDemoActivity : ComponentActivity() {
customMethodPickerLayout = { providers, onProviderSelected ->
SpotlightMethodPicker(
providers = providers,
onProviderSelected = onProviderSelected
onProviderSelected = onProviderSelected,
enabled = termsAccepted
)
}
},
customMethodPickerTermsConfiguration = MethodPickerTermsConfiguration(
content = {
Row(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = termsAccepted,
onCheckedChange = { termsAccepted = it }
)
Text(
text = "I have read and accept the Terms of Service and Privacy Policy",
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 8.dp)
)
}
},
accepted = termsAccepted,
disableProvidersUntilAccepted = true,
),
)
}
}
Expand All @@ -148,6 +178,7 @@ class CustomMethodPickerDemoActivity : ComponentActivity() {
fun SpotlightMethodPicker(
providers: List<AuthProvider>,
onProviderSelected: (AuthProvider) -> Unit,
enabled: Boolean = true,
) {
val stringProvider = LocalAuthUIStringProvider.current

Expand Down Expand Up @@ -195,6 +226,7 @@ fun SpotlightMethodPicker(
.padding(horizontal = 32.dp),
provider = provider,
onClick = { onProviderSelected(provider) },
enabled = enabled,
stringProvider = stringProvider
)
}
Expand All @@ -219,6 +251,7 @@ fun SpotlightMethodPicker(
ProviderIconButton(
style = style,
contentDescription = provider.providerId,
enabled = enabled,
onClick = { onProviderSelected(provider) }
)
}
Expand All @@ -242,6 +275,7 @@ fun SpotlightMethodPicker(
.padding(horizontal = 32.dp),
provider = provider,
onClick = { onProviderSelected(provider) },
enabled = enabled,
stringProvider = stringProvider
)
}
Expand All @@ -250,7 +284,7 @@ fun SpotlightMethodPicker(
anonymous?.let {
item {
Spacer(modifier = Modifier.height(8.dp))
TextButton(onClick = { onProviderSelected(it) }) {
TextButton(onClick = { onProviderSelected(it) }, enabled = enabled) {
Text("Continue as guest")
}
}
Expand All @@ -263,9 +297,11 @@ private fun ProviderIconButton(
style: AuthUITheme.ProviderStyle,
contentDescription: String,
onClick: () -> Unit,
enabled: Boolean = true,
) {
Button(
onClick = onClick,
enabled = enabled,
modifier = Modifier.size(52.dp),
shape = CircleShape,
colors = ButtonDefaults.buttonColors(containerColor = style.backgroundColor),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ fun CustomSlotsDemoChooser(
)

DemoCard(
title = "Custom Method Picker Layout",
description = "Replace the default vertical provider list with a 2-column grid using customMethodPickerLayout on FirebaseAuthScreen.",
title = "Custom Method Picker Layout & Terms",
description = "Replace the default provider list with a custom layout, and swap the 'By continuing...' footer with a checkbox using customMethodPickerLayout and customMethodPickerTermsConfiguration on FirebaseAuthScreen.",
onClick = onCustomMethodPickerClick
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ import com.firebase.ui.auth.configuration.theme.AuthUIAsset
import com.firebase.ui.auth.ui.components.AuthProviderButton
import com.firebase.ui.auth.util.SignInPreferenceManager

/**
* Configuration for a custom Terms of Service/Privacy Policy footer in [AuthMethodPicker].
*
* @param content A composable that replaces the default "By continuing..." footer. Use this to
* supply a checkbox or any custom consent UI.
* @param accepted The current acceptance state. Only used when [disableProvidersUntilAccepted]
* is true.
* @param disableProvidersUntilAccepted When true, provider buttons are disabled until [accepted]
* is true. Defaults to false — buttons remain enabled unless explicitly opted in.
*
* @since 10.0.0
*/
class MethodPickerTermsConfiguration(
val content: @Composable () -> Unit,
val accepted: Boolean = true,
val disableProvidersUntilAccepted: Boolean = false,
)

/**
* Renders the provider selection screen.
*
Expand All @@ -68,6 +86,8 @@ import com.firebase.ui.auth.util.SignInPreferenceManager
* @param termsOfServiceUrl The URL for the Terms of Service.
* @param privacyPolicyUrl The URL for the Privacy Policy.
* @param lastSignInPreference The last sign-in preference to show a "Continue as..." button.
* @param termsConfiguration Optional configuration for a custom ToS/Privacy Policy footer.
* When provided, replaces the default "By continuing..." text. See [MethodPickerTermsConfiguration].
*
* @since 10.0.0
*/
Expand All @@ -81,10 +101,14 @@ fun AuthMethodPicker(
privacyPolicyUrl: String? = null,
lastSignInPreference: SignInPreferenceManager.SignInPreference? = null,
customLayout: (@Composable (List<AuthProvider>, (AuthProvider) -> Unit) -> Unit)? = null,
termsConfiguration: MethodPickerTermsConfiguration? = null,
) {
val context = LocalContext.current
val inPreview = LocalInspectionMode.current
val stringProvider = LocalAuthUIStringProvider.current
val providerButtonsEnabled = termsConfiguration == null ||
!termsConfiguration.disableProvidersUntilAccepted ||
termsConfiguration.accepted

Column(
modifier = modifier
Expand All @@ -100,7 +124,9 @@ fun AuthMethodPicker(
)
}
if (customLayout != null) {
customLayout(providers, onProviderSelected)
Box(modifier = Modifier.weight(1f)) {
customLayout(providers, onProviderSelected)
}
} else {
BoxWithConstraints(
modifier = Modifier
Expand All @@ -121,6 +147,7 @@ fun AuthMethodPicker(
ContinueAsButton(
provider = lastProvider,
identifier = preference.identifier,
enabled = providerButtonsEnabled,
onClick = { onProviderSelected(lastProvider) }
)
Spacer(modifier = Modifier.height(24.dp))
Expand Down Expand Up @@ -155,6 +182,7 @@ fun AuthMethodPicker(
onClick = {
onProviderSelected(provider)
},
enabled = providerButtonsEnabled,
provider = provider,
stringProvider = LocalAuthUIStringProvider.current
)
Expand All @@ -163,20 +191,24 @@ fun AuthMethodPicker(
}
}
}
AnnotatedStringResource(
modifier = Modifier.padding(vertical = 16.dp, horizontal = 16.dp),
context = context,
inPreview = inPreview,
previewText = "By continuing, you accept our Terms of Service and Privacy Policy.",
text = stringProvider.tosAndPrivacyPolicy(
termsOfServiceLabel = stringProvider.termsOfService,
privacyPolicyLabel = stringProvider.privacyPolicy
),
links = arrayOf(
stringProvider.termsOfService to (termsOfServiceUrl ?: ""),
stringProvider.privacyPolicy to (privacyPolicyUrl ?: "")
if (termsConfiguration != null) {
termsConfiguration.content()
} else {
AnnotatedStringResource(
modifier = Modifier.padding(vertical = 16.dp, horizontal = 16.dp),
context = context,
inPreview = inPreview,
previewText = "By continuing, you accept our Terms of Service and Privacy Policy.",
text = stringProvider.tosAndPrivacyPolicy(
termsOfServiceLabel = stringProvider.termsOfService,
privacyPolicyLabel = stringProvider.privacyPolicy
),
links = arrayOf(
stringProvider.termsOfService to (termsOfServiceUrl ?: ""),
stringProvider.privacyPolicy to (privacyPolicyUrl ?: "")
)
)
)
}
}
}

Expand All @@ -191,6 +223,7 @@ fun AuthMethodPicker(
private fun ContinueAsButton(
provider: AuthProvider,
identifier: String?,
enabled: Boolean = true,
onClick: () -> Unit
) {
val stringProvider = LocalAuthUIStringProvider.current
Expand All @@ -200,6 +233,7 @@ private fun ContinueAsButton(
.fillMaxWidth()
.testTag("ContinueAsButton"),
onClick = onClick,
enabled = enabled,
provider = provider,
stringProvider = stringProvider,
subtitle = identifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import com.firebase.ui.auth.ui.components.rememberTopLevelDialogController
import com.firebase.ui.auth.mfa.MfaChallengeContentState
import com.firebase.ui.auth.mfa.MfaEnrollmentContentState
import com.firebase.ui.auth.ui.method_picker.AuthMethodPicker
import com.firebase.ui.auth.ui.method_picker.MethodPickerTermsConfiguration
import com.firebase.ui.auth.ui.screens.email.EmailAuthContentState
import com.firebase.ui.auth.ui.screens.email.EmailAuthScreen
import com.firebase.ui.auth.ui.screens.phone.PhoneAuthContentState
Expand Down Expand Up @@ -112,6 +113,7 @@ fun FirebaseAuthScreen(
emailLink: String? = null,
mfaConfiguration: MfaConfiguration = MfaConfiguration(),
customMethodPickerLayout: (@Composable (List<AuthProvider>, (AuthProvider) -> Unit) -> Unit)? = null,
customMethodPickerTermsConfiguration: MethodPickerTermsConfiguration? = null,
emailContent: (@Composable (EmailAuthContentState) -> Unit)? = null,
phoneContent: (@Composable (PhoneAuthContentState) -> Unit)? = null,
mfaEnrollmentContent: (@Composable (MfaEnrollmentContentState) -> Unit)? = null,
Expand Down Expand Up @@ -277,6 +279,7 @@ fun FirebaseAuthScreen(
privacyPolicyUrl = configuration.privacyPolicyUrl,
lastSignInPreference = lastSignInPreference.value,
customLayout = customMethodPickerLayout,
termsConfiguration = customMethodPickerTermsConfiguration,
onProviderSelected = { provider ->
when (provider) {
is AuthProvider.Anonymous -> onSignInAnonymously?.invoke()
Expand Down
Loading
Loading