Skip to content
Open
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
7 changes: 5 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ plugins {
id("org.jetbrains.kotlin.plugin.compose")
id("com.google.gms.google-services")
id("kotlin-kapt")
// The slot demos host the auth screens on their own Navigation 3 back stacks, and a
// rememberNavBackStack key has to be @Serializable to survive process death.
// The slot demos and the full customization demo (AuthMethodPickerUI.kt) each host auth
// screens on their own Navigation 3 back stacks, and a rememberNavBackStack key has to be
// @Serializable to survive process death — the same support the auth module applies for its
// own Navigation 3 keys.
alias(libs.plugins.kotlin.serialization)
}

Expand Down Expand Up @@ -71,6 +73,7 @@ dependencies {
implementation(libs.compose.ui.graphics)
implementation(libs.compose.ui.tooling.preview)
implementation(libs.compose.material3)
implementation(libs.compose.material.icons.extended)

// Facebook
implementation(libs.facebook.login)
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@
android:exported="false"
android:theme="@style/Theme.FirebaseUIAndroid" />

<activity
android:name="com.firebaseui.android.demo.auth.fullcustomization.FullCustomizationDemoActivity"
android:label="Full Customization"
android:exported="false"
android:theme="@style/Theme.FirebaseUIAndroid" />

<!-- Database -->
<activity
android:name="com.firebaseui.android.demo.database.DatabaseDemoActivity"
Expand Down
38 changes: 34 additions & 4 deletions app/src/main/java/com/firebaseui/android/demo/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.firebaseui.android.demo

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
Expand All @@ -23,10 +24,12 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import com.firebase.ui.auth.FirebaseAuthUI
import com.firebase.ui.auth.util.EmailLinkConstants
import com.firebaseui.android.demo.auth.AuthChooserActivity
import com.firebaseui.android.demo.auth.HighLevelApiDemoActivity
import com.firebaseui.android.demo.auth.fullcustomization.FullCustomizationDemoActivity
import com.firebaseui.android.demo.database.DatabaseDemoActivity
import com.firebaseui.android.demo.firestore.FirestoreDemoActivity
import com.firebaseui.android.demo.storage.StorageDemoActivity
Expand All @@ -36,7 +39,7 @@ import com.google.firebase.firestore.FirebaseFirestore

class MainActivity : ComponentActivity() {
companion object {
internal const val USE_AUTH_EMULATOR = true
internal const val USE_AUTH_EMULATOR = false
private const val AUTH_EMULATOR_HOST = "10.0.2.2"
private const val AUTH_EMULATOR_PORT = 9099

Expand All @@ -52,6 +55,27 @@ class MainActivity : ComponentActivity() {
// useEmulator() throws once the Firestore/Database client has been used elsewhere in
// the process, so this must only run once per process, not on every onCreate().
private var emulatorsConfigured = false

// Every demo sends its email links through the same Firebase host, so the only thing
// telling them apart is the last path segment each one puts on its continue URL. It goes
// in the path rather than the query because ContinueUrlBuilder appends "?" to the URL
// unconditionally, which corrupts a query string that is already there.
const val ORIGIN_FULL_CUSTOMIZATION = "fullcustomization"
const val ORIGIN_HIGH_LEVEL = "highlevel"

/** The continue URL, which sits either directly on [uri] or nested inside its `link`. */
private fun continueUrlOf(uri: Uri): Uri? {
uri.getQueryParameter("continueUrl")?.let { return it.toUri() }
uri.getQueryParameter("link")?.let { return continueUrlOf(it.toUri()) }
return null
}

/** Which demo sent [link], or null when it says nothing about where it came from. */
internal fun emailLinkOrigin(link: String?): String? =
link?.takeIf { it.isNotEmpty() }
?.let { runCatching { continueUrlOf(it.toUri()) }.getOrNull() }
?.pathSegments
?.lastOrNull()
}

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -85,8 +109,14 @@ class MainActivity : ComponentActivity() {

Log.d("MainActivity", "Pending email link: $pendingEmailLink")

fun launchHighLevelDemo() {
val demoIntent = Intent(this, HighLevelApiDemoActivity::class.java).apply {
fun launchDemoForEmailLink() {
val target = when (emailLinkOrigin(pendingEmailLink)) {
ORIGIN_FULL_CUSTOMIZATION -> FullCustomizationDemoActivity::class.java
// Anything else, including links sent before the demos started tagging
// themselves, belongs to the demo that has always handled them.
else -> HighLevelApiDemoActivity::class.java
}
val demoIntent = Intent(this, target).apply {
pendingEmailLink?.let { link ->
putExtra(EmailLinkConstants.EXTRA_EMAIL_LINK, link)
pendingEmailLink = null
Expand All @@ -96,7 +126,7 @@ class MainActivity : ComponentActivity() {
}

if (savedInstanceState == null && !pendingEmailLink.isNullOrEmpty()) {
launchHighLevelDemo()
launchDemoForEmailLink()
finish()
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ class CustomMethodPickerDemoActivity : ComponentActivity() {
Log.d("CustomMethodPickerDemo", "Auth cancelled")
},
customMethodPickerLayout = { providers, onProviderSelected ->
// customMethodPickerLayout now renders as the entire screen (no
// built-in logo/ToS footer/inset handling), so the terms checkbox
// that used to live in customMethodPickerTermsConfiguration is
// rendered inline here instead, and this composable owns its own
// insets via Modifier.safeDrawingPadding() in SpotlightMethodPicker.
SpotlightMethodPicker(
providers = providers,
onProviderSelected = onProviderSelected,
Expand Down Expand Up @@ -181,6 +186,8 @@ fun SpotlightMethodPicker(
val anonymous = groups["anonymous"]?.firstOrNull()

LazyColumn(
// customMethodPickerLayout now renders as the entire screen, so this composable is
// responsible for its own insets.
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding(),
Expand Down Expand Up @@ -298,7 +305,7 @@ fun SpotlightMethodPicker(
}

@Composable
private fun ProviderIconButton(
fun ProviderIconButton(
style: AuthUITheme.ProviderStyle,
contentDescription: String,
onClick: () -> Unit,
Expand Down Expand Up @@ -335,12 +342,12 @@ private fun ProviderIconButton(
}

@Composable
private fun AuthUIAsset.asPainter(): Painter = when (this) {
fun AuthUIAsset.asPainter(): Painter = when (this) {
is AuthUIAsset.Resource -> painterResource(resId)
is AuthUIAsset.Vector -> rememberVectorPainter(image)
}

private fun styleForProvider(provider: AuthProvider): AuthUITheme.ProviderStyle = when (provider) {
fun styleForProvider(provider: AuthProvider): AuthUITheme.ProviderStyle = when (provider) {
is AuthProvider.Facebook -> ProviderStyleDefaults.Facebook
is AuthProvider.Twitter -> ProviderStyleDefaults.Twitter
is AuthProvider.Github -> ProviderStyleDefaults.Github
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.firebaseui.android.demo.auth.fullcustomization.FullCustomizationDemoActivity

class CustomSlotsThemingDemoActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -46,6 +47,9 @@ class CustomSlotsThemingDemoActivity : ComponentActivity() {
},
onCustomMethodPickerClick = {
startActivity(Intent(this, CustomMethodPickerDemoActivity::class.java))
},
onFullCustomizationClick = {
startActivity(Intent(this, FullCustomizationDemoActivity::class.java))
}
)
}
Expand All @@ -60,6 +64,7 @@ fun CustomSlotsDemoChooser(
onPhoneAuthSlotClick: () -> Unit,
onShapeCustomizationClick: () -> Unit,
onCustomMethodPickerClick: () -> Unit,
onFullCustomizationClick: () -> Unit,
) {
Column(
modifier = Modifier
Expand Down Expand Up @@ -106,6 +111,12 @@ fun CustomSlotsDemoChooser(
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
)

DemoCard(
title = "Full Customization",
description = "customMethodPickerLayout renders as the entire screen, so this layers a full-bleed background image and scrim behind the custom method picker.",
onClick = onFullCustomizationClick
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import com.firebase.ui.auth.ui.screens.reauth.ReauthContentState
import com.firebase.ui.auth.util.EmailLinkConstants
import com.firebase.ui.auth.util.displayIdentifier
import com.firebase.ui.auth.util.getDisplayEmail
import com.firebaseui.android.demo.MainActivity
import com.firebaseui.android.demo.R
import com.google.firebase.auth.actionCodeSettings

Expand Down Expand Up @@ -131,7 +132,10 @@ class HighLevelApiDemoActivity : ComponentActivity() {
isEmailLinkForceSameDeviceEnabled = false,
isEmailLinkSignInEnabled = true,
emailLinkActionCodeSettings = actionCodeSettings {
url = "https://flutterfire-e2e-tests.firebaseapp.com"
// The trailing segment is what MainActivity routes the returning
// link on — see MainActivity.emailLinkOrigin.
url = "https://flutterfire-e2e-tests.firebaseapp.com/demo/" +
MainActivity.ORIGIN_HIGH_LEVEL
handleCodeInApp = true
setAndroidPackageName(
"com.firebaseui.android.demo",
Expand Down
Loading