diff --git a/JournalApp/Data/PreferenceService.cs b/JournalApp/Data/PreferenceService.cs index 4fb53b3..8e99fa0 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,40 @@ 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()) + 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"); + + // 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/MainPage.xaml.cs b/JournalApp/MainPage.xaml.cs index 957c618..2639611 100644 --- a/JournalApp/MainPage.xaml.cs +++ b/JournalApp/MainPage.xaml.cs @@ -1,4 +1,4 @@ -namespace JournalApp; +namespace JournalApp; public partial class MainPage : ContentPage { diff --git a/JournalApp/wwwroot/app.css b/JournalApp/wwwroot/app.css index 3e44c52..c2a8b27 100644 --- a/JournalApp/wwwroot/app.css +++ b/JournalApp/wwwroot/app.css @@ -2,6 +2,14 @@ -webkit-tap-highlight-color: transparent; } +/* 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: 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 { 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 47bcf2b..6d2a875 100644 --- a/JournalApp/wwwroot/index.html +++ b/JournalApp/wwwroot/index.html @@ -15,8 +15,6 @@ -
-

Getting things ready...