From efeeee1eedf31476865cfc3ed650d0668e486175 Mon Sep 17 00:00:00 2001 From: Sudhanshu Vohra Date: Wed, 1 Jul 2026 15:48:08 +0530 Subject: [PATCH 1/2] Shimmer implemented on Authenticator screen --- CHANGELOG.md | 9 + README.md | 1 + .../ui/components/skeleton/Shimmer.kt | 126 +++++++++++++ .../ui/components/skeleton/SkeletonCards.kt | 172 ++++++++++++++++++ .../ui/components/skeleton/SkeletonShape.kt | 35 ++++ .../ui/mfa/AuthenticatorMethodsScreen.kt | 25 ++- .../ui/mfa/EnrolledAuthenticatorListScreen.kt | 7 + .../src/main/res/values/strings.xml | 1 + 8 files changed, 372 insertions(+), 4 deletions(-) create mode 100644 universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/Shimmer.kt create mode 100644 universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonCards.kt create mode 100644 universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonShape.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 732d544..cf3b9aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## [Unreleased] + +Added + +- **Skeleton shimmer loading** โ€” `AuthenticatorMethodsScreen` and + `EnrolledAuthenticatorListScreen` now show animated, theme-aware skeleton placeholders + while data loads, replacing the previous spinner. Supports light/dark mode and honours + the system "remove animations" setting. + ## [1.0.0-beta.0](https://github.com/auth0/ui-components-android/tree/1.0.0-beta.0) (2026-06-01) Auth0UniversalComponents Android โ€” Beta Release diff --git a/README.md b/README.md index dd05692..4fae49f 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ This library provides ready-to-use UI components for multi-factor authentication - ๐Ÿ’ฌ **SMS OTP** - Phone number verification via one-time codes - ๐Ÿ“ง **Email OTP** - Email-based verification - ๐Ÿ”‘ **Recovery Codes** - Backup authentication codes for account recovery +- โœจ **Skeleton shimmer loading** - Card screens show animated, theme-aware skeleton placeholders while data loads (light/dark mode, honours the system animation setting) All components are built on top of the [Auth0 Android SDK](https://github.com/auth0/Auth0.Android) and integrate with Auth0's My Account APIs. diff --git a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/Shimmer.kt b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/Shimmer.kt new file mode 100644 index 0000000..d291786 --- /dev/null +++ b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/Shimmer.kt @@ -0,0 +1,126 @@ +package com.auth0.universalcomponents.presentation.ui.components.skeleton + +import android.provider.Settings +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.platform.LocalContext +import com.auth0.universalcomponents.theme.Auth0Theme + +private const val SHIMMER_DURATION_MS = 1300 +private const val BAND_WIDTH_MULTIPLIER = 2f +private const val BAND_TRAVEL_MULTIPLIER = 3f +private const val LUMINANCE_DARK_THRESHOLD = 0.5f + +// Purpose-built skeleton greys. Declared as properties (not inline literals) so the colour +// hex values are exempt from detekt's MagicNumber rule, mirroring how the theme tokens are +// handled. +private val DarkSkeletonBase = Color(0xFF383838) +private val DarkSkeletonHighlight = Color(0xFF575757) +private val LightSkeletonBase = Color(0xFFE0E0E0) +private val LightSkeletonHighlight = Color(0xFFF7F7F7) + +private val DarkSkeletonPalette = SkeletonPalette(base = DarkSkeletonBase, highlight = DarkSkeletonHighlight) +private val LightSkeletonPalette = SkeletonPalette(base = LightSkeletonBase, highlight = LightSkeletonHighlight) + +/** + * Light/dark greys for skeletons. The SDK's layer tokens are near-white-on-white in + * light mode (an invisible sweep), so we use purpose-built greys that adapt to the + * colour scheme. + */ +internal data class SkeletonPalette(val base: Color, val highlight: Color) { + companion object { + fun forScheme(dark: Boolean): SkeletonPalette = + if (dark) DarkSkeletonPalette else LightSkeletonPalette + } +} + +/** + * Resolves the active skeleton palette from the current [Auth0Theme]. + * + * Uses the resolved theme background's luminance rather than [androidx.compose.foundation.isSystemInDarkTheme] + * so that a consumer who forces `Auth0Theme(darkTheme = โ€ฆ)` (independent of the system + * setting) still gets greys that match the rendered surface. + */ +@Composable +internal fun rememberSkeletonPalette(): SkeletonPalette { + val dark = Auth0Theme.colors.backgroundLayerBase.luminance() < LUMINANCE_DARK_THRESHOLD + return remember(dark) { SkeletonPalette.forScheme(dark) } +} + +/** + * Sweeps an animated shimmer highlight across this composable to indicate loading. + * Apply to ONE container (a list/Column) so every child animates in a single + * synchronized sweep โ€” the highlight is clipped to the content that is actually drawn. + * + * When [active] is false, or the system "remove animations" setting is on, this is a + * no-op and the placeholders render as static base-grey blocks (the reduce-motion + * fallback). + */ +@Composable +internal fun Modifier.shimmer(active: Boolean = true): Modifier { + if (!active || !animationsEnabled()) return this + + val palette = rememberSkeletonPalette() + val transition = rememberInfiniteTransition(label = "shimmer") + val phase by transition.animateFloat( + initialValue = 0f, + targetValue = 1f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = SHIMMER_DURATION_MS, easing = LinearEasing), + repeatMode = RepeatMode.Restart, // sweep travels in one direction only + ), + label = "shimmer-phase", + ) + + // SrcAtop masks to the content only when that content is drawn into an isolated + // buffer; without an offscreen layer the band would paint across the whole bounding + // rect (including the gaps between cards), instead of just the drawn placeholders. + return this + .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + // `phase` is read inside the draw lambda โ†’ only the draw phase re-runs per frame, + // not recomposition. + .drawWithContent { + drawContent() // 1) draw the real placeholders first + val w = size.width + // base โ†’ highlight โ†’ base band, 2ร— width, translated by (phase*3 - 2)*w. + val startX = (phase * BAND_TRAVEL_MULTIPLIER - BAND_WIDTH_MULTIPLIER) * w + val brush = Brush.linearGradient( + colors = listOf(palette.base, palette.highlight, palette.base), + start = Offset(startX, 0f), + end = Offset(startX + w * BAND_WIDTH_MULTIPLIER, 0f), + ) + // 2) paint the band ONLY over already-drawn pixels โ†’ clips the sweep to the content. + drawRect(brush = brush, blendMode = BlendMode.SrcAtop) + } +} + +/** + * Compose has no direct `accessibilityReduceMotion`. Honour the system "remove animations" + * setting instead: when animations are scaled to 0, skip the sweep (static placeholders). + */ +@Composable +internal fun animationsEnabled(): Boolean { + val context = LocalContext.current + val scale = Settings.Global.getFloat( + context.contentResolver, + Settings.Global.ANIMATOR_DURATION_SCALE, + 1f, + ) + return scale != 0f +} diff --git a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonCards.kt b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonCards.kt new file mode 100644 index 0000000..6a6fe73 --- /dev/null +++ b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonCards.kt @@ -0,0 +1,172 @@ +package com.auth0.universalcomponents.presentation.ui.components.skeleton + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +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.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.auth0.universalcomponents.theme.Auth0Theme + +private val AuthMethodCardHeight = 70.dp +private val EnrolledCardHeight = 84.dp +private val EnrolledCardPadding = 20.dp +private val AuthMethodTitleWidth = 140.dp +private val EnrolledTitleWidth = 180.dp +private val EnrolledSubtitleWidth = 120.dp + +/** + * Repeats a skeleton [row] [count] times. Intentionally does NOT shimmer itself โ€” apply + * Modifier.shimmer() ONCE on the screen's loading container (see the screens) so the header + * line and every card animate in a single synchronized sweep. + */ +@Composable +internal fun SkeletonList( + count: Int = 5, + modifier: Modifier = Modifier, + spacing: Dp = Auth0Theme.dimensions.spacingMd, + row: @Composable () -> Unit, +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(spacing), + ) { + repeat(count) { row() } + } +} + +/** + * Border-only card shell matching the real cards' size, border, and radius (NO fill โ€” + * a grey sweep over the near-white `backgroundLayerMedium` fill would read as a dark band + * crossing a white card). Size, border, and radius still match the real card so there's no + * layout shift when data arrives. Parametrised because the two real cards differ in + * height/padding. + */ +@Composable +internal fun SkeletonCard( + height: Dp, + contentPadding: Dp, + verticalAlignment: Alignment.Vertical = Alignment.CenterVertically, + content: @Composable RowScope.() -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(height) + .border(1.dp, Auth0Theme.colors.borderDefault, Auth0Theme.shapes.large) + .padding(contentPadding), + verticalAlignment = verticalAlignment, + content = content, + ) +} + +/** Mirrors AuthenticatorItem: leading icon, title line, trailing chevron. */ +@Composable +internal fun AuthMethodCardSkeleton() = SkeletonCard( + height = AuthMethodCardHeight, + contentPadding = Auth0Theme.sizes.padding, // 16.dp +) { + SkeletonBox(Modifier.size(Auth0Theme.sizes.iconMedium), shape = Auth0Theme.shapes.small) + Spacer(Modifier.width(Auth0Theme.dimensions.spacingMd)) + SkeletonLine(width = AuthMethodTitleWidth) + Spacer(Modifier.weight(1f)) + SkeletonBox(Modifier.size(Auth0Theme.sizes.iconMedium), shape = Auth0Theme.shapes.small) +} + +/** Mirrors EnrolledAuthenticatorItem: title + "created on" line + trailing menu glyph. */ +@Composable +internal fun EnrolledAuthenticatorCardSkeleton() = SkeletonCard( + height = EnrolledCardHeight, + contentPadding = EnrolledCardPadding, + verticalAlignment = Alignment.Top, +) { + Column(verticalArrangement = Arrangement.spacedBy(Auth0Theme.dimensions.spacingXxs)) { + SkeletonLine(width = EnrolledTitleWidth) + SkeletonLine(width = EnrolledSubtitleWidth) + } + Spacer(Modifier.weight(1f)) + SkeletonBox(Modifier.size(Auth0Theme.sizes.iconMedium), shape = Auth0Theme.shapes.small) +} + +// region Previews + +@Preview(name = "Auth method skeleton ยท light", showBackground = true) +@Composable +private fun AuthMethodSkeletonLightPreview() { + Auth0Theme(darkTheme = false) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(Auth0Theme.sizes.padding) + .shimmer(), + ) { + SkeletonLine(width = EnrolledTitleWidth, modifier = Modifier.height(22.dp)) + Spacer(Modifier.height(Auth0Theme.dimensions.spacingMd)) + SkeletonList(count = 3) { AuthMethodCardSkeleton() } + } + } +} + +@Preview(name = "Auth method skeleton ยท dark", showBackground = true) +@Composable +private fun AuthMethodSkeletonDarkPreview() { + Auth0Theme(darkTheme = true) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(Auth0Theme.sizes.padding) + .shimmer(), + ) { + SkeletonLine(width = EnrolledTitleWidth, modifier = Modifier.height(22.dp)) + Spacer(Modifier.height(Auth0Theme.dimensions.spacingMd)) + SkeletonList(count = 3) { AuthMethodCardSkeleton() } + } + } +} + +@Preview(name = "Enrolled skeleton ยท light", showBackground = true) +@Composable +private fun EnrolledSkeletonLightPreview() { + Auth0Theme(darkTheme = false) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(Auth0Theme.sizes.padding) + .shimmer(), + ) { + SkeletonList(count = 3, spacing = Auth0Theme.dimensions.spacingSm) { + EnrolledAuthenticatorCardSkeleton() + } + } + } +} + +@Preview(name = "Enrolled skeleton ยท dark", showBackground = true) +@Composable +private fun EnrolledSkeletonDarkPreview() { + Auth0Theme(darkTheme = true) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(Auth0Theme.sizes.padding) + .shimmer(), + ) { + SkeletonList(count = 3, spacing = Auth0Theme.dimensions.spacingSm) { + EnrolledAuthenticatorCardSkeleton() + } + } + } +} + +// endregion diff --git a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonShape.kt b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonShape.kt new file mode 100644 index 0000000..3180186 --- /dev/null +++ b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonShape.kt @@ -0,0 +1,35 @@ +package com.auth0.universalcomponents.presentation.ui.components.skeleton + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** Default height of a single text-line placeholder. */ +private val SkeletonLineHeight = 14.dp + +/** + * A single themed placeholder block โ€” the atom of the skeleton system. Compose several + * inside Rows/Columns to mirror a real layout, then wrap the parent in Modifier.shimmer(). + * Renders the resting base grey; the sweep is layered on by the parent's shimmer(). + */ +@Composable +internal fun SkeletonBox( + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(8.dp), +) { + val base = rememberSkeletonPalette().base + Box(modifier.background(color = base, shape = shape)) +} + +/** Convenience: a single text-line placeholder (default 14.dp tall). */ +@Composable +internal fun SkeletonLine(width: Dp, modifier: Modifier = Modifier) { + SkeletonBox(modifier.width(width).height(SkeletonLineHeight)) +} diff --git a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/AuthenticatorMethodsScreen.kt b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/AuthenticatorMethodsScreen.kt index 10eda46..ab121e5 100644 --- a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/AuthenticatorMethodsScreen.kt +++ b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/AuthenticatorMethodsScreen.kt @@ -3,7 +3,9 @@ package com.auth0.universalcomponents.presentation.ui.mfa import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -15,6 +17,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.auth0.universalcomponents.R @@ -22,6 +27,10 @@ import com.auth0.universalcomponents.di.UniversalComponentsModule import com.auth0.universalcomponents.presentation.ui.components.CircularLoader import com.auth0.universalcomponents.presentation.ui.components.ErrorHandler import com.auth0.universalcomponents.presentation.ui.components.TopBar +import com.auth0.universalcomponents.presentation.ui.components.skeleton.AuthMethodCardSkeleton +import com.auth0.universalcomponents.presentation.ui.components.skeleton.SkeletonLine +import com.auth0.universalcomponents.presentation.ui.components.skeleton.SkeletonList +import com.auth0.universalcomponents.presentation.ui.components.skeleton.shimmer import com.auth0.universalcomponents.presentation.ui.mfa.authenticatormethods.PrimaryAuthenticatorListScreen import com.auth0.universalcomponents.presentation.ui.mfa.authenticatormethods.SecondaryAuthenticatorListScreen import com.auth0.universalcomponents.presentation.ui.passkeys.PasskeyEvent @@ -106,11 +115,19 @@ internal fun AuthenticatorMethodsScreen( } AuthenticatorUiState.Loading -> { - Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.Center + // Skeleton mirrors the loaded layout โ†’ no layout shift when data arrives. + // One shimmer on the container = one synchronized sweep over header + cards. + val loadingDescription = stringResource(R.string.loading) + Column( + modifier = Modifier + .fillMaxSize() + .padding(vertical = dimensions.spacingMd) + .shimmer() + .semantics { contentDescription = loadingDescription }, ) { - CircularLoader() + SkeletonLine(width = 180.dp, modifier = Modifier.height(22.dp)) + Spacer(Modifier.height(dimensions.spacingMd)) + SkeletonList(count = 5) { AuthMethodCardSkeleton() } } } diff --git a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt index 4bd773d..16de694 100644 --- a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt +++ b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt @@ -22,6 +22,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.auth0.universalcomponents.R @@ -33,6 +36,10 @@ import com.auth0.universalcomponents.presentation.ui.components.EmptyAuthenticat import com.auth0.universalcomponents.presentation.ui.components.EnrolledAuthenticatorItem import com.auth0.universalcomponents.presentation.ui.components.ErrorHandler import com.auth0.universalcomponents.presentation.ui.components.TopBar +import com.auth0.universalcomponents.presentation.ui.components.skeleton.EnrolledAuthenticatorCardSkeleton +import com.auth0.universalcomponents.presentation.ui.components.skeleton.SkeletonLine +import com.auth0.universalcomponents.presentation.ui.components.skeleton.SkeletonList +import com.auth0.universalcomponents.presentation.ui.components.skeleton.shimmer import com.auth0.universalcomponents.presentation.ui.menu.MenuAction import com.auth0.universalcomponents.presentation.ui.menu.MenuItem import com.auth0.universalcomponents.presentation.ui.passkeys.PasskeyEvent diff --git a/universal_components/src/main/res/values/strings.xml b/universal_components/src/main/res/values/strings.xml index 8d017c5..f43ca8a 100644 --- a/universal_components/src/main/res/values/strings.xml +++ b/universal_components/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Copy as Code Active Remove + Loading Add Phone for SMS OTP From 724a2e1b90076eb26500558953ea815d98b3465e Mon Sep 17 00:00:00 2001 From: Sudhanshu Vohra Date: Thu, 2 Jul 2026 14:23:29 +0530 Subject: [PATCH 2/2] Code refactored and removed dead code --- CHANGELOG.md | 9 -- .../ui/components/skeleton/SkeletonCards.kt | 92 ------------------- .../ui/mfa/EnrolledAuthenticatorListScreen.kt | 7 -- 3 files changed, 108 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf3b9aa..732d544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,5 @@ # Change Log -## [Unreleased] - -Added - -- **Skeleton shimmer loading** โ€” `AuthenticatorMethodsScreen` and - `EnrolledAuthenticatorListScreen` now show animated, theme-aware skeleton placeholders - while data loads, replacing the previous spinner. Supports light/dark mode and honours - the system "remove animations" setting. - ## [1.0.0-beta.0](https://github.com/auth0/ui-components-android/tree/1.0.0-beta.0) (2026-06-01) Auth0UniversalComponents Android โ€” Beta Release diff --git a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonCards.kt b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonCards.kt index 6a6fe73..91660ad 100644 --- a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonCards.kt +++ b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/components/skeleton/SkeletonCards.kt @@ -14,17 +14,12 @@ import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.auth0.universalcomponents.theme.Auth0Theme private val AuthMethodCardHeight = 70.dp -private val EnrolledCardHeight = 84.dp -private val EnrolledCardPadding = 20.dp private val AuthMethodTitleWidth = 140.dp -private val EnrolledTitleWidth = 180.dp -private val EnrolledSubtitleWidth = 120.dp /** * Repeats a skeleton [row] [count] times. Intentionally does NOT shimmer itself โ€” apply @@ -83,90 +78,3 @@ internal fun AuthMethodCardSkeleton() = SkeletonCard( Spacer(Modifier.weight(1f)) SkeletonBox(Modifier.size(Auth0Theme.sizes.iconMedium), shape = Auth0Theme.shapes.small) } - -/** Mirrors EnrolledAuthenticatorItem: title + "created on" line + trailing menu glyph. */ -@Composable -internal fun EnrolledAuthenticatorCardSkeleton() = SkeletonCard( - height = EnrolledCardHeight, - contentPadding = EnrolledCardPadding, - verticalAlignment = Alignment.Top, -) { - Column(verticalArrangement = Arrangement.spacedBy(Auth0Theme.dimensions.spacingXxs)) { - SkeletonLine(width = EnrolledTitleWidth) - SkeletonLine(width = EnrolledSubtitleWidth) - } - Spacer(Modifier.weight(1f)) - SkeletonBox(Modifier.size(Auth0Theme.sizes.iconMedium), shape = Auth0Theme.shapes.small) -} - -// region Previews - -@Preview(name = "Auth method skeleton ยท light", showBackground = true) -@Composable -private fun AuthMethodSkeletonLightPreview() { - Auth0Theme(darkTheme = false) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(Auth0Theme.sizes.padding) - .shimmer(), - ) { - SkeletonLine(width = EnrolledTitleWidth, modifier = Modifier.height(22.dp)) - Spacer(Modifier.height(Auth0Theme.dimensions.spacingMd)) - SkeletonList(count = 3) { AuthMethodCardSkeleton() } - } - } -} - -@Preview(name = "Auth method skeleton ยท dark", showBackground = true) -@Composable -private fun AuthMethodSkeletonDarkPreview() { - Auth0Theme(darkTheme = true) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(Auth0Theme.sizes.padding) - .shimmer(), - ) { - SkeletonLine(width = EnrolledTitleWidth, modifier = Modifier.height(22.dp)) - Spacer(Modifier.height(Auth0Theme.dimensions.spacingMd)) - SkeletonList(count = 3) { AuthMethodCardSkeleton() } - } - } -} - -@Preview(name = "Enrolled skeleton ยท light", showBackground = true) -@Composable -private fun EnrolledSkeletonLightPreview() { - Auth0Theme(darkTheme = false) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(Auth0Theme.sizes.padding) - .shimmer(), - ) { - SkeletonList(count = 3, spacing = Auth0Theme.dimensions.spacingSm) { - EnrolledAuthenticatorCardSkeleton() - } - } - } -} - -@Preview(name = "Enrolled skeleton ยท dark", showBackground = true) -@Composable -private fun EnrolledSkeletonDarkPreview() { - Auth0Theme(darkTheme = true) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(Auth0Theme.sizes.padding) - .shimmer(), - ) { - SkeletonList(count = 3, spacing = Auth0Theme.dimensions.spacingSm) { - EnrolledAuthenticatorCardSkeleton() - } - } - } -} - -// endregion diff --git a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt index 16de694..4bd773d 100644 --- a/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt +++ b/universal_components/src/main/java/com/auth0/universalcomponents/presentation/ui/mfa/EnrolledAuthenticatorListScreen.kt @@ -22,9 +22,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.auth0.universalcomponents.R @@ -36,10 +33,6 @@ import com.auth0.universalcomponents.presentation.ui.components.EmptyAuthenticat import com.auth0.universalcomponents.presentation.ui.components.EnrolledAuthenticatorItem import com.auth0.universalcomponents.presentation.ui.components.ErrorHandler import com.auth0.universalcomponents.presentation.ui.components.TopBar -import com.auth0.universalcomponents.presentation.ui.components.skeleton.EnrolledAuthenticatorCardSkeleton -import com.auth0.universalcomponents.presentation.ui.components.skeleton.SkeletonLine -import com.auth0.universalcomponents.presentation.ui.components.skeleton.SkeletonList -import com.auth0.universalcomponents.presentation.ui.components.skeleton.shimmer import com.auth0.universalcomponents.presentation.ui.menu.MenuAction import com.auth0.universalcomponents.presentation.ui.menu.MenuItem import com.auth0.universalcomponents.presentation.ui.passkeys.PasskeyEvent