Error Handler
This commit is contained in:
parent
be950a83da
commit
085ea807c9
@ -63,4 +63,5 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
package id.ac.ubharajaya.sistemakademik
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Lock
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
|
import androidx.compose.material.icons.filled.WifiOff
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
// 1. Tampilan Error Full Screen (Misal saat gagal load list)
|
||||||
|
@Composable
|
||||||
|
fun FullScreenErrorState(
|
||||||
|
message: String,
|
||||||
|
onRetry: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxSize().padding(32.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (message.contains("internet")) Icons.Default.WifiOff else Icons.Default.Warning,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(64.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = "Ups, Terjadi Masalah",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = message,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Button(onClick = onRetry) {
|
||||||
|
Text("Coba Lagi")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Dialog Error (Misal saat gagal submit absen)
|
||||||
|
@Composable
|
||||||
|
fun ErrorDialog(
|
||||||
|
message: String,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text("Gagal") },
|
||||||
|
text = { Text(message) },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("OK")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(Icons.Default.Warning, contentDescription = null, tint = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Session Expired Dialog (Khusus Logout)
|
||||||
|
@Composable
|
||||||
|
fun SessionExpiredDialog(
|
||||||
|
onConfirm: () -> Unit
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {}, // Tidak bisa dicancel
|
||||||
|
title = { Text("Sesi Berakhir") },
|
||||||
|
text = { Text("Token akses Anda sudah kadaluarsa. Silakan login kembali untuk melanjutkan.") },
|
||||||
|
confirmButton = {
|
||||||
|
Button(onClick = onConfirm) {
|
||||||
|
Text("Login Ulang")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(Icons.Default.Lock, contentDescription = null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,43 @@
|
|||||||
|
package id.ac.ubharajaya.sistemakademik
|
||||||
|
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.net.ConnectException
|
||||||
|
import java.net.SocketTimeoutException
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
|
object ErrorHandler {
|
||||||
|
|
||||||
|
// Menerjemahkan Exception Java ke Pesan Manusia
|
||||||
|
fun parseException(e: Exception): String {
|
||||||
|
return when (e) {
|
||||||
|
is UnknownHostException -> "🚫 Tidak ada koneksi internet. Periksa wifi/data Anda."
|
||||||
|
is SocketTimeoutException -> "⏳ Waktu habis. Server tidak merespons (Timeout)."
|
||||||
|
is ConnectException -> "🔌 Gagal terhubung ke server. Server mungkin sedang offline."
|
||||||
|
else -> "⚠️ Terjadi kesalahan: ${e.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menerjemahkan Response Code HTTP
|
||||||
|
fun parseHttpError(responseCode: Int, errorBody: String): String {
|
||||||
|
// Coba ambil pesan error spesifik dari JSON server (jika ada)
|
||||||
|
val serverMessage = try {
|
||||||
|
val json = JSONObject(errorBody)
|
||||||
|
json.optString("error", "")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverMessage.isNotEmpty()) return serverMessage
|
||||||
|
|
||||||
|
// Jika tidak ada pesan JSON, gunakan pesan default berdasarkan kode
|
||||||
|
return when (responseCode) {
|
||||||
|
400 -> "❌ Permintaan tidak valid (Bad Request)."
|
||||||
|
401 -> "🔒 Sesi telah berakhir. Silakan login kembali."
|
||||||
|
403 -> "⛔ Anda tidak memiliki akses ke fitur ini."
|
||||||
|
404 -> "🔍 Data atau alamat tidak ditemukan."
|
||||||
|
500 -> "🔥 Terjadi kesalahan internal pada server."
|
||||||
|
502, 503 -> "🚧 Server sedang dalam perbaikan (Maintenance)."
|
||||||
|
else -> "⚠️ Terjadi kesalahan (Kode: $responseCode)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user