Skip to content
223 changes: 114 additions & 109 deletions app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -70,10 +71,6 @@ class HighLevelApiDemoActivity : ComponentActivity() {
val authUI = FirebaseAuthUI.getInstance()
val emailLink = intent.getStringExtra(EmailLinkConstants.EXTRA_EMAIL_LINK)

val customTheme = AuthUITheme.Default.copy(
providerButtonShape = ShapeDefaults.ExtraLarge
)

class CustomAuthUIStringProvider(
private val defaultProvider: AuthUIStringProvider
) : AuthUIStringProvider by defaultProvider {
Expand All @@ -85,124 +82,132 @@ class HighLevelApiDemoActivity : ComponentActivity() {
val customStringProvider =
CustomAuthUIStringProvider(DefaultAuthUIStringProvider(applicationContext))

val configuration = authUIConfiguration {
context = applicationContext
theme = customTheme
logo = AuthUIAsset.Resource(R.drawable.firebase_auth)
tosUrl = "https://policies.google.com/terms"
privacyPolicyUrl = "https://policies.google.com/privacy"
isAnonymousUpgradeEnabled = false
isMfaEnabled = false
stringProvider = customStringProvider
transitions = AuthUITransitions(
enterTransition = { slideInHorizontally { it } },
exitTransition = { slideOutHorizontally { -it } },
popEnterTransition = { slideInHorizontally { -it } },
popExitTransition = { slideOutHorizontally { it } }
setContent {
val customTheme = AuthUITheme.Adaptive.copy(
providerButtonShape = ShapeDefaults.ExtraLarge,
topAppBarColors = TopAppBarDefaults.topAppBarColors(
containerColor = Color(0xFFFFA000),
scrolledContainerColor = Color(0xFFFFA000),
)
)
providers {
provider(AuthProvider.Anonymous)
provider(
AuthProvider.Google(
scopes = listOf("email"),
serverClientId = "406099696497-a12gakvts4epfk5pkio7dphc1anjiggc.apps.googleusercontent.com",
)

val configuration = authUIConfiguration {
context = applicationContext
theme = customTheme
logo = AuthUIAsset.Resource(R.drawable.firebase_auth)
tosUrl = "https://policies.google.com/terms"
privacyPolicyUrl = "https://policies.google.com/privacy"
isAnonymousUpgradeEnabled = false
isMfaEnabled = false
stringProvider = customStringProvider
transitions = AuthUITransitions(
enterTransition = { slideInHorizontally { it } },
exitTransition = { slideOutHorizontally { -it } },
popEnterTransition = { slideInHorizontally { -it } },
popExitTransition = { slideOutHorizontally { it } }
)
provider(
AuthProvider.Email(
isDisplayNameRequired = true,
isEmailLinkForceSameDeviceEnabled = false,
isEmailLinkSignInEnabled = true,
emailLinkActionCodeSettings = actionCodeSettings {
url = "https://flutterfire-e2e-tests.firebaseapp.com"
handleCodeInApp = true
setAndroidPackageName(
"com.firebaseui.android.demo",
true,
null
)
},
isNewAccountsAllowed = true,
minimumPasswordLength = 8,
passwordValidationRules = listOf(
PasswordRule.MinimumLength(8),
PasswordRule.RequireLowercase,
PasswordRule.RequireUppercase,
),
providers {
provider(AuthProvider.Anonymous)
provider(
AuthProvider.Google(
scopes = listOf("email"),
serverClientId = "406099696497-a12gakvts4epfk5pkio7dphc1anjiggc.apps.googleusercontent.com",
)
)
)
provider(
AuthProvider.Phone(
defaultNumber = null,
defaultCountryCode = null,
allowedCountries = emptyList(),
smsCodeLength = 6,
timeout = 120L,
isInstantVerificationEnabled = true
provider(
AuthProvider.Email(
isDisplayNameRequired = true,
isEmailLinkForceSameDeviceEnabled = false,
isEmailLinkSignInEnabled = true,
emailLinkActionCodeSettings = actionCodeSettings {
url = "https://flutterfire-e2e-tests.firebaseapp.com"
handleCodeInApp = true
setAndroidPackageName(
"com.firebaseui.android.demo",
true,
null
)
},
isNewAccountsAllowed = true,
minimumPasswordLength = 8,
passwordValidationRules = listOf(
PasswordRule.MinimumLength(8),
PasswordRule.RequireLowercase,
PasswordRule.RequireUppercase,
),
)
)
)
provider(
AuthProvider.Facebook()
)
provider(
AuthProvider.Twitter(
customParameters = emptyMap()
provider(
AuthProvider.Phone(
defaultNumber = null,
defaultCountryCode = null,
allowedCountries = emptyList(),
smsCodeLength = 6,
timeout = 120L,
isInstantVerificationEnabled = true
)
)
)
provider(
AuthProvider.Apple(
customParameters = emptyMap(),
locale = null
provider(
AuthProvider.Facebook()
)
)
provider(
AuthProvider.Microsoft(
scopes = emptyList(),
tenant = "",
customParameters = emptyMap(),
provider(
AuthProvider.Twitter(
customParameters = emptyMap()
)
)
)
provider(
AuthProvider.Github(
scopes = emptyList(),
customParameters = emptyMap(),
provider(
AuthProvider.Apple(
customParameters = emptyMap(),
locale = null
)
)
)
provider(
AuthProvider.Yahoo(
scopes = emptyList(),
customParameters = emptyMap(),
provider(
AuthProvider.Microsoft(
scopes = emptyList(),
tenant = "",
customParameters = emptyMap(),
)
)
)
provider(
AuthProvider.GenericOAuth(
providerName = "LINE",
providerId = "oidc.line",
scopes = emptyList(),
customParameters = emptyMap(),
buttonLabel = "Sign in with LINE",
buttonIcon = AuthUIAsset.Resource(R.drawable.ic_line_logo_24dp),
buttonColor = Color(0xFF06C755),
contentColor = Color.White
provider(
AuthProvider.Github(
scopes = emptyList(),
customParameters = emptyMap(),
)
)
)
provider(
AuthProvider.GenericOAuth(
providerName = "Discord",
providerId = "oidc.discord",
scopes = emptyList(),
customParameters = emptyMap(),
buttonLabel = "Sign in with Discord",
buttonIcon = AuthUIAsset.Resource(R.drawable.ic_discord_24dp),
buttonColor = Color(0xFF5865F2),
contentColor = Color.White
provider(
AuthProvider.Yahoo(
scopes = emptyList(),
customParameters = emptyMap(),
)
)
)
provider(
AuthProvider.GenericOAuth(
providerName = "LINE",
providerId = "oidc.line",
scopes = emptyList(),
customParameters = emptyMap(),
buttonLabel = "Sign in with LINE",
buttonIcon = AuthUIAsset.Resource(R.drawable.ic_line_logo_24dp),
buttonColor = Color(0xFF06C755),
contentColor = Color.White
)
)
provider(
AuthProvider.GenericOAuth(
providerName = "Discord",
providerId = "oidc.discord",
scopes = emptyList(),
customParameters = emptyMap(),
buttonLabel = "Sign in with Discord",
buttonIcon = AuthUIAsset.Resource(R.drawable.ic_discord_24dp),
buttonColor = Color(0xFF5865F2),
contentColor = Color.White
)
)
}
}
}

setContent {
AuthUITheme {
AuthUITheme(theme = customTheme) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
Expand Down
20 changes: 20 additions & 0 deletions auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,26 @@ val configuration = authUIConfiguration {
}
```

### Customizing the Top App Bar

Override the colors used by the top app bar shown on auth screens:

```kotlin
val customTheme = AuthUITheme.Default.copy(
topAppBarColors = TopAppBarDefaults.topAppBarColors(
containerColor = Color(0xFF2E7D32),
scrolledContainerColor = Color(0xFF2E7D32),
)
)

val configuration = authUIConfiguration {
providers { provider(AuthProvider.Email()) }
theme = customTheme
}
```

If left unset (`null`), the top app bar falls back to colors derived from `colorScheme`'s `primary`/`onPrimary`.

### Screen Transitions

Customize the animations when navigating between screens using the `AuthUITransitions` object:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ package com.firebase.ui.auth.configuration.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
Expand Down Expand Up @@ -81,6 +81,12 @@ class AuthUITheme(
* ```
*/
val providerButtonShape: Shape? = null,

/**
* Custom colors for the top app bar shown on auth screens. If null, falls back to
* colors derived from [colorScheme] (see [AuthUITheme.topAppBarColors]).
*/
val topAppBarColors: TopAppBarColors? = null,
Comment thread
demolaf marked this conversation as resolved.
Comment thread
demolaf marked this conversation as resolved.
) {

/**
Expand All @@ -91,6 +97,7 @@ class AuthUITheme(
* @param shapes The shapes to use. Defaults to this theme's shapes.
* @param providerStyles Custom styling for individual providers. Defaults to this theme's provider styles.
* @param providerButtonShape Default shape for provider buttons. Defaults to this theme's provider button shape.
* @param topAppBarColors Custom top app bar colors. Defaults to this theme's top app bar colors.
* @return A new AuthUITheme instance with the specified properties.
*/
fun copy(
Expand All @@ -99,13 +106,15 @@ class AuthUITheme(
shapes: Shapes = this.shapes,
providerStyles: Map<String, ProviderStyle> = this.providerStyles,
providerButtonShape: Shape? = this.providerButtonShape,
topAppBarColors: TopAppBarColors? = this.topAppBarColors,
): AuthUITheme {
return AuthUITheme(
colorScheme = colorScheme,
typography = typography,
shapes = shapes,
providerStyles = providerStyles,
providerButtonShape = providerButtonShape
providerButtonShape = providerButtonShape,
topAppBarColors = topAppBarColors
)
}

Expand All @@ -118,6 +127,7 @@ class AuthUITheme(
if (shapes != other.shapes) return false
if (providerStyles != other.providerStyles) return false
if (providerButtonShape != other.providerButtonShape) return false
if (topAppBarColors != other.topAppBarColors) return false

return true
}
Expand All @@ -128,12 +138,14 @@ class AuthUITheme(
result = 31 * result + shapes.hashCode()
result = 31 * result + providerStyles.hashCode()
result = 31 * result + (providerButtonShape?.hashCode() ?: 0)
result = 31 * result + (topAppBarColors?.hashCode() ?: 0)
return result
}

override fun toString(): String {
return "AuthUITheme(colorScheme=$colorScheme, typography=$typography, shapes=$shapes, " +
"providerStyles=$providerStyles, providerButtonShape=$providerButtonShape)"
"providerStyles=$providerStyles, providerButtonShape=$providerButtonShape, " +
"topAppBarColors=$topAppBarColors)"
}

/**
Expand Down Expand Up @@ -228,14 +240,21 @@ class AuthUITheme(
)
}

@OptIn(ExperimentalMaterial3Api::class)
@get:Composable
val topAppBarColors
get() = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
)

/**
* Resolves the top app bar colors to use for the current [LocalAuthUITheme], falling back
* to [topAppBarColors] when the current theme doesn't specify its own.
*/
@get:Composable
val resolvedTopAppBarColors: TopAppBarColors
get() = LocalAuthUITheme.current.topAppBarColors ?: topAppBarColors
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import com.firebase.ui.auth.configuration.AuthUIConfiguration
import com.firebase.ui.auth.configuration.MfaFactor
import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider
import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider
import com.firebase.ui.auth.configuration.theme.AuthUITheme
import com.firebase.ui.auth.mfa.MfaEnrollmentContentState
import com.firebase.ui.auth.mfa.MfaEnrollmentStep
import com.firebase.ui.auth.mfa.toMfaErrorMessage
Expand Down Expand Up @@ -253,7 +254,8 @@ private fun SelectFactorUI(
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringProvider.mfaManageFactorsTitle) }
title = { Text(stringProvider.mfaManageFactorsTitle) },
colors = AuthUITheme.resolvedTopAppBarColors
)
}
) { innerPadding ->
Expand Down
Loading