Skip to content

Commit

Permalink
- make bottom tab smarter when removing/adding items
Browse files Browse the repository at this point in the history
  • Loading branch information
PureWeen committed Jan 27, 2023
1 parent 5d201a9 commit 75d2628
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@ protected virtual void OnMoreSheetDismissed(object sender, EventArgs e)
protected override void OnShellItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
base.OnShellItemsChanged(sender, e);

SetupMenu();
}

Expand Down Expand Up @@ -439,33 +438,34 @@ protected virtual void OnTabReselected(ShellSection shellSection)

protected virtual void SetupMenu(IMenu menu, int maxBottomItems, ShellItem shellItem)
{
if (_menuSetup &&
_updateMenuItemIcon != null &&
_updateMenuItemSource != null &&
_bottomView.IsAlive() &&
_bottomView?.IsAttachedToWindow == true)
if (_menuSetup)
{
var menuItem = _updateMenuItemIcon;
var shellSection = _updateMenuItemSource;
_updateMenuItemSource = null;
_updateMenuItemIcon = null;
if (_updateMenuItemIcon != null &&
_updateMenuItemSource != null &&
_bottomView.IsAlive() &&
_bottomView?.IsAttachedToWindow == true)
{
var menuItem = _updateMenuItemIcon;
var shellSection = _updateMenuItemSource;
_updateMenuItemSource = null;
_updateMenuItemIcon = null;

UpdateShellSectionIcon(shellSection, menuItem);
return;
}
else if (_menuSetup &&
_updateMenuItemTitle != null &&
_updateMenuItemSource != null &&
_bottomView.IsAlive() &&
_bottomView?.IsAttachedToWindow == true)
{
var menuItem = _updateMenuItemTitle;
var shellSection = _updateMenuItemSource;
_updateMenuItemSource = null;
_updateMenuItemIcon = null;
UpdateShellSectionIcon(shellSection, menuItem);
return;
}
else if (_updateMenuItemTitle != null &&
_updateMenuItemSource != null &&
_bottomView.IsAlive() &&
_bottomView?.IsAttachedToWindow == true)
{
var menuItem = _updateMenuItemTitle;
var shellSection = _updateMenuItemSource;
_updateMenuItemSource = null;
_updateMenuItemIcon = null;

UpdateShellSectionTitle(shellSection, menuItem);
return;
UpdateShellSectionTitle(shellSection, menuItem);
return;
}
}

if (DisplayedPage == null)
Expand All @@ -474,6 +474,7 @@ protected virtual void SetupMenu(IMenu menu, int maxBottomItems, ShellItem shell
if (ShellItemController.ShowTabs)
{
_menuSetup = true;

var currentIndex = ((IShellItemController)ShellItem).GetItems().IndexOf(ShellSection);
var items = CreateTabList(shellItem);

Expand All @@ -498,10 +499,7 @@ protected virtual void UpdateShellSectionIcon(ShellSection shellSection, IMenuIt

protected virtual void UpdateShellSectionTitle(ShellSection shellSection, IMenuItem menuItem)
{
using (var title = new Java.Lang.String(shellSection.Title))
{
menuItem.SetTitle(title);
}
BottomNavigationViewUtils.SetMenuItemTitle(menuItem, shellSection.Title);
}

protected virtual void UpdateShellSectionEnabled(ShellSection shellSection, IMenuItem menuItem)
Expand Down
60 changes: 49 additions & 11 deletions src/Controls/src/Core/Platform/Android/BottomNavigationViewUtils.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics.Drawables;
Expand Down Expand Up @@ -39,6 +40,31 @@ internal static void UpdateEnabled(bool tabEnabled, IMenuItem menuItem)
menuItem.SetEnabled(tabEnabled);
}

internal static Task SetupMenuItem(
(string title, ImageSource icon, bool tabEnabled) item,
IMenu menu,
int index,
int currentIndex,
BottomNavigationView bottomView,
IMauiContext mauiContext,
out IMenuItem menuItem)
{
Task returnValue;
using (var title = new Java.Lang.String(item.title))
{
menuItem = menu.Add(0, index, 0, title);
returnValue = SetMenuItemIcon(menuItem, item.icon, mauiContext);
UpdateEnabled(item.tabEnabled, menuItem);
if (index == currentIndex)
{
menuItem.SetChecked(true);
bottomView.SelectedItemId = index;
}
}

return returnValue;
}

internal static async void SetupMenu(
IMenu menu,
int maxBottomItems,
Expand All @@ -48,29 +74,33 @@ internal static async void SetupMenu(
IMauiContext mauiContext)
{
Context context = mauiContext.Context;
menu.Clear();

while (items.Count < menu.Size())
{
menu.RemoveItem(menu.GetItem(menu.Size() - 1).ItemId);
}

int numberOfMenuItems = items.Count;
bool showMore = numberOfMenuItems > maxBottomItems;
int end = showMore ? maxBottomItems - 1 : numberOfMenuItems;


List<IMenuItem> menuItems = new List<IMenuItem>();
List<Task> loadTasks = new List<Task>();
for (int i = 0; i < end; i++)
{
var item = items[i];
using (var title = new Java.Lang.String(item.title))

IMenuItem menuItem;
if (i >= menu.Size())
loadTasks.Add(SetupMenuItem(item, menu, i, currentIndex, bottomView, mauiContext, out menuItem));
else
{
var menuItem = menu.Add(0, i, 0, title);
menuItems.Add(menuItem);
menuItem = menu.GetItem(i);
SetMenuItemTitle(menuItem, item.title);
loadTasks.Add(SetMenuItemIcon(menuItem, item.icon, mauiContext));
UpdateEnabled(item.tabEnabled, menuItem);
if (i == currentIndex)
{
menuItem.SetChecked(true);
bottomView.SelectedItemId = i;
}
}

menuItems.Add(menuItem);
}

if (showMore)
Expand All @@ -93,6 +123,14 @@ internal static async void SetupMenu(
menuItem.Dispose();
}

internal static void SetMenuItemTitle(IMenuItem menuItem, string title)
{
using (var jTitle = new Java.Lang.String(title))
{
menuItem.SetTitle(jTitle);
}
}

internal static async Task SetMenuItemIcon(IMenuItem menuItem, ImageSource source, IMauiContext context)
{
if (!menuItem.IsAlive())
Expand Down
107 changes: 106 additions & 1 deletion src/Controls/tests/DeviceTests/Elements/Shell/ShellTests.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using AndroidX.DrawerLayout.Widget;
using AndroidX.ViewPager2.Widget;
using Google.Android.Material.AppBar;
using Google.Android.Material.BottomNavigation;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Handlers.Compatibility;
using Microsoft.Maui.Controls.Platform;
Expand Down Expand Up @@ -222,7 +223,6 @@ await CreateHandlerAndAddToWindow<ShellRenderer>(shell, async (handler) =>
});
}


[Fact]
public async Task SwappingOutAndroidContextDoesntCrash()
{
Expand Down Expand Up @@ -260,6 +260,111 @@ await CreateHandlerAndAddToWindow<IWindowHandler>(window, async (handler) =>
}, mauiContextStub2);
}

[Fact]
public async Task ChangingBottomTabAttributesDoesntRecreateBottomTabs()
{
SetupBuilder();

var shell = await CreateShellAsync(shell =>
{
shell.Items.Add(new Tab() { Items = { new ContentPage() }, Title = "Tab 1", Icon = "red.png" });
shell.Items.Add(new Tab() { Items = { new ContentPage() }, Title = "Tab 2", Icon = "red.png" });
});

await CreateHandlerAndAddToWindow<ShellRenderer>(shell, async (handler) =>
{
var menu = GetDrawerLayout(handler).GetFirstChildOfType<BottomNavigationView>().Menu;
var menuItem1 = menu.GetItem(0);
var menuItem2 = menu.GetItem(1);
var icon1 = menuItem1.Icon;
var icon2 = menuItem2.Icon;
var title1 = menuItem1.TitleFormatted;
var title2 = menuItem2.TitleFormatted;

shell.CurrentItem.Items[0].Title = "new Title 1";
shell.CurrentItem.Items[0].Icon = "blue.png";

shell.CurrentItem.Items[1].Title = "new Title 2";
shell.CurrentItem.Items[1].Icon = "blue.png";

// let the icon and title propagate
await AssertionExtensions.Wait(() => menuItem1.Icon != icon1);

menu = GetDrawerLayout(handler).GetFirstChildOfType<BottomNavigationView>().Menu;
Assert.Equal(menuItem1, menu.GetItem(0));
Assert.Equal(menuItem2, menu.GetItem(1));

menuItem1.Icon.AssertColorAtCenter(Android.Graphics.Color.Blue);
menuItem2.Icon.AssertColorAtCenter(Android.Graphics.Color.Blue);

Assert.NotEqual(icon1, menuItem1.Icon);
Assert.NotEqual(icon2, menuItem2.Icon);
Assert.NotEqual(title1, menuItem1.TitleFormatted);
Assert.NotEqual(title2, menuItem2.TitleFormatted);
});
}

[Fact]
public async Task RemovingBottomTabDoesntRecreateMenu()
{
SetupBuilder();

var shell = await CreateShellAsync(shell =>
{
shell.Items.Add(new Tab() { Items = { new ContentPage() }, Title = "Tab 1", Icon = "red.png" });
shell.Items.Add(new Tab() { Items = { new ContentPage() }, Title = "Tab 2", Icon = "red.png" });
shell.Items.Add(new Tab() { Items = { new ContentPage() }, Title = "Tab 3", Icon = "red.png" });
});

await CreateHandlerAndAddToWindow<ShellRenderer>(shell, async (handler) =>
{
var bottomView = GetDrawerLayout(handler).GetFirstChildOfType<BottomNavigationView>();
var menu = bottomView.Menu;
var menuItem1 = menu.GetItem(0);
var menuItem2 = menu.GetItem(1);

shell.CurrentItem.Items.RemoveAt(2);

// let the change propagate
await AssertionExtensions.Wait(() => bottomView.Menu.Size() == 2);

menu = bottomView.Menu;
Assert.Equal(menuItem1, menu.GetItem(0));
Assert.Equal(menuItem2, menu.GetItem(1));
});
}

[Fact]
public async Task AddingBottomTabDoesntRecreateMenu()
{
SetupBuilder();

var shell = await CreateShellAsync(shell =>
{
shell.Items.Add(new Tab() { Items = { new ContentPage() }, Title = "Tab 1", Icon = "red.png" });
shell.Items.Add(new Tab() { Items = { new ContentPage() }, Title = "Tab 3", Icon = "red.png" });
});

await CreateHandlerAndAddToWindow<ShellRenderer>(shell, async (handler) =>
{
var bottomView = GetDrawerLayout(handler).GetFirstChildOfType<BottomNavigationView>();
var menu = bottomView.Menu;
var menuItem1 = menu.GetItem(0);
var menuItem2 = menu.GetItem(1);
var menuItem2Icon = menuItem2.Icon;

shell.CurrentItem.Items.Insert(1, new Tab() { Items = { new ContentPage() }, Title = "Tab 2", Icon = "purple.png" });

// let the change propagate
await AssertionExtensions.Wait(() => bottomView.Menu.GetItem(1).Icon != menuItem2Icon);

menu = bottomView.Menu;

menu.GetItem(1).Icon.AssertColorAtCenter(Android.Graphics.Color.Purple);
menu.GetItem(2).Icon.AssertColorAtCenter(Android.Graphics.Color.Red);
});
}

protected AView GetFlyoutPlatformView(ShellRenderer shellRenderer)
{
var drawerLayout = GetDrawerLayout(shellRenderer);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AndroidX.CoordinatorLayout.Widget;
using Google.Android.Material.BottomNavigation;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
[Category(TestCategory.TabbedPage)]
public partial class TabbedPageTests : ControlsHandlerTestBase
{
[Fact]
public async Task ChangingBottomTabAttributesDoesntRecreateBottomTabs()
{
SetupBuilder();

var tabbedPage = CreateBasicTabbedPage(true, pages: new[]
{
new ContentPage() { Title = "Tab 1", IconImageSource = "red.png" },
new ContentPage() { Title = "Tab 2", IconImageSource = "red.png" }
});

await CreateHandlerAndAddToWindow<TabbedViewHandler>(tabbedPage, async (handler) =>
{
var menu = GetBottomNavigationView(handler).Menu;
var menuItem1 = menu.GetItem(0);
var menuItem2 = menu.GetItem(1);
var icon1 = menuItem1.Icon;
var icon2 = menuItem2.Icon;
var title1 = menuItem1.TitleFormatted;
var title2 = menuItem2.TitleFormatted;

tabbedPage.Children[0].Title = "new Title 1";
tabbedPage.Children[0].IconImageSource = "blue.png";

tabbedPage.Children[1].Title = "new Title 2";
tabbedPage.Children[1].IconImageSource = "blue.png";

// let the icon and title propagate
await AssertionExtensions.Wait(() => menuItem1.Icon != icon1);

menu = GetBottomNavigationView(handler).Menu;
Assert.Equal(menuItem1, menu.GetItem(0));
Assert.Equal(menuItem2, menu.GetItem(1));

menuItem1.Icon.AssertColorAtCenter(Android.Graphics.Color.Blue);
menuItem2.Icon.AssertColorAtCenter(Android.Graphics.Color.Blue);

Assert.NotEqual(icon1, menuItem1.Icon);
Assert.NotEqual(icon2, menuItem2.Icon);
Assert.NotEqual(title1, menuItem1.TitleFormatted);
Assert.NotEqual(title2, menuItem2.TitleFormatted);
});
}


BottomNavigationView GetBottomNavigationView(TabbedViewHandler tabViewHandler)
{
var layout = (tabViewHandler.PlatformView as Android.Views.IViewParent).FindParent((view) => view is CoordinatorLayout)
as CoordinatorLayout;

return layout.GetFirstChildOfType<BottomNavigationView>();
}
}
}

0 comments on commit 75d2628

Please sign in to comment.