Overview
EnvaSistema provides a collection of reusable Compose components designed for warehouse management interfaces. All components follow Material 3 design principles and support customization through parameters.
All components are located in app/src/main/java/com/example/envasistema/ui/components/
Component Library
ScanningLayout
A comprehensive layout for barcode scanning workflows with integrated counter, info card, and action button.
Usage Example
Full Signature
@Composable
fun ProduccionNuevaScreen (onBackClick: () -> Unit) {
ScanningLayout (
title = "INGRESOS" ,
subtitle = "Producción Nueva" ,
infoText = "Escanee los códigos de los productos terminados nuevos" ,
onBackClick = onBackClick,
onSaveClick = { count ->
// Handle save with scan count
},
primaryColor = Color ( 0xFF43A047 )
)
}
Parameters
Header title displayed in the secondary header
Header subtitle displayed below the title
Instructional text shown in the info card
Callback invoked when the back button is clicked
Callback invoked when save button is clicked, receives scan count as parameter
counterLabel
String
default: "Códigos escaneados"
Label text displayed next to the scan counter
saveButtonText
String
default: "Guardar Ingreso"
Text displayed on the save button
saveButtonIcon
ImageVector
default: "Icons.Default.Save"
Icon displayed on the save button
primaryColor
Color
default: "Color(0xFF0061A6)"
Primary theme color used for header, accents, and buttons
infoCardBackground
Color
default: "Color(0xFFE1F5FE)"
Background color of the info alert card
infoIcon
ImageVector
default: "Icons.Default.Radar"
Icon displayed in the info card
Custom color for the info icon (defaults to primaryColor if null)
isSaveButtonEnabled
((Int) -> Boolean)?
default: "null"
Custom validation function to enable/disable save button based on scan count
Additional content to display above the info card
Features
Scan Simulation : Click the scan area to increment counter (for testing)
Automatic Button State : Save button enables/disables based on scan count
Customizable Colors : Supports themed variants for different workflows
Scrollable Content : Handles overflow with vertical scrolling
View Complete Implementation
package com.example.envasistema.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout. *
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.QrCodeScanner
import androidx.compose.material.icons.filled.Radar
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3. *
import androidx.compose.runtime. *
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun ScanningLayout (
title: String ,
subtitle: String ,
infoText: String ,
onBackClick: () -> Unit,
onSaveClick: ( Int ) -> Unit ,
counterLabel: String = "Códigos escaneados" ,
saveButtonText: String = "Guardar Ingreso" ,
saveButtonIcon: ImageVector = Icons.Default.Save,
primaryColor: Color = Color ( 0xFF0061A6 ),
infoCardBackground: Color = Color ( 0xFFE1F5FE ),
infoIcon: ImageVector = Icons.Default.Radar,
infoIconColor: Color ? = null ,
isSaveButtonEnabled: (( Int ) -> Boolean )? = null ,
extraContent: @Composable (ColumnScope.() -> Unit)? = null
) {
var scannCount by remember { mutableIntStateOf ( 0 ) }
val buttonEnabled = isSaveButtonEnabled?. invoke (scannCount) ?: (scannCount > 0 )
Column (
modifier = Modifier
. fillMaxSize ()
. background ( Color ( 0xFFF8F9FA ))
) {
SecondaryHeader (
title = title,
subtitle = subtitle,
backgroundColor = primaryColor,
onBackClick = onBackClick
)
Column (
modifier = Modifier
. fillMaxSize ()
. padding ( 20 .dp)
. verticalScroll ( rememberScrollState ()),
horizontalAlignment = Alignment.CenterHorizontally
) {
extraContent?. invoke ( this )
if (extraContent != null ) {
Spacer (modifier = Modifier. height ( 16 .dp))
}
// Info Alert Card
Surface (
modifier = Modifier. fillMaxWidth (),
color = infoCardBackground,
shape = RoundedCornerShape ( 12 .dp)
) {
Row (
modifier = Modifier. fillMaxWidth (),
verticalAlignment = Alignment.CenterVertically
) {
Box (modifier = Modifier. width ( 6 .dp). height ( 60 .dp). background (primaryColor))
Icon (
imageVector = infoIcon,
contentDescription = null ,
tint = infoIconColor ?: primaryColor,
modifier = Modifier. padding (horizontal = 12 .dp). size ( 24 .dp)
)
Text (
text = infoText,
color = primaryColor,
fontSize = 14 .sp,
fontWeight = FontWeight.Medium,
modifier = Modifier. padding (end = 16 .dp, top = 8 .dp, bottom = 8 .dp)
)
}
}
Spacer (modifier = Modifier. height ( 24 .dp))
// Scan Area
Box (
modifier = Modifier
. fillMaxWidth ()
. height ( 200 .dp)
. border ( 1 .dp, Color ( 0xFFBDBDBD ), RoundedCornerShape ( 16 .dp))
. clip ( RoundedCornerShape ( 16 .dp))
. clickable { scannCount ++ },
contentAlignment = Alignment.Center
) {
Column (horizontalAlignment = Alignment.CenterHorizontally) {
Surface (
color = Color ( 0xFFEEEEEE ),
shape = CircleShape,
modifier = Modifier. size ( 80 .dp)
) {
Icon (
imageVector = Icons.Default.QrCodeScanner,
contentDescription = null ,
tint = Color ( 0xFF9E9E9E ),
modifier = Modifier. padding ( 20 .dp)
)
}
Spacer (modifier = Modifier. height ( 16 .dp))
Text (
text = "Presione el botón lateral del terminal para escanear" ,
color = Color ( 0xFF607D8B ),
fontSize = 14 .sp,
textAlign = TextAlign.Center
)
}
}
Spacer (modifier = Modifier. height ( 24 .dp))
// Scanned Codes Counter
Row (
modifier = Modifier. fillMaxWidth (),
verticalAlignment = Alignment.CenterVertically
) {
Surface (
color = primaryColor,
shape = CircleShape,
modifier = Modifier. size ( 32 .dp)
) {
Box (contentAlignment = Alignment.Center) {
Text (text = scannCount. toString (), color = Color.White, fontWeight = FontWeight.Bold)
}
}
Spacer (modifier = Modifier. width ( 12 .dp))
Text (
text = counterLabel,
color = Color ( 0xFF455A64 ),
fontSize = 16 .sp,
fontWeight = FontWeight.Bold
)
}
Spacer (modifier = Modifier. height ( 32 .dp))
if (scannCount == 0 ) {
Text (
text = "No hay códigos escaneados" ,
color = Color ( 0xFFBDBDBD ),
fontSize = 15 .sp
)
}
Spacer (modifier = Modifier. weight ( 1f ))
Spacer (modifier = Modifier. height ( 24 .dp))
// Footer Button
Button (
onClick = { if (buttonEnabled) onSaveClick (scannCount) },
modifier = Modifier
. fillMaxWidth ()
. height ( 56 .dp),
shape = RoundedCornerShape ( 16 .dp),
colors = ButtonDefaults. buttonColors (
containerColor = if (buttonEnabled) primaryColor else Color ( 0xFFB0BEC5 ),
contentColor = Color.White
)
) {
Icon (imageVector = saveButtonIcon, contentDescription = null )
Spacer (modifier = Modifier. width ( 8 .dp))
Text (text = saveButtonText, fontSize = 18 .sp, fontWeight = FontWeight.Bold)
}
}
}
}
A clickable card component for menu navigation with icon, title, subtitle, and accent styling.
Usage Example
Full Signature
MenuCard (
title = "INGRESOS" ,
subtitle = "Producción, armados y devoluciones" ,
icon = Icons.Default.AddBox,
primaryColor = Color ( 0xFF0061A6 ),
onClick = { navController. navigate ( "ingresos" ) }
)
Parameters
Primary text displayed prominently
Secondary descriptive text
Icon displayed on the left side
primaryColor
Color
default: "Color(0xFF0061A6)"
Color for the accent strip and title text
iconBackgroundColor
Color
default: "Color(0xFFE1F5FE)"
Background color behind the icon
iconTintColor
Color
default: "Color(0xFF01579B)"
Icon tint color
rightIcon
ImageVector?
default: "Icons.AutoMirrored.Filled.KeyboardArrowRight"
Optional icon displayed on the right (null to hide)
Callback invoked when the card is clicked
Layout Structure
┌─────────────────────────────────────┐
│┃ [Icon] │ TITLE → │
│┃ │ Subtitle text │
└─────────────────────────────────────┘
The main application header with status badge, user info, and logout button.
Usage Example
Full Signature
HomeHeader (
statusText = "C66 · ONLINE" ,
userName = "J. PÉREZ" ,
onLogoutClick = { /* Handle logout */ }
)
Parameters
Status text displayed with green indicator (e.g., “C66 · ONLINE”)
User name displayed in the header badge
Callback invoked when logout button is clicked
Features
Online Status Indicator : Green dot with status text
User Badge : Displays current user with person icon
Logout Button : Material icon button with semi-transparent background
App Title : Multi-line title with subtitle styling
Rounded Bottom : Curved bottom corners for modern design
A simplified header for secondary screens with back navigation, title, and subtitle.
Usage Example
Full Signature
SecondaryHeader (
title = "INGRESOS" ,
subtitle = "Producción Nueva" ,
backgroundColor = Color ( 0xFF43A047 ),
onBackClick = { navController. popBackStack () }
)
Parameters
Secondary descriptive text
backgroundColor
Color
default: "Color(0xFF0061A6)"
Background color of the header
Callback invoked when back button is clicked
ShiftInfoRow
A compact row displaying date and shift information with badge styling.
Usage Example
Full Signature
ShiftInfoRow (
dateText = "Lun, 9 de Marzo 2026" ,
shiftText = "Turno Mañana"
)
Parameters
Date text displayed on the left
Shift text displayed in a green badge on the right
Visual Design
Date: Gray text, medium weight
Shift Badge: Green background (#E8F5E9) with green text (#4CAF50)
Color Schemes
EnvaSistema uses contextual colors for different workflows:
Ingresos Green theme (#43A047)
Salidas Red theme (#E53935)
Movimientos Orange theme (#FB8C00)
Transformaciones Brown theme (#6D4C41)
Best Practices
Prefer composing existing components rather than creating new ones. For example, use ScanningLayout with extraContent parameter for custom sections.
Components manage their own internal UI state (like scannCount in ScanningLayout). Parent screens receive state through callbacks when actions occur.
Use the primaryColor parameter to maintain visual consistency across related screens. Each workflow category should use its designated color.
All interactive components include proper content descriptions and maintain minimum touch target sizes of 48dp.
Common Patterns
Screen with Scanning
@Composable
fun MyScreen (onBackClick: () -> Unit) {
ScanningLayout (
title = "CATEGORY" ,
subtitle = "Screen Title" ,
infoText = "Instructions for the user" ,
onBackClick = onBackClick,
onSaveClick = { count ->
// Process scanned items
},
primaryColor = Color ( 0xFF43A047 )
)
}
@Composable
fun CategoryScreen (
onBackClick: () -> Unit,
onOption1Click: () -> Unit,
onOption2Click: () -> Unit
) {
Column (modifier = Modifier. fillMaxSize ()) {
SecondaryHeader (
title = "CATEGORY" ,
subtitle = "Select an option" ,
onBackClick = onBackClick
)
Column (modifier = Modifier. padding ( 20 .dp)) {
MenuCard (
title = "Option 1" ,
subtitle = "Description" ,
icon = Icons.Default.Add,
onClick = onOption1Click
)
Spacer (modifier = Modifier. height ( 16 .dp))
MenuCard (
title = "Option 2" ,
subtitle = "Description" ,
icon = Icons.Default.Remove,
onClick = onOption2Click
)
}
}
}
Component Testing
All components support Compose previews for rapid development:
@Preview (showBackground = true )
@Composable
fun MenuCardPreview () {
EnvaSistemaTheme {
MenuCard (
title = "INGRESOS" ,
subtitle = "Producción, armados y devoluciones" ,
icon = Icons.Default.AddBox,
onClick = {}
)
}
}
Use Android Studio’s Compose Preview feature to view and interact with components without running the full app.
Next Steps
Architecture Understand the app structure
Development Setup Set up your development environment