fix: blank page at start — SSL, HTTP errors, old-key migration
CI / test-and-package (push) Successful in 1m6s
CI / build-android (push) Successful in 1m39s

- WebView background set to #F5F5F5 to avoid white flash
- SSL certificate errors now show a dialog (user can accept self-signed)
- HTTP errors (4xx/5xx) shown with status code
- Migrates old 'public_url' key to 'server_url' automatically
- Better error messages with troubleshooting tips
- Progress bar shows real page load progress
- No-server-configured state shows clear instructions
This commit is contained in:
agentbox
2026-08-01 12:00:07 +00:00
parent d7f1005316
commit 8d2585bb6e
3 changed files with 80 additions and 24 deletions
@@ -6,13 +6,16 @@ import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.http.SslError;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -30,6 +33,7 @@ public class MainActivity extends AppCompatActivity {
private static final String PREFS_NAME = "mealtracker_prefs";
private static final String KEY_SERVER_URL = "server_url";
private static final String KEY_PUBLIC_URL = "public_url"; // migrate old key
private WebView webView;
private SwipeRefreshLayout swipeRefresh;
@@ -38,6 +42,7 @@ public class MainActivity extends AppCompatActivity {
private TextView toolbarTitle;
private String serverUrl;
private String currentUrl;
private boolean firstLoad = true;
@SuppressLint("SetJavaScriptEnabled")
@Override
@@ -61,11 +66,22 @@ public class MainActivity extends AppCompatActivity {
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
serverUrl = prefs.getString(KEY_SERVER_URL, "");
// Migrate from old public_url key if present
if (serverUrl.isEmpty()) {
String oldUrl = prefs.getString(KEY_PUBLIC_URL, "");
if (!oldUrl.isEmpty()) {
prefs.edit().putString(KEY_SERVER_URL, oldUrl).remove(KEY_PUBLIC_URL).apply();
serverUrl = oldUrl;
}
}
if (!serverUrl.isEmpty()) {
loadUrl(serverUrl);
toolbarTitle.setText(getString(R.string.app_name));
} else {
toolbarTitle.setText(R.string.app_name);
errorView.setText(R.string.server_not_configured);
errorView.setVisibility(View.VISIBLE);
webView.setVisibility(View.GONE);
showSettingsDialog();
}
}
@@ -86,7 +102,9 @@ public class MainActivity extends AppCompatActivity {
settings.setAllowFileAccess(false);
settings.setSaveFormData(true);
// Accept all cookies so login sessions persist
// Avoid blank flash — match the site background
webView.setBackgroundColor(0xFFF5F5F5);
CookieManager.getInstance().setAcceptCookie(true);
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);
@@ -96,6 +114,7 @@ public class MainActivity extends AppCompatActivity {
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
firstLoad = false;
progressBar.setVisibility(View.VISIBLE);
errorView.setVisibility(View.GONE);
webView.setVisibility(View.VISIBLE);
@@ -112,23 +131,39 @@ public class MainActivity extends AppCompatActivity {
public void onReceivedError(WebView view, WebResourceRequest request,
WebResourceError error) {
if (request.isForMainFrame()) {
progressBar.setVisibility(View.GONE);
swipeRefresh.setRefreshing(false);
if (!isNetworkAvailable()) {
errorView.setText(R.string.error_no_network);
} else {
errorView.setText(R.string.error_load_failed);
}
errorView.setVisibility(View.VISIBLE);
webView.setVisibility(View.GONE);
showError(isNetworkAvailable()
? R.string.error_load_failed : R.string.error_no_network);
}
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request,
WebResourceResponse errorResponse) {
// Only show error for the main page, not subresources
if (request.isForMainFrame() && firstLoad) {
int code = errorResponse.getStatusCode();
errorView.setText(getString(R.string.error_http, code));
showError(0); // 0 means keep the text we just set
}
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
// Let the user decide — some self-hosted instances use self-signed certs
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.ssl_error_title)
.setMessage(getString(R.string.ssl_error_msg,
error.getUrl(), error.getPrimaryError()))
.setPositiveButton(R.string.ssl_proceed, (d, w) -> handler.proceed())
.setNegativeButton(R.string.ssl_cancel, (d, w) -> handler.cancel())
.setCancelable(false)
.show();
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
// Keep all navigation inside the WebView
view.loadUrl(url);
view.loadUrl(request.getUrl().toString());
return true;
}
});
@@ -136,6 +171,7 @@ public class MainActivity extends AppCompatActivity {
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
progressBar.setProgress(newProgress);
if (newProgress == 100) {
progressBar.setVisibility(View.GONE);
swipeRefresh.setRefreshing(false);
@@ -151,17 +187,32 @@ public class MainActivity extends AppCompatActivity {
});
errorView.setOnClickListener(v -> {
if (serverUrl != null) {
if (serverUrl != null && !serverUrl.isEmpty()) {
firstLoad = true;
loadUrl(serverUrl);
} else {
showSettingsDialog();
}
});
}
private void showError(int resId) {
progressBar.setVisibility(View.GONE);
swipeRefresh.setRefreshing(false);
if (resId != 0) {
errorView.setText(resId);
}
errorView.setVisibility(View.VISIBLE);
webView.setVisibility(View.GONE);
}
private void loadUrl(String url) {
serverUrl = url;
currentUrl = url;
firstLoad = true;
errorView.setVisibility(View.GONE);
webView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.VISIBLE);
webView.loadUrl(url);
}
@@ -182,13 +233,14 @@ public class MainActivity extends AppCompatActivity {
builder.setPositiveButton(R.string.save, (dialog, which) -> {
String url = urlInput.getText().toString().trim();
if (!url.isEmpty()) {
// Normalize: strip trailing slashes, auto-add https://
url = url.replaceAll("/+$", "");
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "https://" + url;
}
prefs.edit().putString(KEY_SERVER_URL, url).apply();
firstLoad = true;
loadUrl(url);
toolbarTitle.setText(R.string.app_name);
Toast.makeText(this, R.string.url_saved, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, R.string.url_empty, Toast.LENGTH_SHORT).show();
@@ -220,9 +272,7 @@ public class MainActivity extends AppCompatActivity {
webView.clearFormData();
CookieManager.getInstance().removeAllCookies(null);
CookieManager.getInstance().flush();
// Clear WebView storage
webView.clearSslPreferences();
// Nuke DOM/local storage
webView.evaluateJavascript("localStorage.clear(); sessionStorage.clear();", null);
}
@@ -60,9 +60,10 @@
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="3dp"
android:indeterminate="true"
android:max="100"
android:visibility="gone"
android:indeterminateTint="@color/colorAccent" />
android:indeterminateTint="@color/colorAccent"
android:progressTint="@color/colorAccent" />
</com.google.android.material.appbar.AppBarLayout>
+8 -3
View File
@@ -5,14 +5,19 @@
<string name="settings">Settings</string>
<string name="settings_title">Server Configuration</string>
<string name="url_label">Enter your Meal Tracker server URL:</string>
<string name="url_hint_text">This is the URL where your household\'s meal tracker is hosted.\nExample: https://meals.example.com</string>
<string name="url_hint_text">The address where your meal tracker is hosted.\nExample: https://meals.example.com</string>
<string name="save">Save</string>
<string name="cancel">Cancel</string>
<string name="clear_data">Clear Data</string>
<string name="url_saved">Connected to server</string>
<string name="url_empty">Please enter a server URL</string>
<string name="error_no_network">No network connection.\nTap to retry.</string>
<string name="error_load_failed">Could not reach the server.\nCheck the URL and tap to retry.</string>
<string name="server_not_configured">No server configured.\nTap the gear icon to set up.</string>
<string name="error_load_failed">Could not reach the server.\nCheck the URL and tap to retry.\n\nTip: make sure the URL opens in a browser first.</string>
<string name="error_http">Server returned HTTP %d.\nCheck your server URL and tap to retry.</string>
<string name="server_not_configured">No server configured.\nTap the gear icon ⚙ to enter your server URL.</string>
<string name="data_cleared">Local data cleared</string>
<string name="ssl_error_title">⚠ Certificate Warning</string>
<string name="ssl_error_msg">The server at %1$s uses an untrusted certificate (error %2$d).\n\nThis is common for self-hosted servers. Proceed only if you trust this server.</string>
<string name="ssl_proceed">Proceed</string>
<string name="ssl_cancel">Cancel</string>
</resources>