diff --git a/auth/build.gradle.kts b/auth/build.gradle.kts index 8e6d5304e..b26051d1c 100644 --- a/auth/build.gradle.kts +++ b/auth/build.gradle.kts @@ -115,6 +115,7 @@ dependencies { testImplementation(Config.Libs.Test.robolectric) testImplementation(Config.Libs.Test.kotlinReflect) testImplementation(Config.Libs.Provider.facebook) + testImplementation(libs.androidx.ui.test.junit4) debugImplementation(project(":internal:lintchecks")) } diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt index 98be20ac0..4d9bc280d 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIConfiguration.kt @@ -20,6 +20,7 @@ import com.google.firebase.auth.ActionCodeSettings import androidx.compose.ui.graphics.vector.ImageVector import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme fun actionCodeSettings(block: ActionCodeSettings.Builder.() -> Unit) = ActionCodeSettings.newBuilder().apply(block).build() diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUITheme.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUITheme.kt deleted file mode 100644 index d2ae7032d..000000000 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUITheme.kt +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2025 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.firebase.ui.auth.compose.configuration - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ColorScheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Shapes -import androidx.compose.material3.Typography -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -private val LocalAuthUITheme = staticCompositionLocalOf { AuthUITheme.Default } - -/** - * Theming configuration for the entire Auth UI. - */ -class AuthUITheme( - /** - * The color scheme to use. - */ - val colorScheme: ColorScheme, - - /** - * The typography to use. - */ - val typography: Typography, - - /** - * The shapes to use for UI elements. - */ - val shapes: Shapes, - - /** - * A map of provider IDs to custom styling. - */ - val providerStyles: Map = emptyMap() -) { - - /** - * A class nested within AuthUITheme that defines the visual appearance of a specific - * provider button, allowing for per-provider branding and customization. - */ - class ProviderStyle( - /** - * The background color of the button. - */ - val backgroundColor: Color, - - /** - * The color of the text label on the button. - */ - val contentColor: Color, - - /** - * An optional tint color for the provider's icon. If null, - * the icon's intrinsic color is used. - */ - var iconTint: Color? = null, - - /** - * The shape of the button container. Defaults to RoundedCornerShape(4.dp). - */ - val shape: Shape = RoundedCornerShape(4.dp), - - /** - * The shadow elevation for the button. Defaults to 2.dp. - */ - val elevation: Dp = 2.dp - ) - - companion object { - /** - * A standard light theme with Material 3 defaults and - * pre-configured provider styles. - */ - val Default = AuthUITheme( - colorScheme = lightColorScheme(), - typography = Typography(), - shapes = Shapes(), - providerStyles = defaultProviderStyles - ) - - /** - * Creates a theme inheriting the app's current Material - * Theme settings. - */ - @Composable - fun fromMaterialTheme( - providerStyles: Map = Default.providerStyles - ): AuthUITheme { - return AuthUITheme( - colorScheme = MaterialTheme.colorScheme, - typography = MaterialTheme.typography, - shapes = MaterialTheme.shapes, - providerStyles = providerStyles - ) - } - - internal val defaultProviderStyles - get(): Map { - return Provider.entries.associate { provider -> - when (provider) { - Provider.GOOGLE -> { - provider.id to ProviderStyle( - backgroundColor = Color.White, - contentColor = Color(0xFF757575) - ) - } - - Provider.FACEBOOK -> { - provider.id to ProviderStyle( - backgroundColor = Color(0xFF3B5998), - contentColor = Color.White - ) - } - - Provider.TWITTER -> { - provider.id to ProviderStyle( - backgroundColor = Color(0xFF5BAAF4), - contentColor = Color.White - ) - } - - Provider.GITHUB -> { - provider.id to ProviderStyle( - backgroundColor = Color(0xFF24292E), - contentColor = Color.White - ) - } - - Provider.EMAIL -> { - provider.id to ProviderStyle( - backgroundColor = Color(0xFFD0021B), - contentColor = Color.White - ) - } - - Provider.PHONE -> { - provider.id to ProviderStyle( - backgroundColor = Color(0xFF43C5A5), - contentColor = Color.White - ) - } - - Provider.ANONYMOUS -> { - provider.id to ProviderStyle( - backgroundColor = Color(0xFFF4B400), - contentColor = Color.White - ) - } - - Provider.MICROSOFT -> { - provider.id to ProviderStyle( - backgroundColor = Color(0xFF2F2F2F), - contentColor = Color.White - ) - } - - Provider.YAHOO -> { - provider.id to ProviderStyle( - backgroundColor = Color(0xFF720E9E), - contentColor = Color.White - ) - } - - Provider.APPLE -> { - provider.id to ProviderStyle( - backgroundColor = Color.Black, - contentColor = Color.White - ) - } - } - } - } - } -} - -@Composable -fun AuthUITheme( - theme: AuthUITheme = AuthUITheme.Default, - content: @Composable () -> Unit -) { - CompositionLocalProvider(LocalAuthUITheme provides theme) { - content() - } -} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/theme/AuthUITheme.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/theme/AuthUITheme.kt new file mode 100644 index 000000000..d83cf5923 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/theme/AuthUITheme.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.configuration.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes +import androidx.compose.material3.Typography +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Theming configuration for the entire Auth UI. + */ +class AuthUITheme( + /** + * The color scheme to use. + */ + val colorScheme: ColorScheme, + + /** + * The typography to use. + */ + val typography: Typography, + + /** + * The shapes to use for UI elements. + */ + val shapes: Shapes, + + /** + * A map of provider IDs to custom styling. + */ + val providerStyles: Map = emptyMap() +) { + + /** + * A class nested within AuthUITheme that defines the visual appearance of a specific + * provider button, allowing for per-provider branding and customization. + */ + class ProviderStyle( + /** + * The background color of the button. + */ + val backgroundColor: Color, + + /** + * The color of the text label on the button. + */ + val contentColor: Color, + + /** + * An optional tint color for the provider's icon. If null, + * the icon's intrinsic color is used. + */ + var iconTint: Color? = null, + + /** + * The shape of the button container. Defaults to RoundedCornerShape(4.dp). + */ + val shape: Shape = RoundedCornerShape(4.dp), + + /** + * The shadow elevation for the button. Defaults to 2.dp. + */ + val elevation: Dp = 2.dp + ) + + companion object { + /** + * A standard light theme with Material 3 defaults and + * pre-configured provider styles. + */ + val Default = AuthUITheme( + colorScheme = lightColorScheme( + primary = Color(0xFFFFA611) + ), + typography = Typography(), + shapes = Shapes(), + providerStyles = ProviderStyleDefaults.default + ) + + /** + * Creates a theme inheriting the app's current Material + * Theme settings. + */ + @Composable + fun fromMaterialTheme( + providerStyles: Map = ProviderStyleDefaults.default + ): AuthUITheme { + return AuthUITheme( + colorScheme = MaterialTheme.colorScheme, + typography = MaterialTheme.typography, + shapes = MaterialTheme.shapes, + providerStyles = providerStyles + ) + } + + } +} + +@Composable +fun AuthUITheme( + theme: AuthUITheme = AuthUITheme.Default, + content: @Composable () -> Unit +) { + MaterialTheme( + colorScheme = theme.colorScheme, + typography = theme.typography, + shapes = theme.shapes, + content = content + ) +} diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/theme/ProviderStyleDefaults.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/theme/ProviderStyleDefaults.kt new file mode 100644 index 000000000..4f063c9a5 --- /dev/null +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/theme/ProviderStyleDefaults.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.configuration.theme + +import androidx.compose.ui.graphics.Color +import com.firebase.ui.auth.compose.configuration.Provider + +/** + * Default provider styling configurations for authentication providers. + * + * This object provides brand-appropriate visual styling for each supported authentication + * provider, including background colors, text colors, and other visual properties that + * match each provider's brand guidelines. + * + * The styles are automatically applied when using [AuthUITheme.Default] or can be + * customized by passing a modified map to [AuthUITheme.fromMaterialTheme]. + */ +internal object ProviderStyleDefaults { + val default: Map + get() = Provider.entries.associate { provider -> + when (provider) { + Provider.GOOGLE -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color.White, + contentColor = Color(0xFF757575) + ) + } + + Provider.FACEBOOK -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color(0xFF3B5998), + contentColor = Color.White + ) + } + + Provider.TWITTER -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color(0xFF5BAAF4), + contentColor = Color.White + ) + } + + Provider.GITHUB -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color(0xFF24292E), + contentColor = Color.White + ) + } + + Provider.EMAIL -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color(0xFFD0021B), + contentColor = Color.White + ) + } + + Provider.PHONE -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color(0xFF43C5A5), + contentColor = Color.White + ) + } + + Provider.ANONYMOUS -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color(0xFFF4B400), + contentColor = Color.White + ) + } + + Provider.MICROSOFT -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color(0xFF2F2F2F), + contentColor = Color.White + ) + } + + Provider.YAHOO -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color(0xFF720E9E), + contentColor = Color.White + ) + } + + Provider.APPLE -> { + provider.id to AuthUITheme.ProviderStyle( + backgroundColor = Color.Black, + contentColor = Color.White + ) + } + } + } +} \ No newline at end of file diff --git a/auth/src/test/AndroidManifest.xml b/auth/src/test/AndroidManifest.xml new file mode 100644 index 000000000..66eb2ad51 --- /dev/null +++ b/auth/src/test/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt index 96a13795e..118977c09 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/AuthUIConfigurationTest.kt @@ -22,6 +22,7 @@ import androidx.test.core.app.ApplicationProvider import com.firebase.ui.auth.R import com.firebase.ui.auth.compose.configuration.stringprovider.AuthUIStringProvider import com.firebase.ui.auth.compose.configuration.stringprovider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme import com.google.common.truth.Truth.assertThat import com.google.firebase.auth.actionCodeSettings import org.junit.Assert.assertThrows diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/configuration/theme/AuthUIThemeTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/theme/AuthUIThemeTest.kt new file mode 100644 index 000000000..f2a9b88cc --- /dev/null +++ b/auth/src/test/java/com/firebase/ui/auth/compose/configuration/theme/AuthUIThemeTest.kt @@ -0,0 +1,81 @@ +package com.firebase.ui.auth.compose.configuration.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes +import androidx.compose.material3.Typography +import androidx.compose.material3.lightColorScheme +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@Config(sdk = [34]) +@RunWith(RobolectricTestRunner::class) +class AuthUIThemeTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun `Default AuthUITheme applies to MaterialTheme`() { + val theme = AuthUITheme.Default + + composeTestRule.setContent { + AuthUITheme { + assertThat(MaterialTheme.colorScheme).isEqualTo(theme.colorScheme) + assertThat(MaterialTheme.typography).isEqualTo(theme.typography) + assertThat(MaterialTheme.shapes).isEqualTo(theme.shapes) + } + } + } + + @Test + fun `fromMaterialTheme inherits client MaterialTheme values`() { + val appLightColorScheme = lightColorScheme( + primary = Color(0xFF6650a4), + secondary = Color(0xFF625b71), + tertiary = Color(0xFF7D5260) + ) + + val appTypography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + ) + + val appShapes = Shapes(extraSmall = RoundedCornerShape(13.dp)) + + composeTestRule.setContent { + MaterialTheme( + colorScheme = appLightColorScheme, + typography = appTypography, + shapes = appShapes, + ) { + AuthUITheme( + theme = AuthUITheme.fromMaterialTheme() + ) { + assertThat(MaterialTheme.colorScheme) + .isEqualTo(appLightColorScheme) + assertThat(MaterialTheme.typography) + .isEqualTo(appTypography) + assertThat(MaterialTheme.shapes) + .isEqualTo(appShapes) + } + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 379fe1b5b..44eb15432 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,8 @@ kotlin = "2.2.0" [libraries] +# Testing +androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" } [plugins] compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } \ No newline at end of file