fix: blank page at start — SSL, HTTP errors, old-key migration
- 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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user