From f698dd9b1c638092c5c313889c1ac94feb91245f Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 6 Nov 2026 23:05:05 -0700 Subject: The WebView builds fit between the system bars, wear the page's colour there, and offer passkeys. Android 15 draws every app edge to edge, so the page ran under the status bar and the navigation buttons sat on its bottom row: MainActivity now hosts the WebView in a frame padded by the system bars, the display cutout and the keyboard; the frame takes the page's theme-color (read on load and watched for theme switches, through CoinosHost.bars) with dark or light bar icons to match, and 3-button navigation's own light scrim is turned off. And a WebView exposes no WebAuthn unless the app asks, so passkey sign-in and the fingerprint lock were missing (Adam, 2026-11-06, on the Beta): the webview, fcm and fdroid builds turn on androidx.webkit's WebAuthn support (WEB_AUTHENTICATION_SUPPORT_FOR_APP), and assetlinks.json grants get_login_creds for v3.coinos.io to io.coinos.app, io.coinos.beta, io.coinos.webview and io.coinos.fdroid (same signing certificate; the TWA's handle_all_urls entry unchanged) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VMan6Ts5Tktega8VbLg31q --- .../webview/java/io/coinos/app/MainActivity.java | 53 +++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) (limited to 'android/app/src/webview') diff --git a/android/app/src/webview/java/io/coinos/app/MainActivity.java b/android/app/src/webview/java/io/coinos/app/MainActivity.java index b26d6c5..806fd8f 100644 --- a/android/app/src/webview/java/io/coinos/app/MainActivity.java +++ b/android/app/src/webview/java/io/coinos/app/MainActivity.java @@ -38,6 +38,7 @@ public class MainActivity extends Activity { static final String BASE = "https://v3.coinos.io/"; private WebView web; + private android.widget.FrameLayout root; private ValueCallback filePicker; private static final int REQ_FILE = 10, REQ_PERMS = 11; @@ -45,8 +46,27 @@ public class MainActivity extends Activity { protected void onCreate(Bundle saved) { super.onCreate(saved); web = new WebView(this); - setContentView(web, new ViewGroup.LayoutParams( + // Android 15 draws every app edge to edge, so the page ran under the + // status bar and the navigation buttons sat on top of its bottom nav. + // The page goes in a frame padded by the system bars (and the keyboard, + // which then resizes the page as adjustResize did before); the frame's + // background is the theme's bar colour. + root = new android.widget.FrameLayout(this); + root.addView(web, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + // 3-button navigation gets a light scrim of the system's own over + // whatever colour is behind it; the frame's colour is the one wanted + if (Build.VERSION.SDK_INT >= 29) getWindow().setNavigationBarContrastEnforced(false); + if (Build.VERSION.SDK_INT >= 30) { + root.setOnApplyWindowInsetsListener((v, insets) -> { + android.graphics.Insets b = insets.getInsets( + android.view.WindowInsets.Type.systemBars() | android.view.WindowInsets.Type.ime() + | android.view.WindowInsets.Type.displayCutout()); + v.setPadding(b.left, b.top, b.right, b.bottom); + return android.view.WindowInsets.CONSUMED; + }); + } + setContentView(root); // A debug build can be inspected from a desktop (chrome://inspect); a // release build of a wallet must not be — anything with ADB access could @@ -63,6 +83,16 @@ public class MainActivity extends Activity { s.setLoadWithOverviewMode(true); s.setUseWideViewPort(true); CookieManager.getInstance().setAcceptCookie(true); + // Passkeys: a WebView offers the page no WebAuthn unless the app asks for + // it, so passkey sign-in and the fingerprint lock were missing here. The + // site's assetlinks.json vouches for this app (get_login_creds), which + // is what lets it use v3.coinos.io's passkeys. + try { + if (androidx.webkit.WebViewFeature.isFeatureSupported(androidx.webkit.WebViewFeature.WEB_AUTHENTICATION)) { + androidx.webkit.WebSettingsCompat.setWebAuthenticationSupport(s, + androidx.webkit.WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_FOR_APP); + } + } catch (Throwable ignored) {} web.setWebViewClient(new WebViewClient() { // The bundled build answers the site's own files from assets; the @@ -79,6 +109,10 @@ public class MainActivity extends Activity { @Override public void onPageFinished(WebView v, String url) { endpointToPage(); + // the bars take the page's colour, now and whenever its theme changes + v.evaluateJavascript("(function(){var m=document.querySelector('meta[name=theme-color]');" + + "if(!m||!window.CoinosHost||!CoinosHost.bars)return;var f=function(){CoinosHost.bars(m.content||'')};" + + "f();new MutationObserver(f).observe(m,{attributes:true,attributeFilter:['content']})})()", null); } @Override @@ -165,6 +199,23 @@ public class MainActivity extends Activity { + ",\"distributor\":" + (dist == null ? "null" : "\"" + dist + "\"") + ",\"endpoint\":" + (endpoint != null) + "}"; } + /** The page's theme colour, for the bar areas around it (see onCreate). */ + @android.webkit.JavascriptInterface + public void bars(String css) { + final int color; + try { color = android.graphics.Color.parseColor(css.trim()); } catch (Exception e) { return; } + runOnUiThread(() -> { + if (root != null) root.setBackgroundColor(color); + if (Build.VERSION.SDK_INT >= 30 && getWindow().getInsetsController() != null) { + // dark icons on a light colour, light icons on a dark one + double lum = (0.299 * android.graphics.Color.red(color) + 0.587 * android.graphics.Color.green(color) + + 0.114 * android.graphics.Color.blue(color)) / 255; + int light = android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS + | android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; + getWindow().getInsetsController().setSystemBarsAppearance(lum > 0.5 ? light : 0, light); + } + }); + } /** The wake job's bridge has this too; here it's a no-op. */ @android.webkit.JavascriptInterface public void done() {} -- cgit v1.2.3