Catálogo de componentes
Tres archivos concentran todo lo reutilizable: CardComponents.kt (la carta y sus piezas),
Buttons.kt (variantes de botón) e Inputs.kt (estilos compartidos de campo de texto).
ArtworkFace / CardArtwork
Sección titulada «ArtworkFace / CardArtwork»El corazón visual de la app. ArtworkFace dibuja sólo el contenido (foto del auto, viñeta de
color, nombre, chips, roundel de número, badge) sin marco propio; CardArtwork lo envuelve en un
OutlinedCard con el radio Radius.xl — es la que usan Gallery, Details y el formulario.
@Composablefun ArtworkFace( name: String, cls: String, carNumber: String, modifier: Modifier = Modifier, compact: Boolean = false, carImageIndex: Int = 0, backgroundIndex: Int = 0, badgeIndex: Int = 0, showOverlays: Boolean = true, tier: String? = null,)Capas del fondo, dibujadas en drawWithCache (una sola pasada, cacheada entre recomposiciones):
- Viñeta radial — de
cardBackgroundLightTone(backgroundIndex)(centro) acardBackgroundDarkTone(backgroundIndex)(bordes), ambas derivadas del mismo color de fondo elegido, para que cada color de carta se sienta “vivo” en vez de un relleno plano. - Rayos diagonales — un
sweepGradientcon franjas alternadas de blanco translúcido (cardRayHighlightStops), anclado a la esquina superior derecha. - Sombra de contacto — una elipse radial oscura detrás del auto, achatada verticalmente
(
scaleY = 0.22f) para leerse como sombra proyectada y no como un círculo gris.
Overlays (sólo si showOverlays = true): nombre (esquina superior-izq.), badge de escudería
(superior-der.), chip de clase + chip de rareza (inferior-izq.) y el roundel del número de auto
(inferior-der., un círculo con borde sobre MaterialTheme.colorScheme.surface).
compact reduce paddings y tamaños de fuente/ícono para las celdas densas de la Gallery en modo
grid; el modo showOverlays = false es el que usa PerformanceRollOverlay para mostrar sólo el
auto sobre su viñeta, a pantalla completa, sin marco de carta.
CarArtworkPager
Sección titulada «CarArtworkPager»CardArtwork envuelta en un HorizontalPager (uno por cada una de las 4 fotos de auto), con
indicadores de página tipo “dots”. Es la que usa EditScreen como preview en vivo del
formulario — deslizar cambia carImageIndex y lo reporta vía onIndexChange.
CardWithQuickInfo
Sección titulada «CardWithQuickInfo»La carta (ArtworkFace con aspectRatio = 4f/3f) + un panel inferior con el título “Performance”,
el chip de rareza, y las 4 StatItem en fila — todo dentro de un mismo OutlinedCard. Es lo que
renderiza DetailsScreen.
Chips: ClassChip y TierChip
Sección titulada «Chips: ClassChip y TierChip»Pastillas de color sólido + texto en Inter Bold, con una API casi idéntica entre ambas:
@Composablefun ClassChip(cls: String, modifier: Modifier = Modifier, compact: Boolean = false, heightDp: Dp? = null)
@Composablefun TierChip(tier: String, modifier: Modifier = Modifier, compact: Boolean = false, heightDp: Dp? = null)heightDp existe para poder igualar la altura del roundel de número cuando ambos comparten
fila en la esquina inferior de la carta. TierChip además calcula el color del texto en base a
la luminancia del fondo (tierColor(tier).luminance() > 0.5f), así el amarillo de la rareza S
siempre lleva texto oscuro y el resto lleva texto blanco.
EmptyState
Sección titulada «EmptyState»El layout compartido para “no hay nada que mostrar todavía”: ícono en un círculo amarillo + título en negrita + subtítulo atenuado. Lo usan tanto la Gallery vacía como Details sin carta seleccionada, cada una con su propio ícono/copy.
EmptyState( icon = Icons.Filled.DirectionsCar, title = "No cars yet", subtitle = "Tap Create to add your first car.",)StatItem y SpecRow
Sección titulada «StatItem y SpecRow»Dos formas de mostrar un par ícono+etiqueta+valor: StatItem es la versión vertical
(ícono arriba, etiqueta, valor — usada en las 4 columnas de estadísticas) y SpecRow es la
versión horizontal (ícono + etiqueta a la izquierda, valor a la derecha; declarada pero no
usada actualmente en ninguna pantalla).
Pickers del formulario: SwatchPickerRow y BadgePickerRow
Sección titulada «Pickers del formulario: SwatchPickerRow y BadgePickerRow»Ambos son filas de opciones seleccionables con el mismo lenguaje visual: borde de 2dp en el color
primary cuando está seleccionado, 1dp en outlineVariant cuando no.
SwatchPickerRow— un cuadro de color sólido por cada entrada deCardBackgroundColors, con un check (☑) superpuesto en la opción activa (color de check adaptado a la luminancia del swatch).BadgePickerRow— la imagen de cada escudería disponible:

Raven

Alpine

Astra

Vortex
El nombre de marca de la carta (RacingCard.brand) no se escribe a mano: se deriva del
badge elegido con badgeBrandName(badgeIndex), un simple List<String> indexado en paralelo a
badgeImages.
Botones: PrimaryButton / SecondaryButton / TertiaryButton
Sección titulada «Botones: PrimaryButton / SecondaryButton / TertiaryButton»Tres variantes con texto siempre en mayúsculas + letterSpacing, definidas en Buttons.kt pero
actualmente no consumidas por ninguna pantalla — las pantallas reales usan directamente
FilledIconButton (check de guardar), OutlinedButton (eliminar) y TextButton (diálogo) de
Material 3. Quedan como el sistema “oficial” de botones de marca para cuando se necesiten CTAs de
texto completo.
@Composable fun PrimaryButton(text: String, onClick: () -> Unit, ...) // Button relleno, color primary@Composable fun SecondaryButton(text: String, onClick: () -> Unit, ...) // OutlinedButton@Composable fun TertiaryButton(text: String, onClick: () -> Unit, ...) // TextButtonCampos de texto: Inputs.kt
Sección titulada «Campos de texto: Inputs.kt»Tres constantes compartidas por todo OutlinedTextField/ExposedDropdownMenuBox de la app,
para que cada “elegí uno de estos” (filtro de Gallery, clase, badge…) se vea y sienta igual:
val AppFieldShape = RoundedCornerShape(Radius.sm)
@Composablefun appFieldColors(): TextFieldColors = OutlinedTextFieldDefaults.colors( unfocusedBorderColor = MaterialTheme.colorScheme.outlineVariant, focusedBorderColor = MaterialTheme.colorScheme.primary,)
val SelectFieldTextStyle: TextStyle @Composable get() = MaterialTheme.typography.labelLarge.copy( fontWeight = FontWeight.Bold, letterSpacing = 0.5.sp, )appFieldColors() existe puntualmente porque el borde outline por defecto de Material 3 se ve
demasiado claro sobre el fondo oscuro de marca — se reemplaza por outlineVariant, más discreto,
y el color de marca sólo aparece al enfocar el campo.
