From c6f71992cc4fbd6147004fa885f6bce4f8086ba0 Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 17 Jul 2026 12:41:08 -0500 Subject: [PATCH 1/3] Go edge-to-edge on Android with a window insets bridge The web UI now extends under the transparent status and navigation bars. An insets listener on the BlazorWebView injects safe area values as CSS custom properties, with env() as a fallback for newer WebViews, since env(safe-area-inset-*) is unreliable below Chromium 140. The sticky page header absorbs the top inset so the status bar always sits on the app bar surface, and the page body pads by the bottom inset so content scrolls under the gesture bar. Bar icon contrast now follows dark mode through the insets controller. Android 14 and below keep opaque bars tinted to the M3 surface, and the navigation bar is now themed there too. Verified on the API 35 emulator in light and dark, with the bridge proven by disabling the env() fallback, and keyboard resize intact. --- JournalApp/Data/CommonServices.cs | 1 + JournalApp/Data/PreferenceService.cs | 27 +++++++--- JournalApp/Data/SafeAreaService.cs | 76 ++++++++++++++++++++++++++++ JournalApp/MainPage.xaml | 4 +- JournalApp/MainPage.xaml.cs | 11 +++- JournalApp/Pages/MainLayout.razor | 4 ++ JournalApp/wwwroot/app.css | 50 ++++++++---------- JournalApp/wwwroot/index.html | 2 - 8 files changed, 134 insertions(+), 41 deletions(-) create mode 100644 JournalApp/Data/SafeAreaService.cs diff --git a/JournalApp/Data/CommonServices.cs b/JournalApp/Data/CommonServices.cs index 7bf80da8..e6237188 100644 --- a/JournalApp/Data/CommonServices.cs +++ b/JournalApp/Data/CommonServices.cs @@ -24,6 +24,7 @@ public static void AddCommonJournalAppServices(this IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); } diff --git a/JournalApp/Data/PreferenceService.cs b/JournalApp/Data/PreferenceService.cs index 4fb53b38..7ea48570 100644 --- a/JournalApp/Data/PreferenceService.cs +++ b/JournalApp/Data/PreferenceService.cs @@ -51,7 +51,7 @@ public PreferenceService(ILogger logger, IPreferences prefere _application.RequestedThemeChanged += Application_RequestedThemeChanged; } - UpdateStatusBar(); + UpdateSystemBars(); } public AppTheme SelectedAppTheme @@ -188,20 +188,35 @@ private void Application_RequestedThemeChanged(object sender, AppThemeChangedEve private void OnThemeChanged() { - UpdateStatusBar(); + UpdateSystemBars(); ThemeChanged?.Invoke(this, IsDarkMode); } - private void UpdateStatusBar() + private void UpdateSystemBars() { - if (OperatingSystem.IsAndroid()) +#if ANDROID + logger.LogDebug("Updating system bars"); + + // Edge-to-edge bars are transparent overlays so icon contrast is the only knob on Android 15+. + var window = Platform.CurrentActivity?.Window; + if (window is not null) + { + var controller = AndroidX.Core.View.WindowCompat.GetInsetsController(window, window.DecorView); + controller.AppearanceLightStatusBars = !IsDarkMode; + controller.AppearanceLightNavigationBars = !IsDarkMode; + } + + // Older versions still draw opaque bars so tint them to the M3 surface tone to blend into the page. + if (!OperatingSystem.IsAndroidVersionAtLeast(35)) { - logger.LogDebug("Updating status bar"); - // Match the M3 surface tone so the status bar blends into the page header. var surface = IsDarkMode ? GetTheme().PaletteDark.Background : GetTheme().PaletteLight.Background; StatusBar.SetColor(Color.FromRgb(surface.R, surface.G, surface.B)); StatusBar.SetStyle(IsDarkMode ? StatusBarStyle.LightContent : StatusBarStyle.DarkContent); +#pragma warning disable CA1422 // Deprecated in API 35 which the surrounding check excludes. + window?.SetNavigationBarColor(Android.Graphics.Color.Rgb(surface.R, surface.G, surface.B)); +#pragma warning restore CA1422 } +#endif } public string GetMoodColor(string emoji) diff --git a/JournalApp/Data/SafeAreaService.cs b/JournalApp/Data/SafeAreaService.cs new file mode 100644 index 00000000..7e9f6d0b --- /dev/null +++ b/JournalApp/Data/SafeAreaService.cs @@ -0,0 +1,76 @@ +namespace JournalApp; + +/// +/// Bridges Android window insets into CSS custom properties so the edge-to-edge web UI can pad around the system bars. +/// +public sealed class SafeAreaService(ILogger logger) +{ +#if ANDROID + private Android.Webkit.WebView _webView; + private float _density = 1; + private AndroidX.Core.Graphics.Insets _insets; + + /// + /// Watches the web view for inset changes and pushes each one into the page. + /// + public void Attach(Android.Webkit.WebView webView) + { + _webView = webView; + _density = webView.Resources.DisplayMetrics.Density; + + AndroidX.Core.View.ViewCompat.SetOnApplyWindowInsetsListener(webView, new InsetsListener(this)); + AndroidX.Core.View.ViewCompat.RequestApplyInsets(webView); + } + + private void OnInsetsChanged(AndroidX.Core.Graphics.Insets insets) + { + if (insets.Equals(_insets)) + return; + + _insets = insets; + logger.LogDebug($"Window insets changed: {insets}"); + Apply(); + } + + private void Apply() + { + if (_webView == null || _insets == null) + return; + + // CSS pixels are density-independent, same as dp with the app's unscaled viewport. + var js = "document.documentElement.style.setProperty('--safe-area-inset-top', '" + Dp(_insets.Top) + "px');" + + "document.documentElement.style.setProperty('--safe-area-inset-right', '" + Dp(_insets.Right) + "px');" + + "document.documentElement.style.setProperty('--safe-area-inset-bottom', '" + Dp(_insets.Bottom) + "px');" + + "document.documentElement.style.setProperty('--safe-area-inset-left', '" + Dp(_insets.Left) + "px');"; + + _webView.Post(() => _webView.EvaluateJavascript(js, null)); + } + + private int Dp(int px) => (int)Math.Round(px / _density); + + private sealed class InsetsListener(SafeAreaService owner) : Java.Lang.Object, AndroidX.Core.View.IOnApplyWindowInsetsListener + { + public AndroidX.Core.View.WindowInsetsCompat OnApplyWindowInsets(Android.Views.View v, AndroidX.Core.View.WindowInsetsCompat insets) + { + var types = AndroidX.Core.View.WindowInsetsCompat.Type.SystemBars() | AndroidX.Core.View.WindowInsetsCompat.Type.DisplayCutout(); + owner.OnInsetsChanged(insets.GetInsets(types)); + return insets; + } + } +#endif + + /// + /// Re-applies the last known insets in case the first dispatch beat the page load. + /// + public void Reapply() + { + logger.LogDebug("Reapplying safe area insets"); + +#if ANDROID + Apply(); + + if (_webView != null) + AndroidX.Core.View.ViewCompat.RequestApplyInsets(_webView); +#endif + } +} diff --git a/JournalApp/MainPage.xaml b/JournalApp/MainPage.xaml index 0b34a8a3..01a9514f 100644 --- a/JournalApp/MainPage.xaml +++ b/JournalApp/MainPage.xaml @@ -3,9 +3,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:JournalApp" x:Class="JournalApp.MainPage" - SafeAreaEdges="All"> + SafeAreaEdges="None"> - + diff --git a/JournalApp/MainPage.xaml.cs b/JournalApp/MainPage.xaml.cs index 957c618d..2c03050a 100644 --- a/JournalApp/MainPage.xaml.cs +++ b/JournalApp/MainPage.xaml.cs @@ -1,4 +1,6 @@ -namespace JournalApp; +using Microsoft.AspNetCore.Components.WebView; + +namespace JournalApp; public partial class MainPage : ContentPage { @@ -6,4 +8,11 @@ public MainPage() { InitializeComponent(); } + + private void OnBlazorWebViewInitialized(object sender, BlazorWebViewInitializedEventArgs e) + { +#if ANDROID + IPlatformApplication.Current.Services.GetRequiredService().Attach(e.WebView); +#endif + } } diff --git a/JournalApp/Pages/MainLayout.razor b/JournalApp/Pages/MainLayout.razor index 30a555e6..27decfeb 100644 --- a/JournalApp/Pages/MainLayout.razor +++ b/JournalApp/Pages/MainLayout.razor @@ -3,6 +3,7 @@ @implements IDisposable @inject KeyEventService KeyEventService @inject PreferenceService PreferenceService +@inject SafeAreaService SafeAreaService @inject ILogger logger @@ -49,6 +50,9 @@ { _hasInitiallyRendered = true; logger.LogInformation("Blazor layout first render completed"); + + // The first inset dispatch can beat the page load so push the values again now that the DOM is live. + SafeAreaService.Reapply(); } } diff --git a/JournalApp/wwwroot/app.css b/JournalApp/wwwroot/app.css index 3e44c52a..74f1d2c3 100644 --- a/JournalApp/wwwroot/app.css +++ b/JournalApp/wwwroot/app.css @@ -2,6 +2,14 @@ -webkit-tap-highlight-color: transparent; } +/* Native code injects --safe-area-inset-* on Android where env() needs a recent WebView; env() covers everything else. */ +:root { + --inset-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px)); + --inset-right: var(--safe-area-inset-right, env(safe-area-inset-right, 0px)); + --inset-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)); + --inset-left: var(--safe-area-inset-left, env(safe-area-inset-left, 0px)); +} + body { user-select: none; --mud-palette-action-default: var(--mud-palette-text-primary); @@ -71,11 +79,15 @@ body { font-weight: 400; } +/* The sticky header absorbs the top inset so the transparent status bar always sits on the app bar surface. */ .page-header { z-index: var(--mud-zindex-appbar); display: flex; flex-direction: column; padding: 0 !important; + padding-top: var(--inset-top) !important; + padding-left: var(--inset-left) !important; + padding-right: var(--inset-right) !important; margin: 0 !important; top: 0 !important; position: sticky !important; @@ -83,12 +95,15 @@ body { color: var(--mud-palette-text-primary); } +/* The bottom inset keeps the last rows reachable while content scrolls under the transparent gesture bar. */ .page-body { width: 100%; max-width: 960px; margin: 0 auto !important; padding: 8px !important; - padding-bottom: 15vh !important; + padding-left: calc(8px + var(--inset-left)) !important; + padding-right: calc(8px + var(--inset-right)) !important; + padding-bottom: calc(15vh + var(--inset-bottom)) !important; animation: fadeInUp 0.15s ease-out; } @@ -399,38 +414,13 @@ label.mud-switch.mud-disabled .mud-switch-span { display: none; left: 0; padding: 1.25rem; + padding-bottom: calc(1.25rem + var(--inset-bottom)); position: fixed; width: 100%; z-index: 1000; } -.status-bar-safe-area { - display: none; -} - -@supports (-webkit-touch-callout: none) { - .status-bar-safe-area { - display: flex; - position: sticky; - top: 0; - height: env(safe-area-inset-top); - width: 100%; - z-index: 1; - } - - .flex-column, .navbar-brand { - padding-left: env(safe-area-inset-left); - } -} - -@media (prefers-color-scheme: dark) { - .status-bar-safe-area { - background-color: #181215; - } -} - -@media (prefers-color-scheme: light) { - .status-bar-safe-area { - background-color: #FFF8F9; - } +/* Snackbars float above the gesture bar instead of under it. */ +#mud-snackbar-container.mud-snackbar-location-bottom-center { + bottom: calc(24px + var(--inset-bottom)); } diff --git a/JournalApp/wwwroot/index.html b/JournalApp/wwwroot/index.html index 47bcf2b0..6d2a875c 100644 --- a/JournalApp/wwwroot/index.html +++ b/JournalApp/wwwroot/index.html @@ -15,8 +15,6 @@ -
-

Getting things ready...

From ee1cd710732a14f99d4f1309d592d6293bd2c2d9 Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 17 Jul 2026 12:53:05 -0500 Subject: [PATCH 2/3] Resize for the keyboard since edge-to-edge disables adjustResize The insets listener now tracks the IME inset and pads the activity content frame by the keyboard height, shrinking the web viewport so dialogs re-center above the keyboard and focused inputs scroll into view, matching the old adjustResize behavior. While the keyboard is open the CSS bottom inset drops to zero because the viewport already ends at the keyboard. Verified on the API 35 emulator: the note dialog and its actions stay above the keyboard (WebView 1080x1517 while open) and the viewport restores to full height on dismiss. --- JournalApp/Data/SafeAreaService.cs | 40 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/JournalApp/Data/SafeAreaService.cs b/JournalApp/Data/SafeAreaService.cs index 7e9f6d0b..4cf9f6ba 100644 --- a/JournalApp/Data/SafeAreaService.cs +++ b/JournalApp/Data/SafeAreaService.cs @@ -8,7 +8,8 @@ public sealed class SafeAreaService(ILogger logger) #if ANDROID private Android.Webkit.WebView _webView; private float _density = 1; - private AndroidX.Core.Graphics.Insets _insets; + private AndroidX.Core.Graphics.Insets _bars; + private int _imeHeight; /// /// Watches the web view for inset changes and pushes each one into the page. @@ -22,28 +23,40 @@ public void Attach(Android.Webkit.WebView webView) AndroidX.Core.View.ViewCompat.RequestApplyInsets(webView); } - private void OnInsetsChanged(AndroidX.Core.Graphics.Insets insets) + private void OnInsetsChanged(AndroidX.Core.Graphics.Insets bars, int imeHeight) { - if (insets.Equals(_insets)) + if (bars.Equals(_bars) && imeHeight == _imeHeight) return; - _insets = insets; - logger.LogDebug($"Window insets changed: {insets}"); + _bars = bars; + _imeHeight = imeHeight; + logger.LogDebug($"Window insets changed: {bars}, ime {imeHeight}"); Apply(); } private void Apply() { - if (_webView == null || _insets == null) + if (_webView == null || _bars == null) return; - // CSS pixels are density-independent, same as dp with the app's unscaled viewport. - var js = "document.documentElement.style.setProperty('--safe-area-inset-top', '" + Dp(_insets.Top) + "px');" + - "document.documentElement.style.setProperty('--safe-area-inset-right', '" + Dp(_insets.Right) + "px');" + - "document.documentElement.style.setProperty('--safe-area-inset-bottom', '" + Dp(_insets.Bottom) + "px');" + - "document.documentElement.style.setProperty('--safe-area-inset-left', '" + Dp(_insets.Left) + "px');"; + _webView.Post(() => + { + // Edge-to-edge disables the adjustResize soft input mode, so shrink the content frame ourselves to keep dialogs and focused inputs above the keyboard. + var contentFrame = Platform.CurrentActivity?.FindViewById(Android.Resource.Id.Content); + if (contentFrame != null && contentFrame.PaddingBottom != _imeHeight) + contentFrame.SetPadding(0, 0, 0, _imeHeight); + + // The view already ends at the keyboard when it's open, so the gesture bar inset only applies while it's closed. + var bottom = _imeHeight > 0 ? 0 : _bars.Bottom; + + // CSS pixels are density-independent, same as dp with the app's unscaled viewport. + var js = "document.documentElement.style.setProperty('--safe-area-inset-top', '" + Dp(_bars.Top) + "px');" + + "document.documentElement.style.setProperty('--safe-area-inset-right', '" + Dp(_bars.Right) + "px');" + + "document.documentElement.style.setProperty('--safe-area-inset-bottom', '" + Dp(bottom) + "px');" + + "document.documentElement.style.setProperty('--safe-area-inset-left', '" + Dp(_bars.Left) + "px');"; - _webView.Post(() => _webView.EvaluateJavascript(js, null)); + _webView.EvaluateJavascript(js, null); + }); } private int Dp(int px) => (int)Math.Round(px / _density); @@ -53,7 +66,8 @@ private sealed class InsetsListener(SafeAreaService owner) : Java.Lang.Object, A public AndroidX.Core.View.WindowInsetsCompat OnApplyWindowInsets(Android.Views.View v, AndroidX.Core.View.WindowInsetsCompat insets) { var types = AndroidX.Core.View.WindowInsetsCompat.Type.SystemBars() | AndroidX.Core.View.WindowInsetsCompat.Type.DisplayCutout(); - owner.OnInsetsChanged(insets.GetInsets(types)); + var ime = insets.GetInsets(AndroidX.Core.View.WindowInsetsCompat.Type.Ime()); + owner.OnInsetsChanged(insets.GetInsets(types), ime.Bottom); return insets; } } From 11c32d0f4eb533d9d98502990f119eb0fb324f99 Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 17 Jul 2026 13:00:36 -0500 Subject: [PATCH 3/3] Replace the insets bridge with native safe areas and a themed page Letterboxing through SafeAreaEdges is the supported MAUI mechanism, so the custom SafeAreaService and its keyboard workaround are gone. The page background now follows the M3 surface tone, which makes the safe area padding around the web view blend into the app, and MAUI handles the keyboard natively again. The web UI keeps env(safe-area-inset-*) variables that are inert while letterboxed but light up if edge-to-edge is enabled once MAUI's SoftInput mode is fixed and WebView 140+ is widespread. Verified on the API 35 emulator: seamless bars in light and dark, and dialogs with focused inputs sit above the keyboard. --- JournalApp/Data/CommonServices.cs | 1 - JournalApp/Data/PreferenceService.cs | 7 ++- JournalApp/Data/SafeAreaService.cs | 90 ---------------------------- JournalApp/MainPage.xaml | 4 +- JournalApp/MainPage.xaml.cs | 9 --- JournalApp/Pages/MainLayout.razor | 4 -- JournalApp/wwwroot/app.css | 10 ++-- 7 files changed, 13 insertions(+), 112 deletions(-) delete mode 100644 JournalApp/Data/SafeAreaService.cs diff --git a/JournalApp/Data/CommonServices.cs b/JournalApp/Data/CommonServices.cs index e6237188..7bf80da8 100644 --- a/JournalApp/Data/CommonServices.cs +++ b/JournalApp/Data/CommonServices.cs @@ -24,7 +24,6 @@ public static void AddCommonJournalAppServices(this IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); } diff --git a/JournalApp/Data/PreferenceService.cs b/JournalApp/Data/PreferenceService.cs index 7ea48570..8e99fa0c 100644 --- a/JournalApp/Data/PreferenceService.cs +++ b/JournalApp/Data/PreferenceService.cs @@ -194,6 +194,12 @@ private void OnThemeChanged() private void UpdateSystemBars() { + var surface = IsDarkMode ? GetTheme().PaletteDark.Background : GetTheme().PaletteLight.Background; + + // The page shows through the safe area padding around the web view, so match it to the M3 surface tone for seamless bars. + if (App.Window?.Page is not null) + App.Window.Page.BackgroundColor = Color.FromRgb(surface.R, surface.G, surface.B); + #if ANDROID logger.LogDebug("Updating system bars"); @@ -209,7 +215,6 @@ private void UpdateSystemBars() // Older versions still draw opaque bars so tint them to the M3 surface tone to blend into the page. if (!OperatingSystem.IsAndroidVersionAtLeast(35)) { - var surface = IsDarkMode ? GetTheme().PaletteDark.Background : GetTheme().PaletteLight.Background; StatusBar.SetColor(Color.FromRgb(surface.R, surface.G, surface.B)); StatusBar.SetStyle(IsDarkMode ? StatusBarStyle.LightContent : StatusBarStyle.DarkContent); #pragma warning disable CA1422 // Deprecated in API 35 which the surrounding check excludes. diff --git a/JournalApp/Data/SafeAreaService.cs b/JournalApp/Data/SafeAreaService.cs deleted file mode 100644 index 4cf9f6ba..00000000 --- a/JournalApp/Data/SafeAreaService.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace JournalApp; - -/// -/// Bridges Android window insets into CSS custom properties so the edge-to-edge web UI can pad around the system bars. -/// -public sealed class SafeAreaService(ILogger logger) -{ -#if ANDROID - private Android.Webkit.WebView _webView; - private float _density = 1; - private AndroidX.Core.Graphics.Insets _bars; - private int _imeHeight; - - /// - /// Watches the web view for inset changes and pushes each one into the page. - /// - public void Attach(Android.Webkit.WebView webView) - { - _webView = webView; - _density = webView.Resources.DisplayMetrics.Density; - - AndroidX.Core.View.ViewCompat.SetOnApplyWindowInsetsListener(webView, new InsetsListener(this)); - AndroidX.Core.View.ViewCompat.RequestApplyInsets(webView); - } - - private void OnInsetsChanged(AndroidX.Core.Graphics.Insets bars, int imeHeight) - { - if (bars.Equals(_bars) && imeHeight == _imeHeight) - return; - - _bars = bars; - _imeHeight = imeHeight; - logger.LogDebug($"Window insets changed: {bars}, ime {imeHeight}"); - Apply(); - } - - private void Apply() - { - if (_webView == null || _bars == null) - return; - - _webView.Post(() => - { - // Edge-to-edge disables the adjustResize soft input mode, so shrink the content frame ourselves to keep dialogs and focused inputs above the keyboard. - var contentFrame = Platform.CurrentActivity?.FindViewById(Android.Resource.Id.Content); - if (contentFrame != null && contentFrame.PaddingBottom != _imeHeight) - contentFrame.SetPadding(0, 0, 0, _imeHeight); - - // The view already ends at the keyboard when it's open, so the gesture bar inset only applies while it's closed. - var bottom = _imeHeight > 0 ? 0 : _bars.Bottom; - - // CSS pixels are density-independent, same as dp with the app's unscaled viewport. - var js = "document.documentElement.style.setProperty('--safe-area-inset-top', '" + Dp(_bars.Top) + "px');" + - "document.documentElement.style.setProperty('--safe-area-inset-right', '" + Dp(_bars.Right) + "px');" + - "document.documentElement.style.setProperty('--safe-area-inset-bottom', '" + Dp(bottom) + "px');" + - "document.documentElement.style.setProperty('--safe-area-inset-left', '" + Dp(_bars.Left) + "px');"; - - _webView.EvaluateJavascript(js, null); - }); - } - - private int Dp(int px) => (int)Math.Round(px / _density); - - private sealed class InsetsListener(SafeAreaService owner) : Java.Lang.Object, AndroidX.Core.View.IOnApplyWindowInsetsListener - { - public AndroidX.Core.View.WindowInsetsCompat OnApplyWindowInsets(Android.Views.View v, AndroidX.Core.View.WindowInsetsCompat insets) - { - var types = AndroidX.Core.View.WindowInsetsCompat.Type.SystemBars() | AndroidX.Core.View.WindowInsetsCompat.Type.DisplayCutout(); - var ime = insets.GetInsets(AndroidX.Core.View.WindowInsetsCompat.Type.Ime()); - owner.OnInsetsChanged(insets.GetInsets(types), ime.Bottom); - return insets; - } - } -#endif - - /// - /// Re-applies the last known insets in case the first dispatch beat the page load. - /// - public void Reapply() - { - logger.LogDebug("Reapplying safe area insets"); - -#if ANDROID - Apply(); - - if (_webView != null) - AndroidX.Core.View.ViewCompat.RequestApplyInsets(_webView); -#endif - } -} diff --git a/JournalApp/MainPage.xaml b/JournalApp/MainPage.xaml index 01a9514f..0b34a8a3 100644 --- a/JournalApp/MainPage.xaml +++ b/JournalApp/MainPage.xaml @@ -3,9 +3,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:JournalApp" x:Class="JournalApp.MainPage" - SafeAreaEdges="None"> + SafeAreaEdges="All"> - + diff --git a/JournalApp/MainPage.xaml.cs b/JournalApp/MainPage.xaml.cs index 2c03050a..26396118 100644 --- a/JournalApp/MainPage.xaml.cs +++ b/JournalApp/MainPage.xaml.cs @@ -1,5 +1,3 @@ -using Microsoft.AspNetCore.Components.WebView; - namespace JournalApp; public partial class MainPage : ContentPage @@ -8,11 +6,4 @@ public MainPage() { InitializeComponent(); } - - private void OnBlazorWebViewInitialized(object sender, BlazorWebViewInitializedEventArgs e) - { -#if ANDROID - IPlatformApplication.Current.Services.GetRequiredService().Attach(e.WebView); -#endif - } } diff --git a/JournalApp/Pages/MainLayout.razor b/JournalApp/Pages/MainLayout.razor index 27decfeb..30a555e6 100644 --- a/JournalApp/Pages/MainLayout.razor +++ b/JournalApp/Pages/MainLayout.razor @@ -3,7 +3,6 @@ @implements IDisposable @inject KeyEventService KeyEventService @inject PreferenceService PreferenceService -@inject SafeAreaService SafeAreaService @inject ILogger logger @@ -50,9 +49,6 @@ { _hasInitiallyRendered = true; logger.LogInformation("Blazor layout first render completed"); - - // The first inset dispatch can beat the page load so push the values again now that the DOM is live. - SafeAreaService.Reapply(); } } diff --git a/JournalApp/wwwroot/app.css b/JournalApp/wwwroot/app.css index 74f1d2c3..c2a8b27b 100644 --- a/JournalApp/wwwroot/app.css +++ b/JournalApp/wwwroot/app.css @@ -2,12 +2,12 @@ -webkit-tap-highlight-color: transparent; } -/* Native code injects --safe-area-inset-* on Android where env() needs a recent WebView; env() covers everything else. */ +/* Safe area insets are zero while the native page letterboxes the web view, but flow in via env() on WebViews that support it if edge-to-edge is ever enabled. */ :root { - --inset-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px)); - --inset-right: var(--safe-area-inset-right, env(safe-area-inset-right, 0px)); - --inset-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)); - --inset-left: var(--safe-area-inset-left, env(safe-area-inset-left, 0px)); + --inset-top: env(safe-area-inset-top, 0px); + --inset-right: env(safe-area-inset-right, 0px); + --inset-bottom: env(safe-area-inset-bottom, 0px); + --inset-left: env(safe-area-inset-left, 0px); } body {