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)
{