• Código e Implementación » History » Version 4
Nelson Ramirez, 12/23/2025 03:27 PM
| 1 | 1 | Nelson Ramirez | h1. • Código e Implementación |
|---|---|---|---|
| 2 | 2 | Nelson Ramirez | |
| 3 | 3 | Nelson Ramirez | Componentes bases. |
| 4 | 3 | Nelson Ramirez | <pre> |
| 5 | 3 | Nelson Ramirez | @OptIn(ExperimentalMaterial3Api::class) |
| 6 | 3 | Nelson Ramirez | @Composable |
| 7 | 3 | Nelson Ramirez | fun SimpleTopBar(title: String, onBack: () -> Unit) { |
| 8 | 3 | Nelson Ramirez | CenterAlignedTopAppBar( |
| 9 | 3 | Nelson Ramirez | title = { Text(text = title, fontWeight = FontWeight.Bold) }, |
| 10 | 3 | Nelson Ramirez | navigationIcon = { |
| 11 | 3 | Nelson Ramirez | IconButton(onClick = onBack) { |
| 12 | 3 | Nelson Ramirez | Text(text = "←", fontSize = 20.sp) |
| 13 | 3 | Nelson Ramirez | } |
| 14 | 3 | Nelson Ramirez | } |
| 15 | 3 | Nelson Ramirez | ) |
| 16 | 3 | Nelson Ramirez | } |
| 17 | 3 | Nelson Ramirez | |
| 18 | 3 | Nelson Ramirez | @Composable |
| 19 | 3 | Nelson Ramirez | fun GradientBackground(content: @Composable ColumnScope.() -> Unit) { |
| 20 | 3 | Nelson Ramirez | val bg = Brush.verticalGradient( |
| 21 | 3 | Nelson Ramirez | colors = listOf( |
| 22 | 3 | Nelson Ramirez | Color(0xFFEFF6FF), |
| 23 | 3 | Nelson Ramirez | Color(0xFFFFFFFF) |
| 24 | 3 | Nelson Ramirez | ) |
| 25 | 3 | Nelson Ramirez | ) |
| 26 | 3 | Nelson Ramirez | Column( |
| 27 | 3 | Nelson Ramirez | modifier = Modifier |
| 28 | 3 | Nelson Ramirez | .fillMaxSize() |
| 29 | 3 | Nelson Ramirez | .background(bg) |
| 30 | 3 | Nelson Ramirez | .padding(24.dp), |
| 31 | 3 | Nelson Ramirez | horizontalAlignment = Alignment.CenterHorizontally, |
| 32 | 3 | Nelson Ramirez | verticalArrangement = Arrangement.Center, |
| 33 | 3 | Nelson Ramirez | content = content |
| 34 | 3 | Nelson Ramirez | ) |
| 35 | 3 | Nelson Ramirez | } |
| 36 | 3 | Nelson Ramirez | |
| 37 | 3 | Nelson Ramirez | @Composable |
| 38 | 3 | Nelson Ramirez | fun PrimaryPillButton(text: String, onClick: () -> Unit) { |
| 39 | 3 | Nelson Ramirez | Button( |
| 40 | 3 | Nelson Ramirez | onClick = onClick, |
| 41 | 3 | Nelson Ramirez | modifier = Modifier |
| 42 | 3 | Nelson Ramirez | .fillMaxWidth() |
| 43 | 3 | Nelson Ramirez | .height(46.dp), |
| 44 | 3 | Nelson Ramirez | shape = RoundedCornerShape(999.dp) |
| 45 | 3 | Nelson Ramirez | ) { |
| 46 | 3 | Nelson Ramirez | Text(text = text, fontWeight = FontWeight.SemiBold) |
| 47 | 3 | Nelson Ramirez | } |
| 48 | 3 | Nelson Ramirez | } |
| 49 | 3 | Nelson Ramirez | |
| 50 | 3 | Nelson Ramirez | @Composable |
| 51 | 3 | Nelson Ramirez | fun OutlinePillButton(text: String, onClick: () -> Unit) { |
| 52 | 3 | Nelson Ramirez | OutlinedButton( |
| 53 | 3 | Nelson Ramirez | onClick = onClick, |
| 54 | 3 | Nelson Ramirez | modifier = Modifier |
| 55 | 3 | Nelson Ramirez | .fillMaxWidth() |
| 56 | 3 | Nelson Ramirez | .height(46.dp), |
| 57 | 3 | Nelson Ramirez | shape = RoundedCornerShape(999.dp) |
| 58 | 3 | Nelson Ramirez | ) { |
| 59 | 3 | Nelson Ramirez | Text(text = text, fontWeight = FontWeight.SemiBold) |
| 60 | 3 | Nelson Ramirez | } |
| 61 | 3 | Nelson Ramirez | } |
| 62 | 3 | Nelson Ramirez | </pre> |
| 63 | 3 | Nelson Ramirez | Ventana principal |
| 64 | 3 | Nelson Ramirez | <pre> |
| 65 | 3 | Nelson Ramirez | @Composable |
| 66 | 3 | Nelson Ramirez | fun HomeScreen( |
| 67 | 3 | Nelson Ramirez | onGoContainers: () -> Unit, |
| 68 | 3 | Nelson Ramirez | onGoNotifications: () -> Unit, |
| 69 | 3 | Nelson Ramirez | onGoLogin: () -> Unit |
| 70 | 3 | Nelson Ramirez | ) { |
| 71 | 3 | Nelson Ramirez | GradientBackground { |
| 72 | 3 | Nelson Ramirez | // “Logo” simple estilo BR (placeholder) |
| 73 | 3 | Nelson Ramirez | Image( |
| 74 | 3 | Nelson Ramirez | painter = painterResource(id = R.drawable.logo_binraiders), |
| 75 | 3 | Nelson Ramirez | contentDescription = "Logo Bin Raiders", |
| 76 | 3 | Nelson Ramirez | modifier = Modifier |
| 77 | 3 | Nelson Ramirez | .size(300.dp) |
| 78 | 3 | Nelson Ramirez | .clip(CircleShape), |
| 79 | 3 | Nelson Ramirez | contentScale = ContentScale.Crop |
| 80 | 3 | Nelson Ramirez | ) |
| 81 | 3 | Nelson Ramirez | |
| 82 | 3 | Nelson Ramirez | |
| 83 | 3 | Nelson Ramirez | Spacer(Modifier.height(18.dp)) |
| 84 | 3 | Nelson Ramirez | |
| 85 | 3 | Nelson Ramirez | Text("¡Bienvenidos a\nBin Raiders!", fontSize = 28.sp, fontWeight = FontWeight.Bold) |
| 86 | 3 | Nelson Ramirez | Spacer(Modifier.height(28.dp)) |
| 87 | 3 | Nelson Ramirez | |
| 88 | 3 | Nelson Ramirez | PrimaryPillButton("Ver contenedores", onGoContainers) |
| 89 | 3 | Nelson Ramirez | Spacer(Modifier.height(12.dp)) |
| 90 | 3 | Nelson Ramirez | OutlinePillButton("Notificaciones", onGoNotifications) |
| 91 | 3 | Nelson Ramirez | Spacer(Modifier.height(12.dp)) |
| 92 | 3 | Nelson Ramirez | PrimaryPillButton("Acceder", onGoLogin) |
| 93 | 3 | Nelson Ramirez | |
| 94 | 3 | Nelson Ramirez | Spacer(Modifier.height(18.dp)) |
| 95 | 3 | Nelson Ramirez | Text("Powered by Raspberry Pi 4", color = Color(0xFF6B7280), fontSize = 13.sp) |
| 96 | 3 | Nelson Ramirez | } |
| 97 | 3 | Nelson Ramirez | } |
| 98 | 3 | Nelson Ramirez | </pre> |
| 99 | 3 | Nelson Ramirez | |
| 100 | 3 | Nelson Ramirez | Contenedores |
| 101 | 3 | Nelson Ramirez | <pre> |
| 102 | 3 | Nelson Ramirez | data class ContainerItem( |
| 103 | 3 | Nelson Ramirez | val id: Int, |
| 104 | 3 | Nelson Ramirez | val name: String, |
| 105 | 3 | Nelson Ramirez | val address: String, |
| 106 | 3 | Nelson Ramirez | val imageHint: String = "" |
| 107 | 3 | Nelson Ramirez | ) |
| 108 | 3 | Nelson Ramirez | |
| 109 | 3 | Nelson Ramirez | </pre> |
| 110 | 3 | Nelson Ramirez | |
| 111 | 3 | Nelson Ramirez | |
| 112 | 3 | Nelson Ramirez | Ventana Contenedores |
| 113 | 3 | Nelson Ramirez | <pre> |
| 114 | 3 | Nelson Ramirez | @Composable |
| 115 | 3 | Nelson Ramirez | fun ContainersScreen( |
| 116 | 3 | Nelson Ramirez | onBack: () -> Unit, |
| 117 | 3 | Nelson Ramirez | onOpenCamera: (ContainerItem) -> Unit |
| 118 | 3 | Nelson Ramirez | ) { |
| 119 | 3 | Nelson Ramirez | val containers = remember { |
| 120 | 3 | Nelson Ramirez | listOf( |
| 121 | 3 | Nelson Ramirez | ContainerItem(1, "Contenedor 1", "Dirección: Av. Diego Portales & Las Torres"), |
| 122 | 3 | Nelson Ramirez | ContainerItem(2, "Contenedor 2", "Dirección: Av. Edmundo Flores 112"), |
| 123 | 3 | Nelson Ramirez | ContainerItem(3, "Contenedor 3", "Dirección: Av. Santa María 421") |
| 124 | 3 | Nelson Ramirez | ) |
| 125 | 3 | Nelson Ramirez | } |
| 126 | 3 | Nelson Ramirez | |
| 127 | 3 | Nelson Ramirez | var estado by remember { mutableStateOf<EstadoResponse?>(null) } |
| 128 | 3 | Nelson Ramirez | var loading by remember { mutableStateOf(false) } |
| 129 | 3 | Nelson Ramirez | val scope = rememberCoroutineScope() |
| 130 | 3 | Nelson Ramirez | |
| 131 | 3 | Nelson Ramirez | suspend fun refreshEstado() { |
| 132 | 3 | Nelson Ramirez | loading = true |
| 133 | 3 | Nelson Ramirez | val res = withContext(Dispatchers.IO) { ApiClient.getEstado() } |
| 134 | 3 | Nelson Ramirez | estado = res |
| 135 | 3 | Nelson Ramirez | loading = false |
| 136 | 3 | Nelson Ramirez | } |
| 137 | 3 | Nelson Ramirez | |
| 138 | 3 | Nelson Ramirez | // 1) Primera carga |
| 139 | 3 | Nelson Ramirez | LaunchedEffect(Unit) { |
| 140 | 3 | Nelson Ramirez | refreshEstado() |
| 141 | 3 | Nelson Ramirez | } |
| 142 | 3 | Nelson Ramirez | |
| 143 | 3 | Nelson Ramirez | // 2) Auto-refresh para demo (cada 2.5s) |
| 144 | 3 | Nelson Ramirez | LaunchedEffect(Unit) { |
| 145 | 3 | Nelson Ramirez | while (true) { |
| 146 | 3 | Nelson Ramirez | delay(2500) |
| 147 | 3 | Nelson Ramirez | refreshEstado() |
| 148 | 3 | Nelson Ramirez | } |
| 149 | 3 | Nelson Ramirez | } |
| 150 | 3 | Nelson Ramirez | |
| 151 | 3 | Nelson Ramirez | Scaffold( |
| 152 | 3 | Nelson Ramirez | topBar = { SimpleTopBar(title = "Contenedores", onBack = onBack) } |
| 153 | 3 | Nelson Ramirez | ) { pad -> |
| 154 | 3 | Nelson Ramirez | Column( |
| 155 | 3 | Nelson Ramirez | modifier = Modifier |
| 156 | 3 | Nelson Ramirez | .padding(pad) |
| 157 | 3 | Nelson Ramirez | .fillMaxSize() |
| 158 | 3 | Nelson Ramirez | .padding(16.dp), |
| 159 | 3 | Nelson Ramirez | verticalArrangement = Arrangement.spacedBy(14.dp) |
| 160 | 3 | Nelson Ramirez | ) { |
| 161 | 3 | Nelson Ramirez | EstadoSensorCard( |
| 162 | 3 | Nelson Ramirez | estado = estado, |
| 163 | 3 | Nelson Ramirez | loading = loading, |
| 164 | 3 | Nelson Ramirez | onRefresh = { scope.launch { refreshEstado() } } |
| 165 | 3 | Nelson Ramirez | ) |
| 166 | 3 | Nelson Ramirez | |
| 167 | 3 | Nelson Ramirez | LazyColumn( |
| 168 | 3 | Nelson Ramirez | modifier = Modifier.fillMaxSize(), |
| 169 | 3 | Nelson Ramirez | verticalArrangement = Arrangement.spacedBy(12.dp) |
| 170 | 3 | Nelson Ramirez | ) { |
| 171 | 3 | Nelson Ramirez | items(containers) { c -> |
| 172 | 3 | Nelson Ramirez | ContainerCard(item = c, onOpenCamera = { onOpenCamera(c) }) |
| 173 | 3 | Nelson Ramirez | } |
| 174 | 3 | Nelson Ramirez | } |
| 175 | 3 | Nelson Ramirez | } |
| 176 | 3 | Nelson Ramirez | } |
| 177 | 3 | Nelson Ramirez | } |
| 178 | 3 | Nelson Ramirez | |
| 179 | 3 | Nelson Ramirez | |
| 180 | 3 | Nelson Ramirez | @Composable |
| 181 | 3 | Nelson Ramirez | fun ContainerCard( |
| 182 | 3 | Nelson Ramirez | item: ContainerItem, |
| 183 | 3 | Nelson Ramirez | onOpenCamera: () -> Unit |
| 184 | 3 | Nelson Ramirez | ) { |
| 185 | 3 | Nelson Ramirez | var enabled by remember { mutableStateOf(true) } |
| 186 | 3 | Nelson Ramirez | |
| 187 | 3 | Nelson Ramirez | Card( |
| 188 | 3 | Nelson Ramirez | modifier = Modifier.fillMaxWidth(), |
| 189 | 3 | Nelson Ramirez | shape = RoundedCornerShape(16.dp) |
| 190 | 3 | Nelson Ramirez | ) { |
| 191 | 3 | Nelson Ramirez | Row( |
| 192 | 3 | Nelson Ramirez | modifier = Modifier |
| 193 | 3 | Nelson Ramirez | .fillMaxWidth() |
| 194 | 3 | Nelson Ramirez | .padding(14.dp), |
| 195 | 3 | Nelson Ramirez | verticalAlignment = Alignment.CenterVertically |
| 196 | 3 | Nelson Ramirez | ) { |
| 197 | 3 | Nelson Ramirez | // Mini imagen placeholder |
| 198 | 3 | Nelson Ramirez | Box( |
| 199 | 3 | Nelson Ramirez | modifier = Modifier |
| 200 | 3 | Nelson Ramirez | .size(68.dp) |
| 201 | 3 | Nelson Ramirez | .clip(RoundedCornerShape(12.dp)) |
| 202 | 3 | Nelson Ramirez | .background(Color(0xFFE5E7EB)) |
| 203 | 3 | Nelson Ramirez | .border(1.dp, Color(0xFFD1D5DB), RoundedCornerShape(12.dp)), |
| 204 | 3 | Nelson Ramirez | contentAlignment = Alignment.Center |
| 205 | 3 | Nelson Ramirez | ) { |
| 206 | 3 | Nelson Ramirez | Text("IMG", color = Color(0xFF6B7280), fontSize = 12.sp) |
| 207 | 3 | Nelson Ramirez | } |
| 208 | 3 | Nelson Ramirez | |
| 209 | 3 | Nelson Ramirez | Spacer(Modifier.width(12.dp)) |
| 210 | 3 | Nelson Ramirez | |
| 211 | 3 | Nelson Ramirez | Column(modifier = Modifier.weight(1f)) { |
| 212 | 3 | Nelson Ramirez | Text(item.name, fontWeight = FontWeight.Bold) |
| 213 | 3 | Nelson Ramirez | Spacer(Modifier.height(2.dp)) |
| 214 | 3 | Nelson Ramirez | Text(item.address, color = Color(0xFF6B7280), fontSize = 13.sp) |
| 215 | 3 | Nelson Ramirez | |
| 216 | 3 | Nelson Ramirez | Spacer(Modifier.height(10.dp)) |
| 217 | 3 | Nelson Ramirez | |
| 218 | 3 | Nelson Ramirez | Row(verticalAlignment = Alignment.CenterVertically) { |
| 219 | 3 | Nelson Ramirez | Button( |
| 220 | 3 | Nelson Ramirez | onClick = onOpenCamera, |
| 221 | 3 | Nelson Ramirez | shape = RoundedCornerShape(10.dp), |
| 222 | 3 | Nelson Ramirez | contentPadding = PaddingValues(horizontal = 14.dp, vertical = 10.dp) |
| 223 | 3 | Nelson Ramirez | ) { |
| 224 | 3 | Nelson Ramirez | Text("Ver cámara") |
| 225 | 3 | Nelson Ramirez | } |
| 226 | 3 | Nelson Ramirez | Spacer(Modifier.width(10.dp)) |
| 227 | 3 | Nelson Ramirez | Switch(checked = enabled, onCheckedChange = { enabled = it }) |
| 228 | 3 | Nelson Ramirez | } |
| 229 | 3 | Nelson Ramirez | } |
| 230 | 3 | Nelson Ramirez | } |
| 231 | 3 | Nelson Ramirez | } |
| 232 | 3 | Nelson Ramirez | } |
| 233 | 3 | Nelson Ramirez | </pre> |
| 234 | 3 | Nelson Ramirez | |
| 235 | 3 | Nelson Ramirez | Ventana camara |
| 236 | 3 | Nelson Ramirez | <pre> |
| 237 | 3 | Nelson Ramirez | @Composable |
| 238 | 3 | Nelson Ramirez | fun CameraScreen( |
| 239 | 3 | Nelson Ramirez | container: ContainerItem?, |
| 240 | 3 | Nelson Ramirez | onBack: () -> Unit |
| 241 | 3 | Nelson Ramirez | ) { |
| 242 | 3 | Nelson Ramirez | val streamUrl = ApiConfig.STREAM_URL |
| 243 | 3 | Nelson Ramirez | |
| 244 | 3 | Nelson Ramirez | Scaffold( |
| 245 | 3 | Nelson Ramirez | topBar = { SimpleTopBar(title = "Cámara", onBack = onBack) } |
| 246 | 3 | Nelson Ramirez | ) { pad -> |
| 247 | 3 | Nelson Ramirez | Column( |
| 248 | 3 | Nelson Ramirez | modifier = Modifier |
| 249 | 3 | Nelson Ramirez | .padding(pad) |
| 250 | 3 | Nelson Ramirez | .fillMaxSize() |
| 251 | 3 | Nelson Ramirez | .padding(16.dp), |
| 252 | 3 | Nelson Ramirez | verticalArrangement = Arrangement.spacedBy(12.dp) |
| 253 | 3 | Nelson Ramirez | ) { |
| 254 | 3 | Nelson Ramirez | Text( |
| 255 | 3 | Nelson Ramirez | text = container?.name ?: "Contenedor", |
| 256 | 3 | Nelson Ramirez | fontWeight = FontWeight.Bold, |
| 257 | 3 | Nelson Ramirez | fontSize = 18.sp |
| 258 | 3 | Nelson Ramirez | ) |
| 259 | 3 | Nelson Ramirez | Text( |
| 260 | 3 | Nelson Ramirez | text = container?.address ?: "", |
| 261 | 3 | Nelson Ramirez | color = Color(0xFF6B7280) |
| 262 | 3 | Nelson Ramirez | ) |
| 263 | 3 | Nelson Ramirez | |
| 264 | 3 | Nelson Ramirez | MjpegWebView(url = streamUrl) |
| 265 | 3 | Nelson Ramirez | |
| 266 | 3 | Nelson Ramirez | Text( |
| 267 | 3 | Nelson Ramirez | text = "Fuente: $streamUrl", |
| 268 | 3 | Nelson Ramirez | fontSize = 12.sp, |
| 269 | 3 | Nelson Ramirez | color = Color(0xFF6B7280) |
| 270 | 3 | Nelson Ramirez | ) |
| 271 | 3 | Nelson Ramirez | } |
| 272 | 3 | Nelson Ramirez | } |
| 273 | 3 | Nelson Ramirez | } |
| 274 | 3 | Nelson Ramirez | |
| 275 | 3 | Nelson Ramirez | @Composable |
| 276 | 3 | Nelson Ramirez | fun MjpegWebView(url: String) { |
| 277 | 3 | Nelson Ramirez | AndroidView( |
| 278 | 3 | Nelson Ramirez | modifier = Modifier |
| 279 | 3 | Nelson Ramirez | .fillMaxWidth() |
| 280 | 3 | Nelson Ramirez | .height(420.dp) |
| 281 | 3 | Nelson Ramirez | .clip(RoundedCornerShape(16.dp)) |
| 282 | 3 | Nelson Ramirez | .border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(16.dp)), |
| 283 | 3 | Nelson Ramirez | factory = { ctx -> |
| 284 | 3 | Nelson Ramirez | WebView(ctx).apply { |
| 285 | 3 | Nelson Ramirez | webViewClient = WebViewClient() |
| 286 | 3 | Nelson Ramirez | settings.javaScriptEnabled = false |
| 287 | 3 | Nelson Ramirez | settings.loadWithOverviewMode = true |
| 288 | 3 | Nelson Ramirez | settings.useWideViewPort = true |
| 289 | 3 | Nelson Ramirez | loadUrl(url) |
| 290 | 3 | Nelson Ramirez | } |
| 291 | 3 | Nelson Ramirez | }, |
| 292 | 3 | Nelson Ramirez | update = { webView -> |
| 293 | 3 | Nelson Ramirez | if (webView.url != url) webView.loadUrl(url) |
| 294 | 3 | Nelson Ramirez | } |
| 295 | 3 | Nelson Ramirez | ) |
| 296 | 3 | Nelson Ramirez | } |
| 297 | 3 | Nelson Ramirez | |
| 298 | 3 | Nelson Ramirez | |
| 299 | 3 | Nelson Ramirez | |
| 300 | 3 | Nelson Ramirez | |
| 301 | 3 | Nelson Ramirez | @Composable |
| 302 | 3 | Nelson Ramirez | fun EstadoSensorCard( |
| 303 | 3 | Nelson Ramirez | estado: EstadoResponse?, |
| 304 | 3 | Nelson Ramirez | loading: Boolean, |
| 305 | 3 | Nelson Ramirez | onRefresh: () -> Unit |
| 306 | 3 | Nelson Ramirez | ) { |
| 307 | 3 | Nelson Ramirez | val pct = estado?.fill_percent ?: 0 |
| 308 | 3 | Nelson Ramirez | val label = when { |
| 309 | 3 | Nelson Ramirez | loading -> "Cargando..." |
| 310 | 3 | Nelson Ramirez | estado == null -> "Sin datos" |
| 311 | 3 | Nelson Ramirez | estado.ok -> "Estado: ${estado.estado}" |
| 312 | 3 | Nelson Ramirez | else -> "Error: ${estado.error ?: "sensor_no_data"}" |
| 313 | 3 | Nelson Ramirez | } |
| 314 | 3 | Nelson Ramirez | |
| 315 | 3 | Nelson Ramirez | val color = when (estado?.estado) { |
| 316 | 3 | Nelson Ramirez | "LLENO" -> Color(0xFFEF4444) // rojo |
| 317 | 3 | Nelson Ramirez | "MEDIO" -> Color(0xFFF59E0B) // amarillo |
| 318 | 3 | Nelson Ramirez | "VACIO" -> Color(0xFF22C55E) // verde |
| 319 | 3 | Nelson Ramirez | else -> Color(0xFF6B7280) // gris |
| 320 | 3 | Nelson Ramirez | } |
| 321 | 3 | Nelson Ramirez | |
| 322 | 3 | Nelson Ramirez | Card(shape = RoundedCornerShape(16.dp), modifier = Modifier.fillMaxWidth()) { |
| 323 | 3 | Nelson Ramirez | Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { |
| 324 | 3 | Nelson Ramirez | Row(verticalAlignment = Alignment.CenterVertically) { |
| 325 | 3 | Nelson Ramirez | Text("Estado del contenedor", fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f)) |
| 326 | 3 | Nelson Ramirez | AssistChip( |
| 327 | 3 | Nelson Ramirez | onClick = {}, |
| 328 | 3 | Nelson Ramirez | label = { Text(estado?.estado ?: "—") }, |
| 329 | 3 | Nelson Ramirez | colors = AssistChipDefaults.assistChipColors( |
| 330 | 3 | Nelson Ramirez | labelColor = Color.White, |
| 331 | 3 | Nelson Ramirez | containerColor = color |
| 332 | 3 | Nelson Ramirez | ) |
| 333 | 3 | Nelson Ramirez | ) |
| 334 | 3 | Nelson Ramirez | } |
| 335 | 3 | Nelson Ramirez | |
| 336 | 3 | Nelson Ramirez | Text(label, color = Color(0xFF374151)) |
| 337 | 3 | Nelson Ramirez | |
| 338 | 3 | Nelson Ramirez | if (estado?.ok == true) { |
| 339 | 3 | Nelson Ramirez | Text("Distancia: ${estado.distance_cm} cm") |
| 340 | 3 | Nelson Ramirez | Text("Relleno: ${estado.fill_percent}%") |
| 341 | 3 | Nelson Ramirez | LinearProgressIndicator( |
| 342 | 3 | Nelson Ramirez | progress = (pct.coerceIn(0, 100) / 100f), |
| 343 | 3 | Nelson Ramirez | modifier = Modifier.fillMaxWidth() |
| 344 | 3 | Nelson Ramirez | ) |
| 345 | 3 | Nelson Ramirez | Text( |
| 346 | 3 | Nelson Ramirez | "Edad dato: ${estado.sensor_age_ms ?: 0} ms", |
| 347 | 3 | Nelson Ramirez | fontSize = 12.sp, |
| 348 | 3 | Nelson Ramirez | color = Color(0xFF6B7280) |
| 349 | 3 | Nelson Ramirez | ) |
| 350 | 3 | Nelson Ramirez | } |
| 351 | 3 | Nelson Ramirez | |
| 352 | 3 | Nelson Ramirez | Button( |
| 353 | 3 | Nelson Ramirez | onClick = onRefresh, |
| 354 | 3 | Nelson Ramirez | enabled = !loading, |
| 355 | 3 | Nelson Ramirez | shape = RoundedCornerShape(12.dp), |
| 356 | 3 | Nelson Ramirez | modifier = Modifier.fillMaxWidth() |
| 357 | 3 | Nelson Ramirez | ) { |
| 358 | 3 | Nelson Ramirez | Text(if (loading) "Actualizando..." else "Actualizar") |
| 359 | 3 | Nelson Ramirez | } |
| 360 | 3 | Nelson Ramirez | } |
| 361 | 3 | Nelson Ramirez | } |
| 362 | 3 | Nelson Ramirez | } |
| 363 | 3 | Nelson Ramirez | </pre> |