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
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ jobs:
java-version: '21'
distribution: 'temurin'

# lintAll gates :proguard-tests, which applies the google-services plugin and
# will not configure without this file. Mirrors what scripts/build.sh copies.
# lintAll gates :app and :proguard-tests, both of which apply the google-services
# plugin and will not configure without this file. Mirrors what build.sh copies.
- name: Copy google-services.json
run: |
cp library/google-services.json app/google-services.json
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ This will:
- Copy the necessary `google-services.json` files
- Download all dependencies
- Build all modules
- Run the R8/ProGuard gate
- Run checkstyle
- Run unit tests

Expand Down Expand Up @@ -145,6 +146,7 @@ commands can be run locally to highlight any issues before committing your code:
This script runs:
- `./gradlew clean`
- `./gradlew assembleDebug` - Build all modules
- `./gradlew proguard-tests:build` - Check the libraries' consumer ProGuard rules against R8
- `./gradlew checkstyle` - Run code style checks
- `./gradlew testDebugUnitTest -x :e2eTest:testDebugUnitTest` - Run unit tests

Expand Down
24 changes: 22 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ 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.
// Nav3 back-stack keys must be @Serializable to survive process death.
alias(libs.plugins.kotlin.serialization)
}

Expand Down Expand Up @@ -39,6 +38,26 @@ android {
}
}
}

lint {
// Module specific
disable += mutableSetOf(
// Reads the root wrapper, but only the application module analyses it, so it
// belongs here rather than in the shared policy. For reproducible builds.
"AndroidGradlePluginVersion",
// The demos log their auth callbacks unconditionally on purpose — watching
// logcat is how you see one fire. Unlike :auth's, none of these log user data.
"LogConditional",
// Glide's KSP processor does not generate GlideApp, which the storage demo and
// storage/README.md are both written around. Migration tracked separately.
"KaptUsageInsteadOfKsp",
// A themed icon needs a flat silhouette drawn for the purpose. The only
// candidate here is ic_launcher_foreground, whose opaque region is a solid
// plate, so it tints to a featureless block — worse than no monochrome layer.
"MonochromeLauncherIcon"
)
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
Expand Down Expand Up @@ -71,6 +90,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
15 changes: 13 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@
android:usesCleartextTraffic="true">
<activity
android:name="com.firebaseui.android.demo.MainActivity"
android:label="@string/app_name"
android:exported="true"
android:theme="@style/Theme.FirebaseUIAndroid">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter android:autoVerify="true">
<!--
The host is a placeholder the demo's own Firebase project fills in, so lint
can neither read it as a domain nor resolve it over the network.
-->
<intent-filter
android:autoVerify="true"
tools:ignore="AppLinkUrlError,AppLinksAutoVerify">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
Expand Down Expand Up @@ -88,6 +93,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
14 changes: 11 additions & 3 deletions app/src/main/java/com/firebaseui/android/demo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ 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
import com.firebaseui.android.demo.utils.emailLinkOrigin
import com.google.firebase.FirebaseApp
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.firestore.FirebaseFirestore
Expand Down Expand Up @@ -85,8 +87,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)) {
FullCustomizationDemoActivity.EMAIL_LINK_ORIGIN ->
FullCustomizationDemoActivity::class.java
// Untagged links predate this routing, so they belong to the demo that had 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 +104,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,7 @@ class CustomMethodPickerDemoActivity : ComponentActivity() {
Log.d("CustomMethodPickerDemo", "Auth cancelled")
},
customMethodPickerLayout = { providers, onProviderSelected ->
// Owns the whole screen now, so the terms checkbox is inline here.
SpotlightMethodPicker(
providers = providers,
onProviderSelected = onProviderSelected,
Expand Down Expand Up @@ -181,6 +182,7 @@ fun SpotlightMethodPicker(
val anonymous = groups["anonymous"]?.firstOrNull()

LazyColumn(
// Owns the whole screen, so it handles its own insets.
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding(),
Expand Down Expand Up @@ -298,7 +300,7 @@ fun SpotlightMethodPicker(
}

@Composable
private fun ProviderIconButton(
fun ProviderIconButton(
style: AuthUITheme.ProviderStyle,
contentDescription: String,
onClick: () -> Unit,
Expand Down Expand Up @@ -335,12 +337,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 @@ -67,9 +67,15 @@ 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.R
import com.firebaseui.android.demo.utils.EMAIL_LINK_ORIGIN_PARAM
import com.google.firebase.auth.actionCodeSettings

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

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
Expand Down Expand Up @@ -131,7 +137,9 @@ class HighLevelApiDemoActivity : ComponentActivity() {
isEmailLinkForceSameDeviceEnabled = false,
isEmailLinkSignInEnabled = true,
emailLinkActionCodeSettings = actionCodeSettings {
url = "https://flutterfire-e2e-tests.firebaseapp.com"
// This tag is what MainActivity routes the return trip on.
url = "https://flutterfire-e2e-tests.firebaseapp.com" +
"?$EMAIL_LINK_ORIGIN_PARAM=$EMAIL_LINK_ORIGIN"
handleCodeInApp = true
setAndroidPackageName(
"com.firebaseui.android.demo",
Expand Down
Loading
Loading