UI: extract a reusable InfoAlertDialog

This commit is contained in:
Han Yin 2025-09-03 10:14:50 -07:00
parent a4459b22d1
commit 0c6ce7b9a3
3 changed files with 156 additions and 81 deletions

View File

@ -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)
}
}
}

View File

@ -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(
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
)
},
confirmButton = {
Button(onClick = onConfirm) { Text("Okay") }
},
)
}
@ -652,27 +644,16 @@ 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(
InfoAlertDialog(
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,
label = "Check it out",
onAction = onConfirm
)
)
},
confirmButton = {}
)
}
@Composable
@ -765,21 +746,16 @@ private fun ErrorDialog(
)
}
AlertDialog(
onDismissRequest = onDismiss,
text = {
InfoView(
modifier = Modifier.fillMaxWidth(),
InfoAlertDialog(
isCritical = true,
title = title,
allowDismiss = true,
onDismiss = onDismiss,
icon = Icons.Default.Error,
message = message,
action = action
)
},
action = action,
confirmButton = {
TextButton(onClick = onDismiss) {
Text("OK")
}
FilledTonalButton(onClick = onDismiss) { Text("Dismiss") }
}
)
}

View File

@ -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")
}
}
)