Add error dialogs with retry/cancel for network failures

Implemented user-friendly error handling for network call failures:

Android (Compose):
- Created reusable ErrorDialog component with retry and cancel buttons
- Updated TasksScreen to show error dialog popup instead of inline error
- Error dialog appears when network calls fail with option to retry

iOS (SwiftUI):
- Created ErrorAlertModifier and ErrorAlertInfo helpers
- Added .errorAlert() view modifier for consistent error handling
- Updated TaskFormView to show error alerts with retry/cancel options
- Error alerts appear when task creation or other network calls fail

Both platforms now provide a consistent user experience when network
errors occur, giving users the choice to retry the operation or cancel.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-14 11:20:58 -06:00
parent c189971906
commit 0e3b9681f6
6 changed files with 176 additions and 29 deletions

View File

@@ -0,0 +1,65 @@
package com.mycrib.android.ui.components
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
/**
* Reusable error dialog component that shows network errors with retry/cancel options
*
* @param title Dialog title (default: "Network Error")
* @param message Error message to display
* @param onRetry Callback when user clicks Retry button
* @param onDismiss Callback when user clicks Cancel or dismisses dialog
* @param retryButtonText Text for retry button (default: "Try Again")
* @param dismissButtonText Text for dismiss button (default: "Cancel")
*/
@Composable
fun ErrorDialog(
title: String = "Network Error",
message: String,
onRetry: () -> Unit,
onDismiss: () -> Unit,
retryButtonText: String = "Try Again",
dismissButtonText: String = "Cancel"
) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = title,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.error
)
},
text = {
Text(
text = message,
style = MaterialTheme.typography.bodyMedium
)
},
confirmButton = {
Button(
onClick = {
onDismiss()
onRetry()
},
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
) {
Text(retryButtonText)
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(dismissButtonText)
}
}
)
}

View File

@@ -11,6 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.mycrib.android.ui.components.CompleteTaskDialog
import com.mycrib.android.ui.components.ErrorDialog
import com.mycrib.android.ui.components.task.TaskCard
import com.mycrib.android.ui.components.task.TaskPill
import com.mycrib.android.ui.utils.getIconFromName
@@ -32,6 +33,16 @@ fun TasksScreen(
var expandedColumns by remember { mutableStateOf(setOf<String>()) }
var showCompleteDialog by remember { mutableStateOf(false) }
var selectedTask by remember { mutableStateOf<com.mycrib.shared.models.TaskDetail?>(null) }
var showErrorDialog by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf("") }
// Show error dialog when tasks fail to load
LaunchedEffect(tasksState) {
if (tasksState is ApiResult.Error) {
errorMessage = (tasksState as ApiResult.Error).message
showErrorDialog = true
}
}
LaunchedEffect(Unit) {
viewModel.loadTasks()
@@ -64,7 +75,7 @@ fun TasksScreen(
// No FAB on Tasks screen - tasks are added from within residences
) { paddingValues ->
when (tasksState) {
is ApiResult.Idle, is ApiResult.Loading -> {
is ApiResult.Idle, is ApiResult.Loading, is ApiResult.Error -> {
Box(
modifier = Modifier
.fillMaxSize()
@@ -74,25 +85,6 @@ fun TasksScreen(
CircularProgressIndicator()
}
}
is ApiResult.Error -> {
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = androidx.compose.ui.Alignment.Center
) {
Column(horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally) {
Text(
text = "Error: ${(tasksState as ApiResult.Error).message}",
color = MaterialTheme.colorScheme.error
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { viewModel.loadTasks() }) {
Text("Retry")
}
}
}
}
is ApiResult.Success -> {
val taskData = (tasksState as ApiResult.Success).data
val hasNoTasks = taskData.columns.all { it.tasks.isEmpty() }
@@ -276,4 +268,18 @@ fun TasksScreen(
}
)
}
// Show error dialog when network call fails
if (showErrorDialog) {
ErrorDialog(
message = errorMessage,
onRetry = {
showErrorDialog = false
viewModel.loadTasks()
},
onDismiss = {
showErrorDialog = false
}
)
}
}