using Android.App; using Android.Content.PM; using Android.OS; using Android.Views; using AndroidX.Core.View; using Microsoft.Extensions.DependencyInjection; using SteUp.Shared.Core.Messages.System; namespace SteUp.Maui { [Activity( Theme = "@style/Maui.SplashTheme", MainLauncher = true, WindowSoftInputMode = SoftInput.AdjustResize | SoftInput.StateHidden, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { private BackButtonService? _backButton; private BlazorBackCallback? _backCallback; protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); ApplySystemBarsAppearance(); HookBackButton(); // Da Android 15 (API 35) il edge-to-edge e' forzato e adjustResize non ridimensiona // piu' la finestra: la tastiera si limita a coprire la WebView. Riduciamo noi il // content view dell'altezza dell'IME cosi' il layout Blazor (100vh) si ricalcola. if (FindViewById(Android.Resource.Id.Content) is { } content) ViewCompat.SetOnApplyWindowInsetsListener(content, new ImeInsetsListener()); } /// /// Le barre di sistema si fondono con lo sfondo chiaro dell'app: icone /// scure su fondo chiaro. Senza questo la barra di stato resta tinta /// dell'accento, che nel sistema di design indica l'azione, non la /// cornice. /// private void ApplySystemBarsAppearance() { if (Window is null) return; var controller = WindowCompat.GetInsetsController(Window, Window.DecorView); controller.AppearanceLightStatusBars = true; controller.AppearanceLightNavigationBars = true; } /// /// Il tasto indietro va intercettato sul dispatcher di AndroidX, non /// sovrascrivendo OnBackPressed: da AndroidX Activity 1.6 il gesto passa /// tutto di li' e il metodo dell'Activity non viene piu' invocato — la /// UI Blazor non riceveva mai il back e il lavoro non salvato del form /// se ne andava con la navigazione. /// /// Il callback resta acceso solo finche' c'e' davvero qualcuno in /// ascolto: quando la UI non ha nulla da dire, il back torna a /// comportarsi come sempre invece di essere inghiottito. /// private void HookBackButton() { _backButton = IPlatformApplication.Current?.Services.GetService(); if (_backButton is null) return; _backCallback = new BlazorBackCallback(_backButton); OnBackPressedDispatcher.AddCallback(this, _backCallback); _backCallback.Enabled = _backButton.HasHandlers; _backButton.HandlersChanged += OnBackHandlersChanged; } private void OnBackHandlersChanged(bool hasHandlers) { if (_backCallback is null) return; _backCallback.Enabled = hasHandlers; if (!hasHandlers) return; // Il dispatcher consulta i callback dall'ultimo registrato al primo, // e BlazorWebView registra il suo (che fa tornare indietro la // cronologia della WebView) quando l'handler viene creato, cioe' dopo // OnCreate. Ri-registrandoci nel momento in cui la UI ha davvero // qualcosa da dire torniamo in cima e il back arriva prima a noi. _backCallback.Remove(); OnBackPressedDispatcher.AddCallback(this, _backCallback); } /// /// Con la navigazione a tre tasti il back arriva come evento di tastiera /// e la WebView lo intercetta per conto suo, tornando indietro nella /// propria cronologia: ne' l'Activity ne' il dispatcher lo vedono mai. /// Qui la chiave passa prima della gerarchia di view, quindi e' l'unico /// punto in cui possiamo fermarla mentre un form ha lavoro non salvato. /// Il callback sul dispatcher resta per la navigazione a gesti, dove /// evento di tastiera non ce n'e'. /// public override bool DispatchKeyEvent(KeyEvent? e) { if (e?.KeyCode == Keycode.Back && _backButton?.HasHandlers == true) { // Si consuma anche il DOWN: lasciandolo passare la WebView // reagirebbe lo stesso. if (e.Action == KeyEventActions.Up) _backButton.TryHandleBackPressed(); return true; } return base.DispatchKeyEvent(e); } protected override void OnDestroy() { if (_backButton is not null) _backButton.HandlersChanged -= OnBackHandlersChanged; base.OnDestroy(); } private sealed class BlazorBackCallback(BackButtonService service) : AndroidX.Activity.OnBackPressedCallback(false) { public override void HandleOnBackPressed() => service.TryHandleBackPressed(); } private sealed class ImeInsetsListener : Java.Lang.Object, IOnApplyWindowInsetsListener { public WindowInsetsCompat OnApplyWindowInsets(Android.Views.View? v, WindowInsetsCompat? insets) { if (v is null || insets is null) return insets!; var ime = insets.GetInsets(WindowInsetsCompat.Type.Ime()).Bottom; var systemBars = insets.GetInsets(WindowInsetsCompat.Type.SystemBars()).Bottom; // La barra di navigazione e' gia' gestita da SafeAreaEdges in MainPage: // teniamo solo la parte di tastiera che la eccede, per non sommare due volte. var bottom = System.Math.Max(0, ime - systemBars); if (v.PaddingBottom != bottom) v.SetPadding(v.PaddingLeft, v.PaddingTop, v.PaddingRight, bottom); return insets; } } } }