From 8b7f9b9fb3218f7010f12150d78fe3f6aaf47c68 Mon Sep 17 00:00:00 2001 From: Dinis Vieira Date: Sun, 1 Oct 2023 15:35:04 +0100 Subject: [PATCH] PM-3349: Android Added CustomTabbedPageHandler for Android to handle the tab "reselection" for PopToRoot. Commented support for Windows in App.csproj Disabled Interpreter on Android to avoid very slow app in Debug (during Login for example) Added some null checks that were causing crashes (on GeneratorPageVM and PickerVM) Minor TabsPage cleanup --- src/App/App.csproj | 21 ++- src/App/MauiProgram.cs | 2 + .../Handlers/CustomTabbedPageHandler.cs | 121 ++++++++++++++++++ .../Pages/Generator/GeneratorPageViewModel.cs | 7 +- src/Core/Pages/PickerViewModel.cs | 9 +- src/Core/Pages/TabsPage.cs | 13 +- 6 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 src/App/Platforms/Android/Handlers/CustomTabbedPageHandler.cs diff --git a/src/App/App.csproj b/src/App/App.csproj index 2a4e3bcae..5e0da5cbe 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -2,9 +2,13 @@ net8.0-android;net8.0-ios - $(TargetFrameworks);net8.0-windows10.0.19041.0 - - + + + + + + + Exe Bit.App true @@ -35,6 +39,17 @@ x64 --> + + True + False + True + + + True + False + False + False + false iossimulator-arm64 diff --git a/src/App/MauiProgram.cs b/src/App/MauiProgram.cs index b1ed570c7..708884c73 100644 --- a/src/App/MauiProgram.cs +++ b/src/App/MauiProgram.cs @@ -29,6 +29,8 @@ namespace Bit.App Bit.App.Handlers.StepperHandlerMappings.Setup(); Bit.App.Handlers.TimePickerHandlerMappings.Setup(); Bit.App.Handlers.ButtonHandlerMappings.Setup(); + + handlers.AddHandler(typeof(TabbedPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler)); #else iOS.Core.Handlers.ButtonHandlerMappings.Setup(); iOS.Core.Handlers.DatePickerHandlerMappings.Setup(); diff --git a/src/App/Platforms/Android/Handlers/CustomTabbedPageHandler.cs b/src/App/Platforms/Android/Handlers/CustomTabbedPageHandler.cs new file mode 100644 index 000000000..ddd72b638 --- /dev/null +++ b/src/App/Platforms/Android/Handlers/CustomTabbedPageHandler.cs @@ -0,0 +1,121 @@ +using AndroidX.AppCompat.View.Menu; +using Bit.Core.Abstractions; +using Bit.Core.Utilities; +using Google.Android.Material.BottomNavigation; +using Microsoft.Maui.Handlers; + +namespace Bit.App.Handlers +{ + public partial class CustomTabbedPageHandler : TabbedViewHandler + { + private TabbedPage _tabbedPage; + private BottomNavigationView _bottomNavigationView; + private Android.Views.ViewGroup _bottomNavigationViewGroup; + private ILogger _logger; + + protected override void ConnectHandler(global::Android.Views.View platformView) + { + _logger = ServiceContainer.Resolve("logger"); + + if(VirtualView is TabbedPage tabbedPage) + { + _tabbedPage = tabbedPage; + _tabbedPage.Loaded += TabbedPage_Loaded; + } + + base.ConnectHandler(platformView); + } + + private void TabbedPage_Loaded(object sender, EventArgs e) + { + try + { + //This layout should always be the same/fixed and therefore this should run with no issues. Nevertheless it's wrapped in try catch to avoid crashing in edge-case scenarios. + _bottomNavigationViewGroup = (((sender as VisualElement).Handler as IPlatformViewHandler) + .PlatformView + .Parent + .Parent as Android.Views.View) + .FindViewById(Microsoft.Maui.Controls.Resource.Id.navigationlayout_bottomtabs) as Android.Views.ViewGroup; + } + catch (Exception ex) + { + _logger.Exception(ex); + } + + if(_bottomNavigationViewGroup == null) { return; } + + //If TabbedPage still doesn't have items we set an event to wait for them + if (_bottomNavigationViewGroup.ChildCount == 0) + { + _bottomNavigationViewGroup.ChildViewAdded += View_ChildViewAdded; + } + else + { //If we already have items we can start listening immediately + var bottomTabs = _bottomNavigationViewGroup.GetChildAt(0); + ListenToItemReselected(bottomTabs); + } + } + + private void ListenToItemReselected(Android.Views.View bottomTabs) + { + if(bottomTabs is BottomNavigationView bottomNavigationView) + { + //If there was an older _bottomNavigationView for some reason we want to make sure to unregister + if(_bottomNavigationView != null) + { + _bottomNavigationView.ItemReselected -= BottomNavigationView_ItemReselected; + _bottomNavigationView = null; + } + + _bottomNavigationView = bottomNavigationView; + _bottomNavigationView.ItemReselected += BottomNavigationView_ItemReselected; + } + } + + private void View_ChildViewAdded(object sender, Android.Views.ViewGroup.ChildViewAddedEventArgs e) + { + //We shouldn't need this to be called anymore times so we can unregister to the events now + if(_bottomNavigationViewGroup != null) + { + _bottomNavigationViewGroup.ChildViewAdded -= View_ChildViewAdded; + } + + var bottomTabs = e.Child; + ListenToItemReselected(bottomTabs); + } + + private void BottomNavigationView_ItemReselected(object sender, Google.Android.Material.Navigation.NavigationBarView.ItemReselectedEventArgs e) + { + if(e.Item is MenuItemImpl item) + { + System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot."); + MainThread.BeginInvokeOnMainThread(async () => await _tabbedPage.CurrentPage.Navigation.PopToRootAsync()); + } + } + + protected override void DisconnectHandler(global::Android.Views.View platformView) + { + if(_bottomNavigationViewGroup != null) + { + _bottomNavigationViewGroup.ChildViewAdded -= View_ChildViewAdded; + _bottomNavigationViewGroup = null; + } + + if(_bottomNavigationView != null) + { + _bottomNavigationView.ItemReselected -= BottomNavigationView_ItemReselected; + _bottomNavigationView = null; + } + + if(_tabbedPage != null) + { + _tabbedPage.Loaded -= TabbedPage_Loaded; + _tabbedPage = null; + } + + _logger = null; + + base.DisconnectHandler(platformView); + } + } +} diff --git a/src/Core/Pages/Generator/GeneratorPageViewModel.cs b/src/Core/Pages/Generator/GeneratorPageViewModel.cs index 03f5fcbae..9600d32ff 100644 --- a/src/Core/Pages/Generator/GeneratorPageViewModel.cs +++ b/src/Core/Pages/Generator/GeneratorPageViewModel.cs @@ -338,7 +338,10 @@ namespace Bit.App.Pages public string PlusAddressedEmail { - get => _usernameOptions.PlusAddressedEmail; + get + { + return _usernameOptions?.PlusAddressedEmail ?? string.Empty; + } set { if (_usernameOptions != null && _usernameOptions.PlusAddressedEmail != value) @@ -850,7 +853,7 @@ namespace Bit.App.Pages } } - await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, message, AppResources.Ok)); + await MainThread.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, message, AppResources.Ok)); } private string GetUsernameTypeLabelDescription(UsernameType value) diff --git a/src/Core/Pages/PickerViewModel.cs b/src/Core/Pages/PickerViewModel.cs index 4050208b8..43168ecdc 100644 --- a/src/Core/Pages/PickerViewModel.cs +++ b/src/Core/Pages/PickerViewModel.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Bit.App.Abstractions; +using Bit.App.Abstractions; using Bit.Core.Resources.Localization; using Bit.Core.Abstractions; using Bit.Core.Utilities; -using Microsoft.Maui.ApplicationModel; using Bit.App.Utilities; namespace Bit.App.Pages @@ -49,6 +44,8 @@ namespace Bit.App.Pages { get { + if (_items == null) { return string.Empty; } + if (_items.TryGetValue(_selectedKey, out var option)) { return option; diff --git a/src/Core/Pages/TabsPage.cs b/src/Core/Pages/TabsPage.cs index 23bedb178..f14917a70 100644 --- a/src/Core/Pages/TabsPage.cs +++ b/src/Core/Pages/TabsPage.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using Bit.App.Effects; +using Bit.App.Effects; using Bit.App.Models; using Bit.Core.Resources.Localization; using Bit.App.Utilities; @@ -8,8 +6,6 @@ using Bit.Core; using Bit.Core.Abstractions; using Bit.Core.Models.Data; using Bit.Core.Utilities; -using Microsoft.Maui.Controls; -using Microsoft.Maui; namespace Bit.App.Pages { @@ -60,8 +56,7 @@ namespace Bit.App.Pages }; Children.Add(settingsPage); - // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes - if (Device.RuntimePlatform == Device.Android) + if (DeviceInfo.Platform == DevicePlatform.Android) { Effects.Add(new TabBarEffect()); @@ -93,7 +88,7 @@ namespace Bit.App.Pages { if (message.Command == "syncCompleted") { - Device.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync()); + MainThread.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync()); } }); await UpdateVaultButtonTitleAsync(); @@ -131,7 +126,7 @@ namespace Bit.App.Pages CurrentPage = _sendGroupingsPage; } - protected async override void OnCurrentPageChanged() + protected override async void OnCurrentPageChanged() { if (CurrentPage is NavigationPage navPage) {