Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.core.content.edit
import com.firebase.ui.auth.AuthException
import com.firebase.ui.auth.FirebaseAuthUI
import com.firebase.ui.auth.configuration.AuthUIConfiguration
Expand All @@ -25,8 +32,12 @@ import com.firebase.ui.auth.configuration.authUIConfiguration
import com.firebase.ui.auth.configuration.auth_provider.AuthProvider
import com.firebase.ui.auth.configuration.theme.AuthUIAsset
import com.firebase.ui.auth.ui.screens.FirebaseAuthScreen
import com.firebase.ui.auth.util.EmailLinkConstants
import com.firebaseui.android.demo.R
import com.firebaseui.android.demo.utils.EMAIL_LINK_ORIGIN_PARAM
import com.firebaseui.android.demo.auth.fullcustomization.common.ThemeBackground
import com.firebaseui.android.demo.auth.fullcustomization.common.ThemeSwitcher
import com.firebaseui.android.demo.auth.fullcustomization.professional.LegalLinks
import com.firebaseui.android.demo.auth.fullcustomization.professional.LocalLegalLinks
import com.firebaseui.android.demo.auth.fullcustomization.screens.AuthMethodPickerUI
import com.firebaseui.android.demo.auth.fullcustomization.screens.AuthenticatedUI
import com.firebaseui.android.demo.auth.fullcustomization.screens.email.EmailAuthUI
Expand All @@ -35,13 +46,17 @@ import com.firebaseui.android.demo.auth.fullcustomization.screens.mfa.MfaEnrollm
import com.firebaseui.android.demo.auth.fullcustomization.screens.phone.PhoneSignInUI
import com.firebaseui.android.demo.auth.fullcustomization.screens.reauth.ReauthUI
import com.firebaseui.android.demo.auth.fullcustomization.theme.FullCustomizationTheme
import com.firebase.ui.auth.util.EmailLinkConstants
import com.firebaseui.android.demo.auth.fullcustomization.theme.FullCustomizationThemeId
import com.firebaseui.android.demo.utils.EMAIL_LINK_ORIGIN_PARAM
import com.google.firebase.auth.actionCodeSettings

class FullCustomizationDemoActivity : ComponentActivity() {
companion object {
/** Marks this demo's email links so MainActivity can route the return trip back here. */
const val EMAIL_LINK_ORIGIN = "fullcustomization"

private const val THEME_PREFERENCES = "full_customization"
private const val THEME_KEY = "theme"
}

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -97,47 +112,74 @@ class FullCustomizationDemoActivity : ComponentActivity() {
}
}

val preferences = getSharedPreferences(THEME_PREFERENCES, MODE_PRIVATE)

setContent {
FullCustomizationTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
var themeId by remember {
mutableStateOf(
FullCustomizationThemeId.entries.firstOrNull {
it.name == preferences.getString(THEME_KEY, null)
} ?: FullCustomizationThemeId.Doggo
)
}
Comment on lines +115 to +124

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Reading SharedPreferences directly inside the remember block during composition can block the main thread and cause frame drops, as composition should be fast and side-effect free. It is highly recommended to read the initial theme value outside of the setContent block and pass it as the initial state.

        val preferences = getSharedPreferences(THEME_PREFERENCES, MODE_PRIVATE)
        val initialTheme = FullCustomizationThemeId.entries.firstOrNull {
            it.name == preferences.getString(THEME_KEY, null)
        } ?: FullCustomizationThemeId.Doggo

        setContent {
            var themeId by remember { mutableStateOf(initialTheme) }

FullCustomizationTheme(themeId = themeId) {
CompositionLocalProvider(
LocalLegalLinks provides LegalLinks(configuration.tosUrl, configuration.privacyPolicyUrl),
) {
FirebaseAuthScreen(
configuration = configuration,
authUI = authUI,
emailLink = emailLink,
onSignInSuccess = { result ->
Log.d("FullCustomizationDemo", "Auth success: ${result.user?.uid}")
},
onSignInFailure = { exception: AuthException ->
Log.e("FullCustomizationDemo", "Auth failed", exception)
},
onSignInCancelled = {
Log.d("FullCustomizationDemo", "Auth cancelled")
},
mfaConfiguration = MfaConfiguration(
allowedFactors = listOf(MfaFactor.Sms, MfaFactor.Totp),
requireEnrollment = false,
),
customMethodPickerLayout = { providers, onProviderSelected ->
MainUI(
authUI = authUI,
Box(modifier = Modifier.fillMaxSize()) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
FirebaseAuthScreen(
configuration = configuration,
providers = providers,
onProviderSelected = onProviderSelected,
authUI = authUI,
emailLink = emailLink,
onSignInSuccess = { result ->
Log.d("FullCustomizationDemo", "Auth success: ${result.user?.uid}")
},
onSignInFailure = { exception: AuthException ->
Log.e("FullCustomizationDemo", "Auth failed", exception)
},
onSignInCancelled = {
Log.d("FullCustomizationDemo", "Auth cancelled")
},
mfaConfiguration = MfaConfiguration(
allowedFactors = listOf(MfaFactor.Sms, MfaFactor.Totp),
requireEnrollment = false,
),
customMethodPickerLayout = { providers, onProviderSelected ->
MainUI(
authUI = authUI,
configuration = configuration,
providers = providers,
onProviderSelected = onProviderSelected,
)
},
// Covers the email flows the library navigates to itself.
emailContent = { state -> EmailAuthUI(state) },
phoneContent = { state -> PhoneSignInUI(state) },
mfaEnrollmentContent = { state -> MfaEnrollmentUI(state) },
mfaChallengeContent = { state -> MfaChallengeUI(state) },
reauthContent = { state -> ReauthUI(state) },
authenticatedContent = { state, uiContext ->
AuthenticatedUI(state = state, uiContext = uiContext)
},
)
},
// Covers the email flows the library navigates to itself.
emailContent = { state -> EmailAuthUI(state) },
phoneContent = { state -> PhoneSignInUI(state) },
mfaEnrollmentContent = { state -> MfaEnrollmentUI(state) },
mfaChallengeContent = { state -> MfaChallengeUI(state) },
reauthContent = { state -> ReauthUI(state) },
authenticatedContent = { state, uiContext ->
AuthenticatedUI(state = state, uiContext = uiContext)
},
)
}
// Over every screen, so the theme can be switched mid-flow.
ThemeSwitcher(
selected = themeId,
onSelect = { selected ->
themeId = selected
preferences.edit { putString(THEME_KEY, selected.name) }
},
modifier = Modifier
.align(Alignment.TopCenter)
.statusBarsPadding()
.padding(top = 8.dp),
)
}
}
}
}
Expand All @@ -153,12 +195,7 @@ private fun MainUI(
) {
val context = LocalContext.current
Box(modifier = Modifier.fillMaxSize()) {
Image(
painter = painterResource(id = R.drawable.custom_background),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
ThemeBackground()
Column(modifier = Modifier.fillMaxSize()) {
// AuthMethodPickerUI builds its own EmailAuthScreen per step, so none is needed here.
AuthMethodPickerUI(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.firebaseui.android.demo.auth.fullcustomization.common

import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
Expand All @@ -14,20 +13,18 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.firebaseui.android.demo.R
import com.firebaseui.android.demo.auth.fullcustomization.professional.ProHeader
import com.firebaseui.android.demo.auth.fullcustomization.professional.ProPage
import com.firebaseui.android.demo.auth.fullcustomization.professional.isProfessional
import com.firebaseui.android.demo.auth.fullcustomization.theme.LocalFullCustomizationStyle

/**
* The page frame shared by the MFA and reauthentication screens: mascot, headline, a single
Expand All @@ -50,14 +47,22 @@ fun AuthPage(
actions: @Composable ColumnScope.() -> Unit,
card: @Composable ColumnScope.() -> Unit,
) {
// Professional lays the same content out as a plain left-aligned form, with no card.
if (isProfessional()) {
ProPage(
modifier = Modifier.semantics { contentDescription = cardContentDescription },
header = { ProHeader(title = title) },
) {
Column(verticalArrangement = Arrangement.spacedBy(16.dp), content = card)
Spacer(modifier = Modifier.height(24.dp))
Column(content = actions)
}
return
}

Box(modifier = Modifier.fillMaxSize()) {
// Outside safeDrawingPadding so it runs edge to edge under the system bars.
Image(
painter = painterResource(id = R.drawable.custom_background),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
ThemeBackground()

BoxWithConstraints(
modifier = Modifier
Expand All @@ -74,16 +79,10 @@ fun AuthPage(
Spacer(modifier = Modifier.weight(1f))

Column(modifier = Modifier.fillMaxWidth()) {
Image(
painter = painterResource(id = mascot),
contentDescription = mascotDescription,
modifier = Modifier.size(72.dp),
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = title,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.primary,
ThemeHeader(
title = title,
mascot = mascot,
mascotDescription = mascotDescription,
)
Spacer(modifier = Modifier.height(24.dp))

Expand All @@ -92,7 +91,7 @@ fun AuthPage(
modifier = Modifier
.fillMaxWidth()
.semantics { contentDescription = cardContentDescription },
color = MaterialTheme.colorScheme.surface,
color = LocalFullCustomizationStyle.current.cardFill,
shape = AuthFieldShape,
) {
Column(
Expand Down
Loading
Loading