Ir al contenido

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).

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.

@Composable
fun 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):

  1. Viñeta radial — de cardBackgroundLightTone(backgroundIndex) (centro) a cardBackgroundDarkTone(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.
  2. Rayos diagonales — un sweepGradient con franjas alternadas de blanco translúcido (cardRayHighlightStops), anclado a la esquina superior derecha.
  3. 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.

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.

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.

Pastillas de color sólido + texto en Inter Bold, con una API casi idéntica entre ambas:

@Composable
fun ClassChip(cls: String, modifier: Modifier = Modifier, compact: Boolean = false, heightDp: Dp? = null)
@Composable
fun 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.

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.",
)

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 de CardBackgroundColors, 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:
Escudería Raven

Raven

Escudería Alpine

Alpine

Escudería Astra

Astra

Escudería Vortex

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, ...) // TextButton

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)
@Composable
fun 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.