From 8d2585bb6e646bc8010c12755258433f23b2231c Mon Sep 17 00:00:00 2001 From: agentbox Date: Sat, 1 Aug 2026 12:00:07 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20blank=20page=20at=20start=20=E2=80=94=20?= =?UTF-8?q?SSL,=20HTTP=20errors,=20old-key=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../com/mealtracker/app/MainActivity.java | 88 +++++++++++++++---- .../app/src/main/res/layout/activity_main.xml | 5 +- android/app/src/main/res/values/strings.xml | 11 ++- 3 files changed, 80 insertions(+), 24 deletions(-) diff --git a/android/app/src/main/java/com/mealtracker/app/MainActivity.java b/android/app/src/main/java/com/mealtracker/app/MainActivity.java index 6dbfba0..1486fac 100644 --- a/android/app/src/main/java/com/mealtracker/app/MainActivity.java +++ b/android/app/src/main/java/com/mealtracker/app/MainActivity.java @@ -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); } diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 70225ea..211b6df 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -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" /> diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 5c3a5ec..34246c9 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -5,14 +5,19 @@ Settings Server Configuration Enter your Meal Tracker server URL: - This is the URL where your household\'s meal tracker is hosted.\nExample: https://meals.example.com + The address where your meal tracker is hosted.\nExample: https://meals.example.com Save Cancel Clear Data Connected to server Please enter a server URL No network connection.\nTap to retry. - Could not reach the server.\nCheck the URL and tap to retry. - No server configured.\nTap the gear icon to set up. + Could not reach the server.\nCheck the URL and tap to retry.\n\nTip: make sure the URL opens in a browser first. + Server returned HTTP %d.\nCheck your server URL and tap to retry. + No server configured.\nTap the gear icon ⚙ to enter your server URL. Local data cleared + ⚠ Certificate Warning + 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. + Proceed + Cancel