From 0c6ce7b9a30be8fe7b1e19423cc8885e216eb09b Mon Sep 17 00:00:00 2001 From: Han Yin Date: Wed, 3 Sep 2025 10:14:50 -0700 Subject: [PATCH] UI: extract a reusable InfoAlertDialog --- .../example/llama/ui/components/InfoView.kt | 104 +++++++++++++++--- .../ModelsManagementAndDeletingScreen.kt | 86 ++++++--------- .../example/llama/ui/screens/ModelsScreen.kt | 47 +++++--- 3 files changed, 156 insertions(+), 81 deletions(-) diff --git a/examples/llama.android/app/src/main/java/com/example/llama/ui/components/InfoView.kt b/examples/llama.android/app/src/main/java/com/example/llama/ui/components/InfoView.kt index 5d72a877dc..1558bfb9b9 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/ui/components/InfoView.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/ui/components/InfoView.kt @@ -3,10 +3,12 @@ package com.example.llama.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -18,18 +20,95 @@ 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.window.DialogProperties data class InfoAction( - val label: String, val icon: ImageVector, + val label: String, val onAction: () -> Unit ) +@Composable +fun InfoAlertDialog( + modifier: Modifier = Modifier, + isCritical: Boolean = false, + allowDismiss: Boolean = false, + onDismiss: () -> Unit = {}, + icon: ImageVector, + title: String, + message: String? = null, + action: InfoAction? = null, + confirmButton: @Composable () -> Unit = {}, +) { + AlertDialog( + modifier = modifier, + onDismissRequest = onDismiss, + properties = DialogProperties( + dismissOnBackPress = allowDismiss, + dismissOnClickOutside = allowDismiss, + ), + icon = { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(64.dp), + ) + }, + title = { + Text( + modifier = Modifier.fillMaxWidth(), + text = title, + style = MaterialTheme.typography.headlineSmall, + textAlign = TextAlign.Center, + fontWeight = FontWeight.SemiBold + ) + }, + text = { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + message?.let { + Text( + modifier = Modifier.padding(top = 8.dp), + text = it, + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + action?.let { + Button( + modifier = Modifier.padding(top = 24.dp), + onClick = it.onAction, + ) { + Icon( + imageVector = it.icon, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(it.label) + } + } + } + + }, + containerColor = if (isCritical) MaterialTheme.colorScheme.errorContainer else AlertDialogDefaults.containerColor, + iconContentColor = if (isCritical) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, + titleContentColor = if (isCritical) MaterialTheme.colorScheme.onErrorContainer else AlertDialogDefaults.titleContentColor, + textContentColor = if (isCritical) MaterialTheme.colorScheme.onErrorContainer else AlertDialogDefaults.textContentColor, + confirmButton = confirmButton, + ) +} + @Composable fun InfoView( modifier: Modifier = Modifier, - title: String, icon: ImageVector, + title: String, message: String? = null, action: InfoAction? = null ) { @@ -52,8 +131,8 @@ fun InfoView( @Composable fun InfoView( modifier: Modifier = Modifier, - title: String, icon: @Composable () -> Unit, + title: String, message: String? = null, action: InfoAction? = null ) { @@ -64,9 +143,8 @@ fun InfoView( ) { icon() - Spacer(modifier = Modifier.height(16.dp)) - Text( + modifier = Modifier.padding(top = 16.dp), text = title, style = MaterialTheme.typography.headlineSmall, textAlign = TextAlign.Center, @@ -74,9 +152,8 @@ fun InfoView( ) message?.let { - Spacer(modifier = Modifier.height(8.dp)) - Text( + modifier = Modifier.padding(top = 8.dp), text = it, style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center, @@ -85,16 +162,17 @@ fun InfoView( } action?.let { - Spacer(modifier = Modifier.height(24.dp)) - - Button(onClick = action.onAction) { + Button( + modifier = Modifier.padding(top = 24.dp), + onClick = it.onAction, + ) { Icon( - imageVector = action.icon, + imageVector = it.icon, contentDescription = null, modifier = Modifier.size(18.dp) ) Spacer(modifier = Modifier.width(8.dp)) - Text(action.label) + Text(it.label) } } } diff --git a/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelsManagementAndDeletingScreen.kt b/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelsManagementAndDeletingScreen.kt index ec1f3e0241..9904409455 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelsManagementAndDeletingScreen.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelsManagementAndDeletingScreen.kt @@ -22,7 +22,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.automirrored.filled.Help import androidx.compose.material.icons.automirrored.outlined.ContactSupport -import androidx.compose.material.icons.filled.ArrowForward import androidx.compose.material.icons.filled.Attribution import androidx.compose.material.icons.filled.Celebration import androidx.compose.material.icons.filled.Download @@ -31,11 +30,11 @@ import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Today import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme @@ -64,6 +63,7 @@ import androidx.core.net.toUri import com.example.llama.data.model.ModelInfo import com.example.llama.data.source.remote.HuggingFaceModel import com.example.llama.ui.components.InfoAction +import com.example.llama.ui.components.InfoAlertDialog import com.example.llama.ui.components.InfoView import com.example.llama.ui.components.ModelCardFullExpandable import com.example.llama.ui.scaffold.ScaffoldEvent @@ -626,25 +626,17 @@ private fun DownloadHuggingFaceDispatchedDialog( modelId: String, onConfirm: () -> Unit, ) { - AlertDialog( - onDismissRequest = {}, - properties = DialogProperties( - dismissOnBackPress = false, - dismissOnClickOutside = false - ), - title = {}, - text = { - InfoView( - title = "Download has started", - icon = Icons.Default.Download, - message = "Your Android system download manager has started downloading the model: $modelId.\n\n" - + "You can track its progress in your notification drawer.\n" - + "Feel free to stay on this screen, or come back to import it after complete.", - ) - }, - confirmButton = { - Button(onClick = onConfirm) { Text("Okay") } - }, + InfoAlertDialog( + title = "Download has started", + icon = Icons.Default.Download, + message = "Your Android system download manager has started downloading the model: $modelId.\n\n" + + "You can track its progress in your notification drawer.\n" + + "Feel free to stay on this screen, or come back to import it after complete.", + action = InfoAction( + icon = Icons.AutoMirrored.Default.ArrowForward, + label = "Okay", + onAction = onConfirm + ) ) } @@ -652,26 +644,15 @@ private fun DownloadHuggingFaceDispatchedDialog( private fun FirstModelImportSuccessDialog( onConfirm: () -> Unit, ) { - AlertDialog( - // Prevent dismissal via back button during deletion - properties = DialogProperties( - dismissOnBackPress = false, - dismissOnClickOutside = false - ), - onDismissRequest = {}, - text = { - InfoView( - title = "Congratulations", - icon = Icons.Default.Celebration, - message = "You have just installed your first Large Language Model!", - action = InfoAction( - label = "Check it out", - icon = Icons.AutoMirrored.Default.ArrowForward, - onAction = onConfirm - ) - ) - }, - confirmButton = {} + InfoAlertDialog( + title = "Congratulations", + icon = Icons.Default.Celebration, + message = "You have just installed your first Large Language Model!", + action = InfoAction( + icon = Icons.AutoMirrored.Default.ArrowForward, + label = "Check it out", + onAction = onConfirm + ) ) } @@ -765,21 +746,16 @@ private fun ErrorDialog( ) } - AlertDialog( - onDismissRequest = onDismiss, - text = { - InfoView( - modifier = Modifier.fillMaxWidth(), - title = title, - icon = Icons.Default.Error, - message = message, - action = action - ) - }, + InfoAlertDialog( + isCritical = true, + title = title, + allowDismiss = true, + onDismiss = onDismiss, + icon = Icons.Default.Error, + message = message, + action = action, confirmButton = { - TextButton(onClick = onDismiss) { - Text("OK") - } + FilledTonalButton(onClick = onDismiss) { Text("Dismiss") } } ) } diff --git a/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelsScreen.kt b/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelsScreen.kt index 7018b33c11..f93ec02937 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelsScreen.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelsScreen.kt @@ -3,13 +3,16 @@ package com.example.llama.ui.screens import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.Text @@ -20,6 +23,8 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.example.llama.data.model.ModelInfo import com.example.llama.ui.components.InfoView @@ -149,12 +154,26 @@ private fun RamErrorDialog( val availableRam = formatFileByteSize(ramError.availableRam) AlertDialog( + icon = { + Icon( + imageVector = Icons.Default.Warning, + contentDescription = "Warning icon", + modifier = Modifier.size(64.dp), + tint = MaterialTheme.colorScheme.error + ) + }, + title = { + Text( + modifier = Modifier.padding(top = 16.dp), + text = "Insufficient RAM", + style = MaterialTheme.typography.headlineSmall, + textAlign = TextAlign.Center, + fontWeight = FontWeight.SemiBold + ) + }, text = { - InfoView( - modifier = Modifier.fillMaxWidth(), - title = "Insufficient RAM", - icon = Icons.Default.Warning, - message = "You are trying to run a $requiredRam size model, " + + Text( + "You are trying to run a $requiredRam size model, " + "but currently there's only $availableRam memory available!", ) }, @@ -162,17 +181,19 @@ private fun RamErrorDialog( titleContentColor = MaterialTheme.colorScheme.onErrorContainer, textContentColor = MaterialTheme.colorScheme.onErrorContainer, dismissButton = { - TextButton(onClick = onDismiss) { - Text("Cancel") + TextButton( + onClick = onConfirm, + colors = ButtonDefaults.textButtonColors( + contentColor = MaterialTheme.colorScheme.error + ) + ) { + Text("Proceed") } }, onDismissRequest = onDismiss, confirmButton = { - TextButton(onClick = onConfirm) { - Text( - text = "Proceed", - color = MaterialTheme.colorScheme.error - ) + FilledTonalButton(onClick = onDismiss) { + Text("Cancel") } } )