diff --git a/app/src/main/java/com/auth0/android/sample/MainActivity.kt b/app/src/main/java/com/auth0/android/sample/MainActivity.kt index b81b7f7..e7e2b72 100644 --- a/app/src/main/java/com/auth0/android/sample/MainActivity.kt +++ b/app/src/main/java/com/auth0/android/sample/MainActivity.kt @@ -170,11 +170,24 @@ fun SampleApp( composable { val context = LocalContext.current - ChooseSignInScreen( - onHostedLogin = { authViewModel.login(context, webAuthProvider) }, - onEmbeddedLogin = { navController.navigate(AppRoute.EmbeddedLogin) }, - onSettings = { navController.navigate(AppRoute.Appearance) } - ) + val snackbarHostState = remember { SnackbarHostState() } + + LaunchedEffect(Unit) { + authViewModel.loginError.collect { message -> + snackbarHostState.showSnackbar(message) + } + } + + Scaffold( + snackbarHost = { SnackbarHost(snackbarHostState) }, + containerColor = androidx.compose.ui.graphics.Color.Transparent + ) { _ -> + ChooseSignInScreen( + onHostedLogin = { authViewModel.login(context, webAuthProvider) }, + onEmbeddedLogin = { navController.navigate(AppRoute.EmbeddedLogin) }, + onSettings = { navController.navigate(AppRoute.Appearance) } + ) + } } composable { diff --git a/app/src/main/java/com/auth0/android/sample/ui/components/Auth0LogoHeader.kt b/app/src/main/java/com/auth0/android/sample/ui/components/Auth0LogoHeader.kt index fecb5ca..248d0af 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/components/Auth0LogoHeader.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/components/Auth0LogoHeader.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import com.auth0.android.sample.R +import com.auth0.android.sample.ui.theme.isAuth0DarkTheme import com.auth0.universalcomponents.theme.Auth0Theme /** @@ -24,6 +25,8 @@ fun Auth0LogoHeader( modifier: Modifier = Modifier, ) { val dimensions = Auth0Theme.dimensions + val colors = Auth0Theme.colors + val logoTint = if (isAuth0DarkTheme()) colors.textBold else Color.Unspecified Spacer(modifier = Modifier.height(dimensions.spacingXl)) @@ -35,7 +38,7 @@ fun Auth0LogoHeader( ) { Icon( painterResource(R.drawable.ic_auth0), contentDescription = "Auth0 logo", - tint = Color.Unspecified + tint = logoTint ) } } diff --git a/app/src/main/java/com/auth0/android/sample/ui/components/FactorCard.kt b/app/src/main/java/com/auth0/android/sample/ui/components/FactorCard.kt index ffa527f..3f644b2 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/components/FactorCard.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/components/FactorCard.kt @@ -2,7 +2,8 @@ package com.auth0.android.sample.ui.components import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,9 +19,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.dp +import com.auth0.android.sample.ui.theme.isAuth0DarkTheme import com.auth0.universalcomponents.theme.Auth0Theme /** @@ -31,6 +32,7 @@ import com.auth0.universalcomponents.theme.Auth0Theme * @param title Factor name (e.g. "Hosted Login") * @param description Short description of the factor * @param icon Leading icon for the factor + * @param isSelected Whether this card is currently selected; shows bold border and filled radio indicator * @param onClick Callback when the card is tapped */ @Composable @@ -38,6 +40,7 @@ fun FactorCard( title: String, description: String, icon: Painter, + isSelected: Boolean = false, onClick: () -> Unit = {} ) { val colors = Auth0Theme.colors @@ -45,23 +48,34 @@ fun FactorCard( val shapes = Auth0Theme.shapes val dimensions = Auth0Theme.dimensions val sizes = Auth0Theme.sizes + val isDark = isAuth0DarkTheme() + + // Light: selected=white (layerTop), unselected=near-white (layerMedium) + // Dark: both cards use layerMedium (#27272A); selection is shown via border only + val cardBackground = when { + isSelected && !isDark -> colors.backgroundLayerTop + else -> colors.backgroundLayerMedium + } Card( - modifier = Modifier - .fillMaxWidth() - .height(112.dp) - .clickable { onClick() }, + onClick = onClick, + modifier = Modifier.fillMaxWidth(), shape = shapes.large, - colors = CardDefaults.cardColors( - containerColor = Auth0Theme.colors.backgroundLayerBase - ), - border = BorderStroke(1.dp, colors.borderBold) + colors = CardDefaults.cardColors(containerColor = cardBackground), + border = BorderStroke( + width = if (isSelected) 2.dp else 1.dp, + color = when { + isSelected && isDark -> colors.backgroundAccent // green #A7F3D0 in dark + isSelected -> colors.borderBold // grey #A1A1AA in light + else -> colors.borderDefault + } + ) ) { Row( modifier = Modifier .fillMaxWidth() .padding(dimensions.spacingMd), - verticalAlignment = Alignment.Top + verticalAlignment = Alignment.CenterVertically ) { Icon( painter = icon, @@ -78,12 +92,48 @@ fun FactorCard( style = typography.title, color = colors.textBold ) + Spacer(modifier = Modifier.height(dimensions.spacingXxs)) Text( text = description, style = typography.body, - color = colors.textDefault + color = colors.textDefault, + minLines = 2 ) } + + Spacer(modifier = Modifier.width(dimensions.spacingMd)) + + // Radio selection indicator: + // Light selected: dark filled circle (#09090B) + white inner dot + // Dark selected: green filled circle (#A7F3D0 accent) + dark inner dot + // Unselected: transparent circle with subtle border + val radioFill = when { + isSelected && isDark -> colors.backgroundAccent + isSelected -> colors.backgroundPrimary + else -> colors.backgroundLayerBase + } + val radioBorder = if (isSelected) radioFill else colors.borderDefault + val radioInnerDot = if (isDark) colors.backgroundLayerMedium else colors.backgroundLayerTop + + Box( + modifier = Modifier + .size(20.dp) + .border( + width = if (isSelected) 0.dp else 1.dp, + color = radioBorder, + shape = shapes.full + ) + .background(color = radioFill, shape = shapes.full), + contentAlignment = Alignment.Center + ) { + if (isSelected) { + Box( + modifier = Modifier + .size(8.dp) + .background(color = radioInnerDot, shape = shapes.full) + ) + } + } } } } diff --git a/app/src/main/java/com/auth0/android/sample/ui/components/NavigationGridCard.kt b/app/src/main/java/com/auth0/android/sample/ui/components/NavigationGridCard.kt index d1b499d..b11d12b 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/components/NavigationGridCard.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/components/NavigationGridCard.kt @@ -51,8 +51,8 @@ fun NavigationGridCard( .clickable { onClick() }, shape = shapes.extraLarge, colors = CardDefaults.cardColors(containerColor = colors.backgroundLayerTop), - border = BorderStroke(1.dp, colors.borderBold), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + border = BorderStroke(1.dp, colors.borderDefault), + elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) ) { Column( modifier = Modifier diff --git a/app/src/main/java/com/auth0/android/sample/ui/screens/AppearanceScreen.kt b/app/src/main/java/com/auth0/android/sample/ui/screens/AppearanceScreen.kt index 142c835..6e58c7a 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/screens/AppearanceScreen.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/screens/AppearanceScreen.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.auth0.android.sample.ui.viewmodels.AppearanceViewModel import com.auth0.universalcomponents.presentation.ui.components.GradientButton import com.auth0.universalcomponents.presentation.ui.components.TopBar +import com.auth0.android.sample.ui.theme.isAuth0DarkTheme import com.auth0.universalcomponents.theme.Auth0Theme /** @@ -39,13 +40,23 @@ fun AppearanceScreen( onBack: () -> Unit, appearanceViewModel: AppearanceViewModel ) { + val selectedIndex by appearanceViewModel.selectedIndex.collectAsStateWithLifecycle() + val previewOption = appearanceViewModel.themeOptions.getOrElse(selectedIndex) { + appearanceViewModel.themeOptions[0] + } + + // Wrap in a local Auth0Theme so the screen previews the selected theme immediately, + // while the global theme (in MainActivity) only updates when "Update Theme" is tapped. + Auth0Theme( + configuration = previewOption.configuration, + darkTheme = previewOption.darkTheme + ) { val colors = Auth0Theme.colors val typography = Auth0Theme.typography val shapes = Auth0Theme.shapes val dimensions = Auth0Theme.dimensions val sizes = Auth0Theme.sizes - - val selectedIndex by appearanceViewModel.selectedIndex.collectAsStateWithLifecycle() + val isDark = isAuth0DarkTheme() Scaffold( topBar = { @@ -108,7 +119,7 @@ fun AppearanceScreen( selected = selectedIndex == index, onClick = { appearanceViewModel.selectTheme(index) }, colors = RadioButtonDefaults.colors( - selectedColor = colors.backgroundPrimary, + selectedColor = if (isDark) colors.backgroundAccent else colors.backgroundPrimary, unselectedColor = colors.borderDefault ) ) @@ -124,7 +135,7 @@ fun AppearanceScreen( } } - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.height(dimensions.spacingLg)) GradientButton( modifier = Modifier @@ -142,7 +153,7 @@ fun AppearanceScreen( ) } - Spacer(modifier = Modifier.height(dimensions.spacingLg)) } } + } // Auth0Theme } diff --git a/app/src/main/java/com/auth0/android/sample/ui/screens/ChooseSignInScreen.kt b/app/src/main/java/com/auth0/android/sample/ui/screens/ChooseSignInScreen.kt index bca7c41..b2f2dd5 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/screens/ChooseSignInScreen.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/screens/ChooseSignInScreen.kt @@ -1,101 +1,155 @@ package com.auth0.android.sample.ui.screens -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +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.res.painterResource import androidx.compose.ui.text.style.TextAlign import com.auth0.android.sample.ui.components.Auth0LogoHeader import com.auth0.android.sample.ui.components.FactorCard -import com.auth0.android.sample.ui.theme.BackGroundColor +import com.auth0.android.sample.ui.theme.auth0ScreenBackground import com.auth0.universalcomponents.theme.Auth0Theme +import com.auth0.universalcomponents.theme.Auth0Theme.colors + +private enum class LoginOption { Embedded, Hosted } /** * Pre-login screen for choosing the sign-in method. * + * Cards are selectable — tapping a card sets the selection state. The Continue button + * navigates to the chosen flow and is disabled until a card is selected. + * * @param onEmbeddedLogin Navigate to embedded login * @param onHostedLogin Navigate to hosted (redirect) Auth0 login * @param onSettings Navigate to settings/appearance */ @Composable fun ChooseSignInScreen( - onEmbeddedLogin: () -> Unit, onHostedLogin: () -> Unit, onSettings: () -> Unit = {} + onEmbeddedLogin: () -> Unit, + onHostedLogin: () -> Unit, + onSettings: () -> Unit = {} ) { - val colors = Auth0Theme.colors + var selectedOption by remember { mutableStateOf(null) } val typography = Auth0Theme.typography val dimensions = Auth0Theme.dimensions + val shapes = Auth0Theme.shapes + val sizes = Auth0Theme.sizes + Column( modifier = Modifier .fillMaxSize() - .background(BackGroundColor) + .auth0ScreenBackground() + .statusBarsPadding() .windowInsetsPadding(WindowInsets.navigationBars) - .verticalScroll(rememberScrollState()) - .padding(horizontal = dimensions.spacingLg), - horizontalAlignment = Alignment.CenterHorizontally ) { - Auth0LogoHeader() + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(horizontal = dimensions.spacingLg), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(dimensions.spacingSm)) + + Auth0LogoHeader() + + Spacer(modifier = Modifier.height(dimensions.spacingXxl * 2)) - Spacer(modifier = Modifier.height(dimensions.spacingXxl * 4)) + Text( + text = "Choose how to sign in", + style = typography.display, + color = colors.textBold, + textAlign = TextAlign.Center + ) - Text( - text = "Choose how to sign in", - style = typography.display, - color = colors.textBold, - textAlign = TextAlign.Center - ) + Spacer(modifier = Modifier.height(dimensions.spacingLg)) - Spacer(modifier = Modifier.height(dimensions.spacingLg)) + FactorCard( + title = "Embedded Login", + description = "Total brand control and low user frictions", + icon = painterResource(com.auth0.android.sample.R.drawable.ic_embedded_login), + isSelected = selectedOption == LoginOption.Embedded, + onClick = { selectedOption = LoginOption.Embedded } + ) - FactorCard( - title = "Embedded Login", - description = "Total brand control and low user frictions", - icon = painterResource(com.auth0.android.sample.R.drawable.ic_embedded_login), - onClick = onEmbeddedLogin, - ) + Spacer(modifier = Modifier.height(dimensions.spacingMd)) - Spacer(modifier = Modifier.height(dimensions.spacingSm)) + FactorCard( + title = "Hosted Login", + description = "Easy to setup, works instantly", + icon = painterResource(com.auth0.android.sample.R.drawable.ic_hosted_login), + isSelected = selectedOption == LoginOption.Hosted, + onClick = { selectedOption = LoginOption.Hosted } + ) - FactorCard( - title = "Hosted Login", - description = "Easy to setup, works instantly", - icon = painterResource(com.auth0.android.sample.R.drawable.ic_hosted_login), - onClick = onHostedLogin, - ) + Spacer(modifier = Modifier.height(dimensions.spacingLg)) - Spacer(modifier = Modifier.height(dimensions.spacingXxl)) + Button( + onClick = { + when (selectedOption) { + LoginOption.Embedded -> onEmbeddedLogin() + LoginOption.Hosted -> onHostedLogin() + null -> Unit + } + }, + enabled = selectedOption != null, + modifier = Modifier + .fillMaxWidth() + .height(sizes.buttonHeight), + shape = shapes.large, + colors = ButtonDefaults.buttonColors( + containerColor = colors.backgroundPrimary, + contentColor = colors.textOnPrimary + ) + ) { + Text( + text = "Continue", + style = typography.label + ) + } - Row( + Spacer(modifier = Modifier.height(dimensions.spacingLg)) + } + + // Appearance — pinned at screen bottom, outside the scroll area + TextButton( + onClick = onSettings, modifier = Modifier - .padding(dimensions.spacingXs) - .clickable(onClick = onSettings), - verticalAlignment = Alignment.CenterVertically + .fillMaxWidth() + .padding(horizontal = dimensions.spacingLg, vertical = dimensions.spacingMd) ) { Icon( painter = painterResource(com.auth0.android.sample.R.drawable.ic_appearance_prefix), - contentDescription = "Appearance", - tint = Auth0Theme.colors.backgroundPrimary, + contentDescription = null, + tint = colors.textBold, modifier = Modifier.padding(end = dimensions.spacingXs) ) Text( - text = "Appearance", style = Auth0Theme.typography.title, color = Auth0Theme.colors.textBold + text = "Appearance", + style = typography.label, + color = colors.textBold ) } - - Spacer(modifier = Modifier.height(dimensions.spacingXl)) } } diff --git a/app/src/main/java/com/auth0/android/sample/ui/screens/DashboardScreen.kt b/app/src/main/java/com/auth0/android/sample/ui/screens/DashboardScreen.kt index e2920e3..3697f54 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/screens/DashboardScreen.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/screens/DashboardScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp import com.auth0.android.sample.R import com.auth0.android.sample.ui.components.NavigationGridCard import com.auth0.android.sample.ui.components.SectionHeader +import com.auth0.android.sample.ui.theme.auth0ScreenBackground import com.auth0.universalcomponents.theme.Auth0Theme enum class DashboardDestination(val label: String, val icon: Int) { @@ -60,7 +61,9 @@ fun DashboardScreen( val dimensions = Auth0Theme.dimensions Box( - modifier = Modifier.background(colors.backgroundLayerBase) + modifier = Modifier + .fillMaxSize() + .auth0ScreenBackground() ) { Column( modifier = Modifier @@ -81,8 +84,7 @@ fun DashboardScreen( columns = GridCells.Fixed(2), contentPadding = PaddingValues(0.dp), horizontalArrangement = Arrangement.spacedBy(13.dp), - verticalArrangement = Arrangement.spacedBy(dimensions.spacingMd), - modifier = Modifier.weight(1f) + verticalArrangement = Arrangement.spacedBy(dimensions.spacingMd) ) { items(DashboardDestination.entries.toList()) { destination -> NavigationGridCard( @@ -93,6 +95,8 @@ fun DashboardScreen( } } + Spacer(modifier = Modifier.height(dimensions.spacingLg)) + TextButton(onClick = onLogout) { Text( text = "Log out", diff --git a/app/src/main/java/com/auth0/android/sample/ui/screens/EmbeddedLoginScreen.kt b/app/src/main/java/com/auth0/android/sample/ui/screens/EmbeddedLoginScreen.kt index f907bc7..cc55b89 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/screens/EmbeddedLoginScreen.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/screens/EmbeddedLoginScreen.kt @@ -2,23 +2,26 @@ package com.auth0.android.sample.ui.screens import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -27,6 +30,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation @@ -34,8 +38,8 @@ import androidx.compose.ui.unit.dp import com.auth0.android.sample.R import com.auth0.android.sample.ui.components.Auth0LogoHeader import com.auth0.android.sample.ui.components.OrDivider -import com.auth0.android.sample.ui.components.SectionHeader -import com.auth0.android.sample.ui.theme.BackGroundColor +import com.auth0.android.sample.ui.theme.auth0ScreenBackground +import com.auth0.android.sample.ui.theme.isAuth0DarkTheme import com.auth0.universalcomponents.presentation.ui.components.GradientButton import com.auth0.universalcomponents.theme.Auth0Theme @@ -62,6 +66,8 @@ fun EmbeddedLoginScreen( val dimensions = Auth0Theme.dimensions val sizes = Auth0Theme.sizes val shapes = Auth0Theme.shapes + val isDark = isAuth0DarkTheme() + val fieldFocusedBorder = if (isDark) colors.backgroundAccent else colors.backgroundPrimary var email by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } @@ -70,15 +76,32 @@ fun EmbeddedLoginScreen( Column( modifier = Modifier .fillMaxSize() - .background(BackGroundColor) + .auth0ScreenBackground() + .statusBarsPadding() .padding(horizontal = dimensions.spacingLg), horizontalAlignment = Alignment.CenterHorizontally ) { + Row(modifier = Modifier.fillMaxWidth()) { + IconButton(onClick = onBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = colors.textBold + ) + } + } + Auth0LogoHeader() Spacer(modifier = Modifier.height(dimensions.spacingXxl)) - SectionHeader(title = "Login or Signup to continue") + Text( + text = "Login or Signup to continue", + style = typography.displayMedium, + color = colors.textBold, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) Spacer(modifier = Modifier.height(dimensions.spacingLg)) @@ -89,7 +112,10 @@ fun EmbeddedLoginScreen( .fillMaxWidth() .height(sizes.buttonHeight), shape = shapes.large, - border = BorderStroke(1.dp, colors.borderDefault) + border = BorderStroke(1.dp, colors.borderDefault), + colors = ButtonDefaults.outlinedButtonColors( + containerColor = if (isDark) colors.backgroundLayerMedium else colors.backgroundLayerTop + ) ) { Image( painter = painterResource(id = R.drawable.ic_google), @@ -131,12 +157,15 @@ fun EmbeddedLoginScreen( ) }, textStyle = typography.body, - shape = shapes.medium, + shape = shapes.large, colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = colors.backgroundPrimary, + focusedBorderColor = fieldFocusedBorder, unfocusedBorderColor = colors.borderDefault, - focusedContainerColor = colors.backgroundLayerTop, - unfocusedContainerColor = colors.backgroundLayerTop + focusedContainerColor = if (isDark) colors.backgroundLayerMedium else colors.backgroundLayerTop, + unfocusedContainerColor = if (isDark) colors.backgroundLayerMedium else colors.backgroundLayerTop, + focusedTextColor = colors.textBold, + unfocusedTextColor = colors.textBold, + cursorColor = fieldFocusedBorder ), singleLine = true ) @@ -163,7 +192,7 @@ fun EmbeddedLoginScreen( ) }, textStyle = typography.body, - shape = shapes.medium, + shape = shapes.large, visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), trailingIcon = { @@ -178,10 +207,13 @@ fun EmbeddedLoginScreen( } }, colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = colors.backgroundPrimary, + focusedBorderColor = fieldFocusedBorder, unfocusedBorderColor = colors.borderDefault, - focusedContainerColor = colors.backgroundLayerTop, - unfocusedContainerColor = colors.backgroundLayerTop + focusedContainerColor = if (isDark) colors.backgroundLayerMedium else colors.backgroundLayerTop, + unfocusedContainerColor = if (isDark) colors.backgroundLayerMedium else colors.backgroundLayerTop, + focusedTextColor = colors.textBold, + unfocusedTextColor = colors.textBold, + cursorColor = fieldFocusedBorder ), singleLine = true ) diff --git a/app/src/main/java/com/auth0/android/sample/ui/screens/ExploreLoginScreen.kt b/app/src/main/java/com/auth0/android/sample/ui/screens/ExploreLoginScreen.kt index 79c2fcd..f4f77f4 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/screens/ExploreLoginScreen.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/screens/ExploreLoginScreen.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.res.painterResource import com.auth0.android.sample.ui.components.Auth0LogoHeader import com.auth0.android.sample.ui.components.FactorCard import com.auth0.android.sample.ui.components.SectionHeader -import com.auth0.android.sample.ui.theme.BackGroundColor +import com.auth0.android.sample.ui.theme.auth0ScreenBackground import com.auth0.universalcomponents.theme.Auth0Theme /** @@ -40,7 +40,7 @@ fun ExploreLoginScreen( Column( modifier = Modifier .fillMaxSize() - .background(BackGroundColor) + .auth0ScreenBackground() .verticalScroll(rememberScrollState()) .padding(horizontal = dimensions.spacingLg), horizontalAlignment = Alignment.CenterHorizontally diff --git a/app/src/main/java/com/auth0/android/sample/ui/theme/BackgroundModifier.kt b/app/src/main/java/com/auth0/android/sample/ui/theme/BackgroundModifier.kt new file mode 100644 index 0000000..5be5c2b --- /dev/null +++ b/app/src/main/java/com/auth0/android/sample/ui/theme/BackgroundModifier.kt @@ -0,0 +1,36 @@ +package com.auth0.android.sample.ui.theme + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.foundation.background +import com.auth0.universalcomponents.theme.Auth0Theme + +/** + * Returns true when the active Auth0Theme is in dark mode. + * + * Centralises the dark-mode check so it only needs updating in one place + * if the underlying token changes. + */ +@Composable +fun isAuth0DarkTheme(): Boolean = Auth0Theme.colors.backgroundLayerBase.red < 0.1f + +/** + * Applies the Auth0 screen background to this modifier. + * + * Light theme: vertical gradient [BackGroundColor] + * Dark theme: flat [DarkBackGroundColor] (#09090B) + * + * Theme mode is detected from [Auth0Theme.colors] so it correctly follows the in-app + * theme switcher, not just the OS dark mode setting. + */ +@Composable +fun Modifier.auth0ScreenBackground(): Modifier { + val isDark = isAuth0DarkTheme() + val brush = if (isDark) DarkBackGroundColor else BackGroundColor + return this + .background(brush) + .run { + if (!isDark) background(BottomWarmOverlay).background(BottomCoolOverlay) + else this + } +} diff --git a/app/src/main/java/com/auth0/android/sample/ui/theme/Color.kt b/app/src/main/java/com/auth0/android/sample/ui/theme/Color.kt index c19e8b1..41c31d9 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/theme/Color.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/theme/Color.kt @@ -12,14 +12,42 @@ val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) val Pink40 = Color(0xFF7D5260) - +/** + * Light theme background — vertical base gradient. + * + */ val BackGroundColor = Brush.linearGradient( - colors = listOf( - Color(0xFFF4F4F5), - Color(0xFFFCFCFC), - Color(0xFFECE5EB), - Color(0xFFEDE9F1) + colorStops = arrayOf( + 0.00f to Color(0xFFF4F4F5), + 0.75f to Color(0xFFEDEBF1), + 1.00f to Color(0xFFEBE1E4) ), start = Offset(0f, 0f), end = Offset(0f, Float.POSITIVE_INFINITY) -) \ No newline at end of file +) + +/** + * Blended on top of [BackGroundColor] to reproduce the #E9D7D4 bottom-left sample. + */ +val BottomWarmOverlay = Brush.radialGradient( + colors = listOf(Color(0x99E9D7D4), Color(0x00E9D7D4)), + center = Offset(0f, Float.POSITIVE_INFINITY), + radius = 900f +) + +/** + * Blended on top of [BackGroundColor] to reproduce the #EDEBF4 bottom-right sample. + */ +val BottomCoolOverlay = Brush.radialGradient( + colors = listOf(Color(0x99EDEBF4), Color(0x00EDEBF4)), + center = Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY), + radius = 900f +) + +/** + * Dark theme background — flat #09090B (Auth0 backgroundLayerBase dark). + * + */ +val DarkBackGroundColor = Brush.linearGradient( + colors = listOf(Color(0xFF09090B), Color(0xFF09090B)) +) diff --git a/app/src/main/java/com/auth0/android/sample/ui/viewmodels/AuthViewModel.kt b/app/src/main/java/com/auth0/android/sample/ui/viewmodels/AuthViewModel.kt index 1102e7a..8fc0bf7 100644 --- a/app/src/main/java/com/auth0/android/sample/ui/viewmodels/AuthViewModel.kt +++ b/app/src/main/java/com/auth0/android/sample/ui/viewmodels/AuthViewModel.kt @@ -60,10 +60,12 @@ class AuthViewModel : ViewModel() { _authState.value = AuthState.Authenticated(credentials) } catch (e: AuthenticationException) { if (e.isBrowserAppNotAvailable || e.isCanceled) { - Log.d("TAG", "login: User cancelled or browser not available") + Log.i("TAG", "login: User cancelled or browser not available") + _loginError.send("User cancelled or browser not available\"") _authState.value = AuthState.Idle } else { Log.e("TAG", "login: ${e.printStackTrace()}") + _loginError.send(e.message ?: "An unknown error occurred") _authState.value = AuthState.Error(e.message ?: "An unknown error occurred") } }