Ir ao contido principal

Compoñentes e patróns da IU

Esta sección documenta compoñentes reutilizables e patróns de IU que se repiten en toda a interface de usuario.

Encabezado de pantalla​

Fichero: ui/theme/ScreenHeader.kt

Compoñente de cabeceira reutilizable usado en todas as pantallas fillas. Inclúe título, subtítulos, botón de retroceso opcional, contido extra á dereita e alternar de tema.

@Composable
fun ScreenHeader(
title: String,
subtitle: String,
visible: Boolean = true,
onBack: (() -> Unit)? = null,
trailing: @Composable (() -> Unit)? = null,
textColor: Color,
subtitleColor: Color,
onToggleTheme: (() -> Unit)? = null,
darkTheme: Boolean? = null
)

O compoñente aplica statusBarsPadding() e anima a súa entrada alfa con animateFloatAsState cando visible pasa de false a true.

Cargando esqueleto​

Fichero: ui/theme/SkeletonLoading.kt

Cargando compoñentes que se mostran mentres se esperan os datos da API, mellorando a experiencia do usuario evitando pantallas en branco.

SkeletonCard​

Marcador de posición rectangular animado para tarxetas de lista:

@Composable
fun SkeletonCard(
modifier: Modifier = Modifier,
height: Int? = null,
width: Int? = null
)

Usa un degradado lineal animado nun bucle con rememberInfiniteTransition para simular o efecto de brillo.

SkeletonCircle​

Marcador de posición circular para avatares e iconas:

@Composable
fun SkeletonCircle(
modifier: Modifier = Modifier,
size: Int = 100
)

SkeletonText​

Marcador de posición rectangular redondeado para textos:

@Composable
fun SkeletonText(
modifier: Modifier = Modifier,
width: Int = 200,
height: Int = 20
)

Lista de esqueletos​

Composición de múltiples SkeletonCard apilados verticalmente:

@Composable
fun SkeletonList(
itemCount: Int = 5,
modifier: Modifier = Modifier,
itemHeight: Int = 80
)

Uso típico:

if (isLoading) {
SkeletonList(itemCount = 5, modifier = Modifier.fillMaxWidth())
} else {
LazyColumn { /* items reales */ }
}

AppTimePickerDialog​

Fichero: ui/theme/AppTimePickerDialog.kt

Diálogo de selección de tempo baseado no TimePicker do Material 3. Substitúe o TimePickerDialog de Android View usado en versións anteriores.

@Composable
fun AppTimePickerDialog(
onDismiss: () -> Unit,
onConfirm: (hour: Int, minute: Int) -> Unit,
initialHour: Int = LocalTime.now().hour,
initialMinute: Int = LocalTime.now().minute,
title: String = "Seleccionar hora",
is24Hour: Boolean = true,
onValidate: ((hour: Int, minute: Int) -> String?)? = null
)

O parámetro onValidate permite aplicar validacións adicionais antes de comprometerse. Se a función devolve un String (mensaxe de erro), móstraa como Toast e non pecha o diálogo. Se devolve null, a selección considérase válida e chámase onConfirm.

Lista de patróns de tarxeta de elementos​

A máquina, a reserva e as listas de pratos seguen o mesmo patrón de tarxeta:

Card (RoundedCornerShape 16 dp, elevation, cardBg)
└── Row (padding 16 dp, verticalAlignment Center)
├── Image/Icon (60-80 dp)
├── Column (weight 1f)
│ ├── Text nombre (titleMedium, fontWeight Bold)
│ └── Text subtítulo (bodyMedium, alpha 0.5)
└── Botones de acción (IconButton)

A cor de fondo cardBg adáptase ao tema activo: Color(0xFF162347).copy(0.9f) en escuridade e Color.White en claro.

Patrón de campo de texto (OutlinedTextField)​

Todos os formularios usan OutlinedTextField co esquema de cores personalizado:

val inputColors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = BlueAccent,
unfocusedBorderColor = if (isDarkTheme) Color.White.copy(0.15f) else BlueDark.copy(0.15f),
focusedLabelColor = BlueAccent,
unfocusedLabelColor = if (isDarkTheme) Color.White.copy(0.4f) else BlueDark.copy(0.4f),
cursorColor = BlueAccent,
focusedTextColor = textColor,
unfocusedTextColor = textColor.copy(0.9f)
)

O bordo activo é sempre BlueAccent (#2196F3) independentemente do tema.

Botón de acción principal (CTA)​

Os botóns de acción principais usan un degradado horizontal en lugar da cor plana do Material 3:

Button(
onClick = accion,
modifier = Modifier.fillMaxWidth().height(56.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
contentPadding = PaddingValues()
) {
Box(
Modifier
.fillMaxSize()
.background(
Brush.horizontalGradient(listOf(BlueAccent, BlueElectric)),
RoundedCornerShape(16.dp)
),
contentAlignment = Alignment.Center
) {
Text(
"CONFIRMAR",
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
letterSpacing = 2.sp,
color = Color.White
)
}
}

Botón Atrás​

A función de retroceso implícase con TextButton en lugar dunha frecha IconButton, para obter máis área táctil. ScreenHeader estandariza este comportamento usando Icons.AutoMirrored.Filled.ArrowBack.

Selector de temas (alternar)​

A alternancia do tema aparece en MainScreen e ProfileScreen como un IconButton aliñado na esquina superior dereita, con statusBarsPadding() para respectar a barra de estado do sistema:

IconButton(
onClick = onToggleTheme,
modifier = Modifier.align(Alignment.TopEnd).padding(16.dp).statusBarsPadding()
) {
Icon(
if (isDarkTheme) Icons.Default.LightMode else Icons.Default.DarkMode,
contentDescription = "Cambiar tema",
tint = if (isDarkTheme) BlueSoft else BlueAccent
)
}

No resto das pantallas, o alternador está integrado en ScreenHeader a través dos parámetros darkTheme e onToggleTheme.

Avatar de usuario​

O avatar do perfil móstrase en ProfileScreen e EditProfileScreen como unha circular Box con clip(CircleShape). Se o usuario ten unha foto en Base64, esta decodificarase con ImageUtils.decodeBase64ToBitmap. Se non tes unha foto, móstrase unha icona xenérica de persoa.

Indicador de estado da máquina​

As tarxetas de máquina en MaquinasScreen mostran unha insignia de estado de cores:

EstadoCorTexto
OperativoVerde (Color(0xFF4CAF50))Dispoñible
En mantementoLaranxa (Color(0xFFFF9800))Mantemento

Chip de filtro (pantalla de reservas)​

Os filtros da pantalla de reserva implícanse como FilterChip do Material 3 nun LazyRow que se pode desprazar horizontalmente.