feat: Add network error handling
- error.html: User-friendly error page with retry button - ErrorHandler.kt: Utility for handling WebView errors - ErrorHandlerTest.kt: Unit tests for error handling Shows friendly error page on network failures instead of blank/broken page. Includes error code display and one-tap retry.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Connection Error</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.error-container {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
}
|
||||
.icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
p {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.retry-btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 14px 32px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.retry-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.retry-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.error-code {
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<div class="icon">📡</div>
|
||||
<h1>Connection Problem</h1>
|
||||
<p>Unable to load the page. Please check your internet connection and try again.</p>
|
||||
<button class="retry-btn" onclick="window.location.reload()">
|
||||
Try Again
|
||||
</button>
|
||||
<div class="error-code" id="errorCode"></div>
|
||||
</div>
|
||||
<script>
|
||||
// Parse error code from URL if present
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get('code');
|
||||
if (code) {
|
||||
document.getElementById('errorCode').textContent = 'Error code: ' + code;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,80 @@
|
||||
package farm.hobbs.kioskbrowser
|
||||
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
|
||||
/**
|
||||
* Handles WebView errors with user-friendly error pages.
|
||||
*
|
||||
* Usage in WebViewClient:
|
||||
* ```
|
||||
* override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
|
||||
* ErrorHandler.handleError(view, request, error)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
object ErrorHandler {
|
||||
|
||||
private const val ERROR_PAGE = "file:///android_asset/error.html"
|
||||
|
||||
/**
|
||||
* Network error codes that should show the error page
|
||||
*/
|
||||
private val NETWORK_ERRORS = setOf(
|
||||
android.webkit.WebViewClient.ERROR_HOST_LOOKUP,
|
||||
android.webkit.WebViewClient.ERROR_CONNECT,
|
||||
android.webkit.WebViewClient.ERROR_TIMEOUT,
|
||||
android.webkit.WebViewClient.ERROR_IO,
|
||||
android.webkit.WebViewClient.ERROR_UNKNOWN
|
||||
)
|
||||
|
||||
/**
|
||||
* Handle WebView errors - show error page for main frame network errors
|
||||
*/
|
||||
fun handleError(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?,
|
||||
error: WebResourceError?
|
||||
): Boolean {
|
||||
// Only handle main frame errors
|
||||
if (request?.isForMainFrame != true) return false
|
||||
|
||||
val errorCode = error?.errorCode ?: return false
|
||||
|
||||
// Show error page for network errors
|
||||
if (errorCode in NETWORK_ERRORS || errorCode < 0) {
|
||||
view?.loadUrl("$ERROR_PAGE?code=$errorCode")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle HTTP errors (4xx, 5xx)
|
||||
*/
|
||||
fun handleHttpError(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?,
|
||||
statusCode: Int
|
||||
): Boolean {
|
||||
// Only handle main frame errors
|
||||
if (request?.isForMainFrame != true) return false
|
||||
|
||||
// Show error page for server errors
|
||||
if (statusCode >= 500) {
|
||||
view?.loadUrl("$ERROR_PAGE?code=$statusCode")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current URL is the error page
|
||||
*/
|
||||
fun isErrorPage(url: String?): Boolean {
|
||||
return url?.startsWith(ERROR_PAGE.substringBefore("?")) == true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package farm.hobbs.kioskbrowser
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Unit tests for ErrorHandler utility
|
||||
*/
|
||||
class ErrorHandlerTest {
|
||||
|
||||
@Test
|
||||
fun `isErrorPage returns true for error page URL`() {
|
||||
assertTrue(ErrorHandler.isErrorPage("file:///android_asset/error.html"))
|
||||
assertTrue(ErrorHandler.isErrorPage("file:///android_asset/error.html?code=-2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isErrorPage returns false for regular URLs`() {
|
||||
assertFalse(ErrorHandler.isErrorPage("https://hobbs.farm"))
|
||||
assertFalse(ErrorHandler.isErrorPage("https://google.com"))
|
||||
assertFalse(ErrorHandler.isErrorPage(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isErrorPage returns false for similar but different URLs`() {
|
||||
assertFalse(ErrorHandler.isErrorPage("file:///android_asset/other.html"))
|
||||
assertFalse(ErrorHandler.isErrorPage("https://error.html"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user