Ir al contenido

Create / Edit

EditScreen es una sola pantalla con dos usos: crear una carta nueva (tab Create, isNewCard = true) y editar una carta existente (isEditingCard = true, llamada desde Details).

Auto de ejemplo en el formulario
@Composable
fun EditScreen(
modifier: Modifier = Modifier,
card: RacingCard,
title: String = "Edit Card",
isNewCard: Boolean = false,
onBack: () -> Unit = {},
onSave: (RacingCard) -> Unit = {},
)

Todo el formulario es estado local inicializado desde card (la carta a editar, o CardOptions.blank si es nueva):

var name by rememberSaveable { mutableStateOf(card.name) }
var cls by rememberSaveable { mutableStateOf(card.cls) }
var carNumber by rememberSaveable {
mutableStateOf(card.carNumber.ifBlank { (1..99).random().toString() })
}
var carImageIndex by rememberSaveable { mutableIntStateOf(card.carImageIndex) }
var background by rememberSaveable { mutableIntStateOf(card.backgroundIndex) }
var badge by rememberSaveable { mutableIntStateOf(card.badgeIndex) }
var rolling by remember { mutableStateOf(false) }

Notas sobre estos campos:

  • carNumber nunca se edita a mano: una carta nueva recibe un número 1–99 aleatorio al entrar a la pantalla; una carta existente conserva el suyo.
  • name está limitado a MAX_NAME_LENGTH = 15 caracteres, forzado en el propio onValueChange del campo.
  • brand no es un campo del formulario: se deriva del badge elegido vía badgeBrandName(badge) al construir la carta final.
CenterAlignedTopAppBar(title, back, check ✓)
└── (scrollable)
├── CarArtworkPager — preview en vivo, swipe para elegir la foto del auto
├── EditSection "Card"
│ ├── Name (OutlinedTextField, contador de caracteres)
│ └── Class (DropdownField sobre CardOptions.classes)
└── EditSection "Appearance"
├── Card background (SwatchPickerRow sobre CardBackgroundColors)
└── Team badge (BadgePickerRow)

CarArtworkPager es la pieza más interesante de la sección: un HorizontalPager que envuelve un CardArtwork completo por página, así que el preview del formulario es visualmente idéntico a como se verá la carta terminada, incluida la viñeta de color y el badge.

FilledIconButton(onClick = {
if (isNewCard) {
rolling = true
} else {
onSave(buildCard(card.stats, card.tier))
}
})
  • Editar una carta existente guarda de inmediato, reutilizando card.stats y card.tier — las estadísticas nunca se vuelven a rifar en una edición.
  • Crear una carta nueva no llama a onSave todavía: activa rolling = true, lo que reemplaza todo el contenido de la pantalla por PerformanceRollOverlay.
if (rolling) {
val finalStats = remember { PerformanceRoller.rollStats() }
val finalTier = remember(finalStats) { PerformanceRoller.tierFor(finalStats) }
PerformanceRollOverlay(
card = buildCard(finalStats, finalTier),
finalStats = finalStats,
finalTier = finalTier,
onFinished = { onSave(buildCard(finalStats, finalTier)) },
)
return
}

PerformanceRollOverlay: el reveal estilo casino

Sección titulada «PerformanceRollOverlay: el reveal estilo casino»

Ocupa toda la pantalla (fondo NeutralDark al 96% de opacidad) y anima, una estadística a la vez, un ciclo de “spin” antes de asentarse en el valor final:

suspend fun spin(pool: IntRange, finalValue: Int, format: (Int) -> String, ...) {
val steps = 16
for (i in 0 until steps) {
setText(format(pool.random()))
delay(35L + i * 7L) // cada paso es un poco más lento -> efecto de frenado
}
setText(format(finalValue))
setDone(true)
delay(350)
}

El orden es siempre velocidad máxima → 0-100 → potencia → peso, en cascada (cada spin se espera con await antes de arrancar el siguiente). Al terminar las 4:

  1. Se muestra el chip de rareza (showTier = true), coloreado con tierColor(finalTier).
  2. El auto hace un pequeño “pulso” de escala (1 → 1.12 → 1, con FastOutSlowInEasing).
  3. Tras una pausa, se llama onFinished() — que es lo que efectivamente persiste la carta y navega de vuelta a la Gallery.

Timeline del reveal

sequenceDiagram
  participant Ov as PerformanceRollOverlay
  Note over Ov: fondo NeutralDark al 96% + auto sin overlays
  Ov->>Ov: spin(topSpeed) ~16 pasos, delay creciente
  Ov->>Ov: spin(zeroToHundred)
  Ov->>Ov: spin(power)
  Ov->>Ov: spin(weight)
  Ov->>Ov: showTier = true (chip de rareza)
  Ov->>Ov: pulso de escala del auto (1 -> 1.12 -> 1)
  Ov->>Ov: delay(500ms)
  Ov->>Ov: onFinished()

La pantalla se divide en dos secciones ponderadas (weight(0.55f) para el auto, weight(0.45f) para las estadísticas) que suman exactamente el alto disponible, así el reveal completo entra siempre en una sola pantalla sin necesidad de scroll.