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
This commit is contained in:
Dinis Vieira 2023-10-01 15:35:04 +01:00
parent d17789d5ee
commit 8b7f9b9fb3
No known key found for this signature in database
GPG key ID: 9389160FF6C295F3
6 changed files with 153 additions and 20 deletions

View file

@ -2,9 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios</TargetFrameworks> <TargetFrameworks>net8.0-android;net8.0-ios</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> <!-- Uncomment to also build for Windows platform.-->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> --> <!--<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>-->
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>Bit.App</RootNamespace> <RootNamespace>Bit.App</RootNamespace>
<UseMaui>true</UseMaui> <UseMaui>true</UseMaui>
@ -35,6 +39,17 @@
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
</PropertyGroup>--> </PropertyGroup>-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-android|AnyCPU'">
<AndroidEnableMultiDex>True</AndroidEnableMultiDex>
<UseInterpreter>False</UseInterpreter>
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-android|AnyCPU'">
<AndroidEnableMultiDex>True</AndroidEnableMultiDex>
<UseInterpreter>False</UseInterpreter>
<DebugSymbols>False</DebugSymbols>
<RunAOTCompilation>False</RunAOTCompilation>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
<CreatePackage>false</CreatePackage> <CreatePackage>false</CreatePackage>
<RuntimeIdentifier>iossimulator-arm64</RuntimeIdentifier> <RuntimeIdentifier>iossimulator-arm64</RuntimeIdentifier>

View file

@ -29,6 +29,8 @@ namespace Bit.App
Bit.App.Handlers.StepperHandlerMappings.Setup(); Bit.App.Handlers.StepperHandlerMappings.Setup();
Bit.App.Handlers.TimePickerHandlerMappings.Setup(); Bit.App.Handlers.TimePickerHandlerMappings.Setup();
Bit.App.Handlers.ButtonHandlerMappings.Setup(); Bit.App.Handlers.ButtonHandlerMappings.Setup();
handlers.AddHandler(typeof(TabbedPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
#else #else
iOS.Core.Handlers.ButtonHandlerMappings.Setup(); iOS.Core.Handlers.ButtonHandlerMappings.Setup();
iOS.Core.Handlers.DatePickerHandlerMappings.Setup(); iOS.Core.Handlers.DatePickerHandlerMappings.Setup();

View file

@ -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<ILogger>("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);
}
}
}

View file

@ -338,7 +338,10 @@ namespace Bit.App.Pages
public string PlusAddressedEmail public string PlusAddressedEmail
{ {
get => _usernameOptions.PlusAddressedEmail; get
{
return _usernameOptions?.PlusAddressedEmail ?? string.Empty;
}
set set
{ {
if (_usernameOptions != null && _usernameOptions.PlusAddressedEmail != value) 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) private string GetUsernameTypeLabelDescription(UsernameType value)

View file

@ -1,13 +1,8 @@
using System; using Bit.App.Abstractions;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.ApplicationModel;
using Bit.App.Utilities; using Bit.App.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
@ -49,6 +44,8 @@ namespace Bit.App.Pages
{ {
get get
{ {
if (_items == null) { return string.Empty; }
if (_items.TryGetValue(_selectedKey, out var option)) if (_items.TryGetValue(_selectedKey, out var option))
{ {
return option; return option;

View file

@ -1,6 +1,4 @@
using System; using Bit.App.Effects;
using System.Threading.Tasks;
using Bit.App.Effects;
using Bit.App.Models; using Bit.App.Models;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.App.Utilities; using Bit.App.Utilities;
@ -8,8 +6,6 @@ using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -60,8 +56,7 @@ namespace Bit.App.Pages
}; };
Children.Add(settingsPage); 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 (DeviceInfo.Platform == DevicePlatform.Android)
if (Device.RuntimePlatform == Device.Android)
{ {
Effects.Add(new TabBarEffect()); Effects.Add(new TabBarEffect());
@ -93,7 +88,7 @@ namespace Bit.App.Pages
{ {
if (message.Command == "syncCompleted") if (message.Command == "syncCompleted")
{ {
Device.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync()); MainThread.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
} }
}); });
await UpdateVaultButtonTitleAsync(); await UpdateVaultButtonTitleAsync();
@ -131,7 +126,7 @@ namespace Bit.App.Pages
CurrentPage = _sendGroupingsPage; CurrentPage = _sendGroupingsPage;
} }
protected async override void OnCurrentPageChanged() protected override async void OnCurrentPageChanged()
{ {
if (CurrentPage is NavigationPage navPage) if (CurrentPage is NavigationPage navPage)
{ {