diff --git a/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs b/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs index 59f72a77bf59..b5c87068545e 100644 --- a/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs +++ b/src/Controls/src/Core/Platform/Android/TabbedPageManager.cs @@ -29,1020 +29,1028 @@ using AView = Android.Views.View; using Color = Microsoft.Maui.Graphics.Color; -namespace Microsoft.Maui.Controls.Handlers +namespace Microsoft.Maui.Controls.Handlers; + +public class TabbedPageManager { - internal class TabbedPageManager + Fragment _tabLayoutFragment; + ColorStateList _originalTabTextColors; + ColorStateList _orignalTabIconColors; + ColorStateList _newTabTextColors; + ColorStateList _newTabIconColors; + FragmentManager _fragmentManager; + TabLayout _tabLayout; + BottomNavigationView _bottomNavigationView; + ViewPager2 _viewPager; + protected Page previousPage; + int[] _checkedStateSet = null; + int[] _selectedStateSet = null; + int[] _emptyStateSet = null; + int _defaultARGBColor = Colors.Transparent.ToPlatform().ToArgb(); + AColor _defaultAndroidColor = Colors.Transparent.ToPlatform(); + readonly IMauiContext _context; + readonly Listeners _listeners; + protected TabbedPage Element { get; set; } + public TabLayout TabLayout => _tabLayout; + public BottomNavigationView BottomNavigationView => _bottomNavigationView; + public ViewPager2 ViewPager => _viewPager; + int _tabplacementId; + Brush _currentBarBackground; + Color _currentBarItemColor; + Color _currentBarTextColor; + Color _currentBarSelectedItemColor; + ColorStateList _currentBarTextColorStateList; + bool _tabItemStyleLoaded; + TabLayoutMediator _tabLayoutMediator; + IDisposable _pendingFragment; + + protected NavigationRootManager NavigationRootManager { get; } + public static bool IsDarkTheme => (Application.Current?.RequestedTheme ?? AppInfo.RequestedTheme) == AppTheme.Dark; + + public TabbedPageManager(IMauiContext context) { - Fragment _tabLayoutFragment; - ColorStateList _originalTabTextColors; - ColorStateList _orignalTabIconColors; - ColorStateList _newTabTextColors; - ColorStateList _newTabIconColors; - FragmentManager _fragmentManager; - TabLayout _tabLayout; - BottomNavigationView _bottomNavigationView; - ViewPager2 _viewPager; - Page _previousPage; - int[] _checkedStateSet = null; - int[] _selectedStateSet = null; - int[] _emptyStateSet = null; - int _defaultARGBColor = Colors.Transparent.ToPlatform().ToArgb(); - AColor _defaultAndroidColor = Colors.Transparent.ToPlatform(); - readonly IMauiContext _context; - readonly Listeners _listeners; - TabbedPage Element { get; set; } - internal TabLayout TabLayout => _tabLayout; - internal BottomNavigationView BottomNavigationView => _bottomNavigationView; - internal ViewPager2 ViewPager => _viewPager; - int _tabplacementId; - Brush _currentBarBackground; - Color _currentBarItemColor; - Color _currentBarTextColor; - Color _currentBarSelectedItemColor; - ColorStateList _currentBarTextColorStateList; - bool _tabItemStyleLoaded; - TabLayoutMediator _tabLayoutMediator; - IDisposable _pendingFragment; - - NavigationRootManager NavigationRootManager { get; } - internal static bool IsDarkTheme => ((Application.Current?.RequestedTheme ?? AppInfo.RequestedTheme) == AppTheme.Dark); - - public TabbedPageManager(IMauiContext context) + _context = context; + _listeners = new Listeners(this); + _viewPager = new ViewPager2(context.Context) { - _context = context; - _listeners = new Listeners(this); - _viewPager = new ViewPager2(context.Context) - { - OverScrollMode = OverScrollMode.Never, - LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent) - }; + OverScrollMode = OverScrollMode.Never, + LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent) + }; - _viewPager.RegisterOnPageChangeCallback(_listeners); - } + _viewPager.RegisterOnPageChangeCallback(_listeners); + } - internal IMauiContext MauiContext => _context; - FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = _context.GetFragmentManager()); - public bool IsBottomTabPlacement => (Element != null) ? Element.OnThisPlatform().GetToolbarPlacement() == ToolbarPlacement.Bottom : false; + internal IMauiContext MauiContext => _context; + protected FragmentManager FragmentManager => _fragmentManager ??= _context.GetFragmentManager(); + public bool IsBottomTabPlacement => (Element != null) ? Element.OnThisPlatform().GetToolbarPlacement() == ToolbarPlacement.Bottom : false; - public Color BarItemColor + public Color BarItemColor + { + get { - get + if (Element != null) { - if (Element != null) - { - if (Element.IsSet(TabbedPage.UnselectedTabColorProperty)) - return Element.UnselectedTabColor; - } - - return null; + if (Element.IsSet(TabbedPage.UnselectedTabColorProperty)) + return Element.UnselectedTabColor; } + + return null; } + } - public Color BarSelectedItemColor + public Color BarSelectedItemColor + { + get { - get + if (Element != null) { - if (Element != null) - { - if (Element.IsSet(TabbedPage.SelectedTabColorProperty)) - return Element.SelectedTabColor; - } - - return null; + if (Element.IsSet(TabbedPage.SelectedTabColorProperty)) + return Element.SelectedTabColor; } + + return null; } + } + + + public virtual void SetElement(TabbedPage tabbedPage) + { + var activity = _context.GetActivity(); + var themeContext = activity; - internal void SetElement(TabbedPage tabbedPage) + if (Element is not null) { - var activity = _context.GetActivity(); - var themeContext = activity; + Element.InternalChildren.ForEach(page => TeardownPage(page as Page)); + ((IPageController)Element).InternalChildren.CollectionChanged -= OnChildrenCollectionChanged; + Element.Appearing -= OnTabbedPageAppearing; + Element.Disappearing -= OnTabbedPageDisappearing; + RemoveTabs(); + _viewPager.LayoutChange -= OnLayoutChanged; + _viewPager.Adapter = null; + } - if (Element is not null) - { - Element.InternalChildren.ForEach(page => TeardownPage(page as Page)); - ((IPageController)Element).InternalChildren.CollectionChanged -= OnChildrenCollectionChanged; - Element.Appearing -= OnTabbedPageAppearing; - Element.Disappearing -= OnTabbedPageDisappearing; - RemoveTabs(); - _viewPager.LayoutChange -= OnLayoutChanged; - _viewPager.Adapter = null; - } + Element = tabbedPage; + if (Element is not null) + { + _viewPager.LayoutChange += OnLayoutChanged; + Element.Appearing += OnTabbedPageAppearing; + Element.Disappearing += OnTabbedPageDisappearing; + _viewPager.Adapter = new MultiPageFragmentStateAdapter(tabbedPage, FragmentManager, _context) { CountOverride = tabbedPage.Children.Count }; - Element = tabbedPage; - if (Element is not null) + if (IsBottomTabPlacement) { - _viewPager.LayoutChange += OnLayoutChanged; - Element.Appearing += OnTabbedPageAppearing; - Element.Disappearing += OnTabbedPageDisappearing; - _viewPager.Adapter = new MultiPageFragmentStateAdapter(tabbedPage, FragmentManager, _context) { CountOverride = tabbedPage.Children.Count }; - - if (IsBottomTabPlacement) + _bottomNavigationView = new BottomNavigationView(_context.Context) { - _bottomNavigationView = new BottomNavigationView(_context.Context) + LayoutParameters = new CoordinatorLayout.LayoutParams(AppBarLayout.LayoutParams.MatchParent, AppBarLayout.LayoutParams.WrapContent) { - LayoutParameters = new CoordinatorLayout.LayoutParams(AppBarLayout.LayoutParams.MatchParent, AppBarLayout.LayoutParams.WrapContent) - { - Gravity = (int)GravityFlags.Bottom - } - }; - } - else + Gravity = (int)GravityFlags.Bottom + } + }; + } + else + { + if (_tabLayout == null) { - if (_tabLayout == null) + var layoutInflater = Element.Handler.MauiContext.GetLayoutInflater(); + _tabLayout = new TabLayout(_context.Context) { - var layoutInflater = Element.Handler.MauiContext.GetLayoutInflater(); - _tabLayout = new TabLayout(_context.Context) - { - TabMode = TabLayout.ModeFixed, - TabGravity = TabLayout.GravityFill, - LayoutParameters = new AppBarLayout.LayoutParams(AppBarLayout.LayoutParams.MatchParent, AppBarLayout.LayoutParams.WrapContent) - }; - } + TabMode = TabLayout.ModeFixed, + TabGravity = TabLayout.GravityFill, + LayoutParameters = new AppBarLayout.LayoutParams(AppBarLayout.LayoutParams.MatchParent, AppBarLayout.LayoutParams.WrapContent) + }; } + } - OnChildrenCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + OnChildrenCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - ScrollToCurrentPage(); + ScrollToCurrentPage(); - _previousPage = tabbedPage.CurrentPage; + previousPage = tabbedPage.CurrentPage; - ((IPageController)tabbedPage).InternalChildren.CollectionChanged += OnChildrenCollectionChanged; + ((IPageController)tabbedPage).InternalChildren.CollectionChanged += OnChildrenCollectionChanged; - SetTabLayout(); - } + SetTabLayout(); } + } - void OnLayoutChanged(object sender, AView.LayoutChangeEventArgs e) - { - Element.Arrange(e); - } + protected virtual void OnLayoutChanged(object sender, AView.LayoutChangeEventArgs e) + { + Element.Arrange(e); + } - void RemoveTabs() + void RemoveTabs() + { + _pendingFragment?.Dispose(); + _pendingFragment = null; + + if (_tabLayoutFragment != null) { - _pendingFragment?.Dispose(); - _pendingFragment = null; + var fragment = _tabLayoutFragment; + _tabLayoutFragment = null; - if (_tabLayoutFragment != null) - { - var fragment = _tabLayoutFragment; - _tabLayoutFragment = null; + var fragmentManager = + _context + .GetNavigationRootManager() + .FragmentManager; - var fragmentManager = - _context - .GetNavigationRootManager() - .FragmentManager; + if (!fragmentManager.IsDestroyed(_context?.Context)) + { + SetContentBottomMargin(0); - if (!fragmentManager.IsDestroyed(_context?.Context)) + if (_context?.Context is Context c) { - SetContentBottomMargin(0); - - if (_context?.Context is Context c) - { - _pendingFragment = - fragmentManager - .RunOrWaitForResume(c, fm => - { - fm - .BeginTransaction() - .Remove(fragment) - .SetReorderingAllowed(true) - .Commit(); - }); - } + _pendingFragment = + fragmentManager + .RunOrWaitForResume(c, fm => + { + fm + .BeginTransaction() + .Remove(fragment) + .SetReorderingAllowed(true) + .Commit(); + }); } - - _tabplacementId = 0; } - } - void OnTabbedPageDisappearing(object sender, EventArgs e) - { - RemoveTabs(); + _tabplacementId = 0; } + } + + protected virtual void OnTabbedPageDisappearing(object sender, EventArgs e) + { + RemoveTabs(); + } - void OnTabbedPageAppearing(object sender, EventArgs e) + protected virtual void OnTabbedPageAppearing(object sender, EventArgs e) + { + SetTabLayout(); + } + + protected virtual void RootViewChanged(object sender, EventArgs e) + { + if (sender is NavigationRootManager rootManager) { + rootManager.RootViewChanged -= RootViewChanged; SetTabLayout(); } + } - void RootViewChanged(object sender, EventArgs e) + internal void SetTabLayout() + { + _pendingFragment?.Dispose(); + _pendingFragment = null; + + int id; + var rootManager = + _context.GetNavigationRootManager(); + + _tabItemStyleLoaded = false; + if (rootManager.RootView == null) { - if (sender is NavigationRootManager rootManager) - { - rootManager.RootViewChanged -= RootViewChanged; - SetTabLayout(); - } + rootManager.RootViewChanged += RootViewChanged; + return; } - internal void SetTabLayout() + if (IsBottomTabPlacement) { - _pendingFragment?.Dispose(); - _pendingFragment = null; - - int id; - var rootManager = - _context.GetNavigationRootManager(); - - _tabItemStyleLoaded = false; - if (rootManager.RootView == null) - { - rootManager.RootViewChanged += RootViewChanged; + id = Resource.Id.navigationlayout_bottomtabs; + if (_tabplacementId == id) return; - } - - if (IsBottomTabPlacement) - { - id = Resource.Id.navigationlayout_bottomtabs; - if (_tabplacementId == id) - return; - SetContentBottomMargin(_context.Context.Resources.GetDimensionPixelSize(Resource.Dimension.design_bottom_navigation_height)); - } - else - { - id = Resource.Id.navigationlayout_toptabs; - if (_tabplacementId == id) - return; + SetContentBottomMargin(_context.Context.Resources.GetDimensionPixelSize(Resource.Dimension.design_bottom_navigation_height)); + } + else + { + id = Resource.Id.navigationlayout_toptabs; + if (_tabplacementId == id) + return; - SetContentBottomMargin(0); - } + SetContentBottomMargin(0); + } - if (_context?.Context is Context c) - { - _pendingFragment = - rootManager - .FragmentManager - .RunOrWaitForResume(c, fm => + if (_context?.Context is Context c) + { + _pendingFragment = + rootManager + .FragmentManager + .RunOrWaitForResume(c, fm => + { + if (IsBottomTabPlacement) { - if (IsBottomTabPlacement) - { - _tabLayoutFragment = new ViewFragment(BottomNavigationView); - } - else - { - _tabLayoutFragment = new ViewFragment(TabLayout); - } + _tabLayoutFragment = new ViewFragment(BottomNavigationView); + } + else + { + _tabLayoutFragment = new ViewFragment(TabLayout); + } - _tabplacementId = id; + _tabplacementId = id; - fm - .BeginTransactionEx() - .ReplaceEx(id, _tabLayoutFragment) - .SetReorderingAllowed(true) - .Commit(); - }); - } + fm + .BeginTransactionEx() + .ReplaceEx(id, _tabLayoutFragment) + .SetReorderingAllowed(true) + .Commit(); + }); + } + } + + void SetContentBottomMargin(int bottomMargin) + { + var rootManager = _context.GetNavigationRootManager(); + var layoutContent = rootManager.RootView?.FindViewById(Resource.Id.navigationlayout_content); + if (layoutContent != null && layoutContent.LayoutParameters is ViewGroup.MarginLayoutParams cl) + { + cl.BottomMargin = bottomMargin; } + } + + protected virtual void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + e.Apply((o, i, c) => SetupPage((Page)o), (o, i) => TeardownPage((Page)o), Reset); + + ViewPager2 pager = _viewPager; - void SetContentBottomMargin(int bottomMargin) + if (pager.Adapter is MultiPageFragmentStateAdapter adapter) { - var rootManager = _context.GetNavigationRootManager(); - var layoutContent = rootManager.RootView?.FindViewById(Resource.Id.navigationlayout_content); - if (layoutContent != null && layoutContent.LayoutParameters is ViewGroup.MarginLayoutParams cl) - { - cl.BottomMargin = bottomMargin; - } + adapter.CountOverride = Element.Children.Count; } - void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + if (IsBottomTabPlacement) { - e.Apply((o, i, c) => SetupPage((Page)o), (o, i) => TeardownPage((Page)o), Reset); + BottomNavigationView bottomNavigationView = _bottomNavigationView; - ViewPager2 pager = _viewPager; + NotifyDataSetChanged(); - if (pager.Adapter is MultiPageFragmentStateAdapter adapter) + if (Element.Children.Count == 0) { - adapter.CountOverride = Element.Children.Count; + bottomNavigationView.Menu.Clear(); } - - if (IsBottomTabPlacement) + else { - BottomNavigationView bottomNavigationView = _bottomNavigationView; - - NotifyDataSetChanged(); + SetupBottomNavigationView(); + bottomNavigationView.SetOnItemSelectedListener(_listeners); + } - if (Element.Children.Count == 0) - { - bottomNavigationView.Menu.Clear(); - } - else - { - SetupBottomNavigationView(); - bottomNavigationView.SetOnItemSelectedListener(_listeners); - } + UpdateIgnoreContainerAreas(); + } + else + { + TabLayout tabs = _tabLayout; - UpdateIgnoreContainerAreas(); + NotifyDataSetChanged(); + if (Element.Children.Count == 0) + { + tabs.RemoveAllTabs(); + tabs.SetupWithViewPager(null); + _tabLayoutMediator?.Detach(); + _tabLayoutMediator = null; } else { - TabLayout tabs = _tabLayout; - - NotifyDataSetChanged(); - if (Element.Children.Count == 0) + if (_tabLayoutMediator == null) { - tabs.RemoveAllTabs(); - tabs.SetupWithViewPager(null); - _tabLayoutMediator?.Detach(); - _tabLayoutMediator = null; + _tabLayoutMediator = new TabLayoutMediator(tabs, _viewPager, _listeners); + _tabLayoutMediator.Attach(); } - else - { - if (_tabLayoutMediator == null) - { - _tabLayoutMediator = new TabLayoutMediator(tabs, _viewPager, _listeners); - _tabLayoutMediator.Attach(); - } - UpdateTabIcons(); + UpdateTabIcons(); #pragma warning disable CS0618 // Type or member is obsolete - tabs.AddOnTabSelectedListener(_listeners); + tabs.AddOnTabSelectedListener(_listeners); #pragma warning restore CS0618 // Type or member is obsolete - } - - UpdateIgnoreContainerAreas(); } + + UpdateIgnoreContainerAreas(); } + } - void NotifyDataSetChanged() + protected void NotifyDataSetChanged() + { + var adapter = _viewPager?.Adapter; + if (adapter is not null) { - var adapter = _viewPager?.Adapter; - if (adapter is not null) - { - var currentIndex = Element.Children.IndexOf(Element.CurrentPage); + var currentIndex = Element.Children.IndexOf(Element.CurrentPage); - // If the modification to the backing collection has changed the position of the current item - // then we need to update the viewpager so it remains selected - if (_viewPager.CurrentItem != currentIndex && currentIndex < Element.Children.Count && currentIndex >= 0) - _viewPager.SetCurrentItem(currentIndex, false); + // If the modification to the backing collection has changed the position of the current item + // then we need to update the viewpager so it remains selected + if (_viewPager.CurrentItem != currentIndex && currentIndex < Element.Children.Count && currentIndex >= 0) + _viewPager.SetCurrentItem(currentIndex, false); - adapter.NotifyDataSetChanged(); - } + adapter.NotifyDataSetChanged(); } + } - void TabSelected(TabLayout.Tab tab) - { - if (Element == null) - return; + protected virtual void TabSelected(TabLayout.Tab tab) + { + if (Element == null) + return; - int selectedIndex = tab.Position; - if (Element.Children.Count > selectedIndex && selectedIndex >= 0) - Element.CurrentPage = Element.Children[selectedIndex]; + int selectedIndex = tab.Position; + if (Element.Children.Count > selectedIndex && selectedIndex >= 0) + Element.CurrentPage = Element.Children[selectedIndex]; - SetIconColorFilter(Element.CurrentPage, tab, true); - } + SetIconColorFilter(Element.CurrentPage, tab, true); + } - void TeardownPage(Page page) - { - page.PropertyChanged -= OnPagePropertyChanged; - } + void TeardownPage(Page page) + { + page.PropertyChanged -= OnPagePropertyChanged; + } - void SetupPage(Page page) - { - page.PropertyChanged += OnPagePropertyChanged; - } + void SetupPage(Page page) + { + page.PropertyChanged += OnPagePropertyChanged; + } + + void Reset() + { + foreach (var page in Element.Children) + SetupPage(page); + } - void Reset() + protected virtual void OnPagePropertyChanged(Page page, PropertyChangedEventArgs e) + { + if (e.PropertyName == Page.TitleProperty.PropertyName) { - foreach (var page in Element.Children) - SetupPage(page); - } + var index = Element.Children.IndexOf(page); - void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) + if (IsBottomTabPlacement) + { + IMenuItem tab = _bottomNavigationView.Menu.GetItem(index); + tab.SetTitle(page.Title); + } + else + { + TabLayout.Tab tab = _tabLayout.GetTabAt(index); + tab.SetText(page.Title); + } + } + else if (e.PropertyName == Page.IconImageSourceProperty.PropertyName) { - if (e.PropertyName == Page.TitleProperty.PropertyName) + var index = Element.Children.IndexOf(page); + if (IsBottomTabPlacement) { - var page = (Page)sender; - var index = Element.Children.IndexOf(page); - - if (IsBottomTabPlacement) - { - IMenuItem tab = _bottomNavigationView.Menu.GetItem(index); - tab.SetTitle(page.Title); - } - else - { - TabLayout.Tab tab = _tabLayout.GetTabAt(index); - tab.SetText(page.Title); - } + var menuItem = _bottomNavigationView.Menu.GetItem(index); + page.IconImageSource.LoadImage( + _context, + result => + { + menuItem.SetIcon(result.Value); + }); + SetupBottomNavigationViewIconColor(page, menuItem, index); } - else if (e.PropertyName == Page.IconImageSourceProperty.PropertyName) + else { - var page = (Page)sender; - var index = Element.Children.IndexOf(page); - if (IsBottomTabPlacement) - { - var menuItem = _bottomNavigationView.Menu.GetItem(index); - page.IconImageSource.LoadImage( - _context, - result => - { - menuItem.SetIcon(result.Value); - }); - SetupBottomNavigationViewIconColor(page, menuItem, index); - } - else - { - TabLayout.Tab tab = _tabLayout.GetTabAt(index); - SetTabIconImageSource(page, tab); - } + TabLayout.Tab tab = _tabLayout.GetTabAt(index); + SetTabIconImageSource(page, tab); } } + } - internal void ScrollToCurrentPage() - { - if (Element.CurrentPage == null) - return; + void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) + { + OnPagePropertyChanged((Page)sender, e); + } - // TODO MAUI - //if (Platform != null) - //{ - // Platform.NavAnimationInProgress = true; - //} + internal void ScrollToCurrentPage() + { + if (Element.CurrentPage == null) + return; - _viewPager.SetCurrentItem(Element.Children.IndexOf(Element.CurrentPage), Element.OnThisPlatform().IsSmoothScrollEnabled()); + // TODO MAUI + //if (Platform != null) + //{ + // Platform.NavAnimationInProgress = true; + //} - //if (Platform != null) - //{ - // Platform.NavAnimationInProgress = false; - //} - } + _viewPager.SetCurrentItem(Element.Children.IndexOf(Element.CurrentPage), Element.OnThisPlatform().IsSmoothScrollEnabled()); - void UpdateIgnoreContainerAreas() - { - foreach (IPageController child in Element.Children) - child.IgnoresContainerArea = child is NavigationPage; - } + //if (Platform != null) + //{ + // Platform.NavAnimationInProgress = false; + //} + } + + void UpdateIgnoreContainerAreas() + { + foreach (IPageController child in Element.Children) + child.IgnoresContainerArea = child is NavigationPage; + } + + void UpdateOffscreenPageLimit() + { + _viewPager.OffscreenPageLimit = Element.OnThisPlatform().OffscreenPageLimit(); + } + + internal void UpdateSwipePaging() + { + _viewPager.UserInputEnabled = Element.OnThisPlatform().IsSwipePagingEnabled(); + } - void UpdateOffscreenPageLimit() + List<(string title, ImageSource icon, bool tabEnabled)> CreateTabList() + { + var items = new List<(string title, ImageSource icon, bool tabEnabled)>(); + + for (int i = 0; i < Element.Children.Count; i++) { - _viewPager.OffscreenPageLimit = Element.OnThisPlatform().OffscreenPageLimit(); + var item = Element.Children[i]; + items.Add((item.Title, item.IconImageSource, item.IsEnabled)); } - internal void UpdateSwipePaging() + return items; + } + + protected virtual void SetupBottomNavigationView() + { + var currentIndex = Element.Children.IndexOf(Element.CurrentPage); + var items = CreateTabList(); + + BottomNavigationViewUtils.SetupMenu( + _bottomNavigationView.Menu, + _bottomNavigationView.MaxItemCount, + items, + currentIndex, + _bottomNavigationView, + Element.FindMauiContext()); + + if (Element.CurrentPage == null && Element.Children.Count > 0) + Element.CurrentPage = Element.Children[0]; + } + + protected virtual void UpdateTabIcons() + { + TabLayout tabs = _tabLayout; + + if (tabs.TabCount != Element.Children.Count) + return; + + for (var i = 0; i < Element.Children.Count; i++) { - _viewPager.UserInputEnabled = Element.OnThisPlatform().IsSwipePagingEnabled(); + Page child = Element.Children[i]; + TabLayout.Tab tab = tabs.GetTabAt(i); + SetTabIconImageSource(child, tab); } + } - List<(string title, ImageSource icon, bool tabEnabled)> CreateTabList() - { - var items = new List<(string title, ImageSource icon, bool tabEnabled)>(); + protected virtual void SetTabIconImageSource(Page page, TabLayout.Tab tab, Drawable icon) + { + tab.SetIcon(icon); + SetIconColorFilter(page, tab); + } - for (int i = 0; i < Element.Children.Count; i++) + void SetTabIconImageSource(Page page, TabLayout.Tab tab) + { + page.IconImageSource.LoadImage( + _context, + result => { - var item = Element.Children[i]; - items.Add((item.Title, item.IconImageSource, item.IsEnabled)); - } + SetTabIconImageSource(page, tab, result?.Value); + }); + } - return items; - } + public virtual void UpdateBarBackgroundColor() + { + if (Element.BarBackground != null) + return; - void SetupBottomNavigationView() + if (IsBottomTabPlacement) { - var currentIndex = Element.Children.IndexOf(Element.CurrentPage); - var items = CreateTabList(); - - BottomNavigationViewUtils.SetupMenu( - _bottomNavigationView.Menu, - _bottomNavigationView.MaxItemCount, - items, - currentIndex, - _bottomNavigationView, - Element.FindMauiContext()); - - if (Element.CurrentPage == null && Element.Children.Count > 0) - Element.CurrentPage = Element.Children[0]; - } + Color tintColor = Element.BarBackgroundColor; - void UpdateTabIcons() + if (tintColor == null) + _bottomNavigationView.SetBackground(null); + else if (tintColor != null) + _bottomNavigationView.SetBackgroundColor(tintColor.ToPlatform()); + } + else { - TabLayout tabs = _tabLayout; - - if (tabs.TabCount != Element.Children.Count) - return; + Color tintColor = Element.BarBackgroundColor; - for (var i = 0; i < Element.Children.Count; i++) + if (tintColor == null) + _tabLayout.BackgroundTintMode = null; + else { - Page child = Element.Children[i]; - TabLayout.Tab tab = tabs.GetTabAt(i); - SetTabIconImageSource(child, tab); + _tabLayout.BackgroundTintMode = PorterDuff.Mode.Src; + _tabLayout.BackgroundTintList = ColorStateList.ValueOf(tintColor.ToPlatform()); } } + } - protected virtual void SetTabIconImageSource(Page page, TabLayout.Tab tab, Drawable icon) + public virtual void UpdateBarBackground() + { + if (_currentBarBackground == Element.BarBackground) + return; + + if (_currentBarBackground is GradientBrush oldGradientBrush) { - tab.SetIcon(icon); - SetIconColorFilter(page, tab); + oldGradientBrush.Parent = null; + oldGradientBrush.InvalidateGradientBrushRequested -= OnBarBackgroundChanged; } - void SetTabIconImageSource(Page page, TabLayout.Tab tab) + _currentBarBackground = Element.BarBackground; + + if (_currentBarBackground is GradientBrush newGradientBrush) { - page.IconImageSource.LoadImage( - _context, - result => - { - SetTabIconImageSource(page, tab, result?.Value); - }); + newGradientBrush.Parent = Element; + newGradientBrush.InvalidateGradientBrushRequested += OnBarBackgroundChanged; } - internal void UpdateBarBackgroundColor() - { - if (Element.BarBackground != null) - return; + RefreshBarBackground(); + } - if (IsBottomTabPlacement) - { - Color tintColor = Element.BarBackgroundColor; + void OnBarBackgroundChanged(object sender, EventArgs e) + { + RefreshBarBackground(); + } - if (tintColor == null) - _bottomNavigationView.SetBackground(null); - else if (tintColor != null) - _bottomNavigationView.SetBackgroundColor(tintColor.ToPlatform()); - } - else - { - Color tintColor = Element.BarBackgroundColor; + protected virtual void RefreshBarBackground() + { + if (IsBottomTabPlacement) + _bottomNavigationView.UpdateBackground(_currentBarBackground); + else + _tabLayout.UpdateBackground(_currentBarBackground); + } - if (tintColor == null) - _tabLayout.BackgroundTintMode = null; - else - { - _tabLayout.BackgroundTintMode = PorterDuff.Mode.Src; - _tabLayout.BackgroundTintList = ColorStateList.ValueOf(tintColor.ToPlatform()); - } - } - } + protected virtual ColorStateList GetItemTextColorStates() + { + if (_originalTabTextColors is null) + _originalTabTextColors = IsBottomTabPlacement ? _bottomNavigationView.ItemTextColor : _tabLayout.TabTextColors; - internal void UpdateBarBackground() - { - if (_currentBarBackground == Element.BarBackground) - return; + Color barItemColor = BarItemColor; + Color barTextColor = Element.BarTextColor; + Color barSelectedItemColor = BarSelectedItemColor; - if (_currentBarBackground is GradientBrush oldGradientBrush) - { - oldGradientBrush.Parent = null; - oldGradientBrush.InvalidateGradientBrushRequested -= OnBarBackgroundChanged; - } + if (barItemColor is null && barTextColor is null && barSelectedItemColor is null) + return _originalTabTextColors; - _currentBarBackground = Element.BarBackground; + if (_newTabTextColors is not null) + return _newTabTextColors; - if (_currentBarBackground is GradientBrush newGradientBrush) - { - newGradientBrush.Parent = Element; - newGradientBrush.InvalidateGradientBrushRequested += OnBarBackgroundChanged; - } + int checkedColor; - RefreshBarBackground(); - } + // The new default color to use may have a color if BarItemColor is not null or the original colors for text + // are not null either. If it does not happens, this variable will be null and the ColorStateList of the + // original colors is used. + int? defaultColor = null; - void OnBarBackgroundChanged(object sender, EventArgs e) + if (barTextColor is not null) { - RefreshBarBackground(); + checkedColor = barTextColor.ToPlatform().ToArgb(); + defaultColor = checkedColor; } - - void RefreshBarBackground() + else { - if (IsBottomTabPlacement) - _bottomNavigationView.UpdateBackground(_currentBarBackground); - else - _tabLayout.UpdateBackground(_currentBarBackground); + // UnSelected tabs TextColor + defaultColor = GetItemTextColor(barItemColor, _originalTabTextColors); + + // Selected tabs TextColor + checkedColor = GetItemTextColor(barSelectedItemColor, _originalTabTextColors); } - protected virtual ColorStateList GetItemTextColorStates() - { - if (_originalTabTextColors is null) - _originalTabTextColors = IsBottomTabPlacement ? _bottomNavigationView.ItemTextColor : _tabLayout.TabTextColors; + _newTabTextColors = GetColorStateList(defaultColor.Value, checkedColor); - Color barItemColor = BarItemColor; - Color barTextColor = Element.BarTextColor; - Color barSelectedItemColor = BarSelectedItemColor; + return _newTabTextColors; + } - if (barItemColor is null && barTextColor is null && barSelectedItemColor is null) - return _originalTabTextColors; + int GetItemTextColor(Color customColor, ColorStateList originalColors) + { + return customColor?.ToPlatform().ToArgb() ?? originalColors?.DefaultColor ?? 0; + } - if (_newTabTextColors is not null) - return _newTabTextColors; + protected virtual ColorStateList GetItemIconTintColorState(Page page) + { + if (page.IconImageSource is FontImageSource fontImageSource && fontImageSource.Color is not null) + { + return null; + } - int checkedColor; + if (_orignalTabIconColors is null) + { + _orignalTabIconColors = IsBottomTabPlacement ? _bottomNavigationView.ItemIconTintList : _tabLayout.TabIconTint; + } - // The new default color to use may have a color if BarItemColor is not null or the original colors for text - // are not null either. If it does not happens, this variable will be null and the ColorStateList of the - // original colors is used. - int? defaultColor = null; + Color barItemColor = BarItemColor; + Color barSelectedItemColor = BarSelectedItemColor; - if (barTextColor is not null) - { - checkedColor = barTextColor.ToPlatform().ToArgb(); - defaultColor = checkedColor; - } - else - { - // UnSelected tabs TextColor - defaultColor = GetItemTextColor(barItemColor, _originalTabTextColors); + if (barItemColor is null && barSelectedItemColor is null) + { + return _orignalTabIconColors; + } - // Selected tabs TextColor - checkedColor = GetItemTextColor(barSelectedItemColor, _originalTabTextColors); - } + if (_newTabIconColors is not null) + { + return _newTabIconColors; + } - _newTabTextColors = GetColorStateList(defaultColor.Value, checkedColor); + int defaultColor; + int checkedColor; - return _newTabTextColors; + if (barItemColor is not null) + { + defaultColor = barItemColor.ToPlatform().ToArgb(); } - - int GetItemTextColor(Color customColor, ColorStateList originalColors) + else { - return customColor?.ToPlatform().ToArgb() ?? originalColors?.DefaultColor ?? 0; + defaultColor = GetDefaultColor(); } - protected virtual ColorStateList GetItemIconTintColorState(Page page) + if (barSelectedItemColor is not null) { - if (page.IconImageSource is FontImageSource fontImageSource && fontImageSource.Color is not null) - { - return null; - } - - if (_orignalTabIconColors is null) - { - _orignalTabIconColors = IsBottomTabPlacement ? _bottomNavigationView.ItemIconTintList : _tabLayout.TabIconTint; - } - - Color barItemColor = BarItemColor; - Color barSelectedItemColor = BarSelectedItemColor; - - if (barItemColor is null && barSelectedItemColor is null) - { - return _orignalTabIconColors; - } - - if (_newTabIconColors is not null) - { - return _newTabIconColors; - } - - int defaultColor; - int checkedColor; + checkedColor = barSelectedItemColor.ToPlatform().ToArgb(); + } + else + { + checkedColor = GetDefaultColor(); + } - if (barItemColor is not null) - { - defaultColor = barItemColor.ToPlatform().ToArgb(); - } - else - { - defaultColor = GetDefaultColor(); - } + _newTabIconColors = GetColorStateList(defaultColor, checkedColor); + return _newTabIconColors; + } - if (barSelectedItemColor is not null) + int GetDefaultColor() + { + int defaultColor; + var styledAttributes = + _context.Context.Theme.ObtainStyledAttributes( + null, + Resource.Styleable.NavigationBarView, + Resource.Attribute.bottomNavigationStyle, + 0); + + try + { + var defaultColors = styledAttributes.GetColorStateList(Resource.Styleable.NavigationBarView_itemIconTint); + if (defaultColors is not null) { - checkedColor = barSelectedItemColor.ToPlatform().ToArgb(); + defaultColor = defaultColors.DefaultColor; } else { - checkedColor = GetDefaultColor(); - } - - _newTabIconColors = GetColorStateList(defaultColor, checkedColor); - return _newTabIconColors; - } - - int GetDefaultColor() - { - int defaultColor; - var styledAttributes = - _context.Context.Theme.ObtainStyledAttributes( - null, - Resource.Styleable.NavigationBarView, - Resource.Attribute.bottomNavigationStyle, - 0); - - try - { - var defaultColors = styledAttributes.GetColorStateList(Resource.Styleable.NavigationBarView_itemIconTint); - if (defaultColors is not null) + // These are the defaults currently set inside android + // It's very unlikely we'll hit this path because the + // NavigationBarView_itemIconTint should always resolve + // But just in case, we'll just hard code to some defaults + // instead of leaving the application in a broken state + if (IsDarkTheme) { - defaultColor = defaultColors.DefaultColor; + defaultColor = ColorUtils.SetAlphaComponent( + ContextCompat.GetColor(_context.Context, Resource.Color.primary_dark_material_light), + 153); // 60% opacity } else { - // These are the defaults currently set inside android - // It's very unlikely we'll hit this path because the - // NavigationBarView_itemIconTint should always resolve - // But just in case, we'll just hard code to some defaults - // instead of leaving the application in a broken state - if (IsDarkTheme) - { - defaultColor = ColorUtils.SetAlphaComponent( - ContextCompat.GetColor(_context.Context, Resource.Color.primary_dark_material_light), - 153); // 60% opacity - } - else - { - defaultColor = ColorUtils.SetAlphaComponent( - ContextCompat.GetColor(_context.Context, Resource.Color.primary_dark_material_dark), - 153); // 60% opacity - } + defaultColor = ColorUtils.SetAlphaComponent( + ContextCompat.GetColor(_context.Context, Resource.Color.primary_dark_material_dark), + 153); // 60% opacity } } - finally - { - styledAttributes.Recycle(); - } - return defaultColor; } - - void OnMoreSheetDismissed(object sender, EventArgs e) + finally { - var index = Element.Children.IndexOf(Element.CurrentPage); - using (var menu = _bottomNavigationView.Menu) - { - index = Math.Min(index, menu.Size() - 1); - if (index < 0) - return; - using (var menuItem = menu.GetItem(index)) - menuItem.SetChecked(true); - } - - if (sender is BottomSheetDialog bsd) - bsd.DismissEvent -= OnMoreSheetDismissed; + styledAttributes.Recycle(); } + return defaultColor; + } - void OnMoreItemSelected(int selectedIndex, BottomSheetDialog dialog) + protected virtual void OnMoreSheetDismissed(object sender, EventArgs e) + { + var index = Element.Children.IndexOf(Element.CurrentPage); + using (var menu = _bottomNavigationView.Menu) { - if (selectedIndex >= 0 && _bottomNavigationView.SelectedItemId != selectedIndex && Element.Children.Count > selectedIndex) - Element.CurrentPage = Element.Children[selectedIndex]; - - dialog.Dismiss(); - dialog.DismissEvent -= OnMoreSheetDismissed; - dialog.Dispose(); + index = Math.Min(index, menu.Size() - 1); + if (index < 0) + return; + using (var menuItem = menu.GetItem(index)) + menuItem.SetChecked(true); } - void UpdateItemIconColor() - { - _newTabIconColors = null; + if (sender is BottomSheetDialog bsd) + bsd.DismissEvent -= OnMoreSheetDismissed; + } - if (IsBottomTabPlacement) + protected virtual void OnMoreItemSelected(int selectedIndex, BottomSheetDialog dialog) + { + if (selectedIndex >= 0 && _bottomNavigationView.SelectedItemId != selectedIndex && Element.Children.Count > selectedIndex) + Element.CurrentPage = Element.Children[selectedIndex]; + + dialog.Dismiss(); + dialog.DismissEvent -= OnMoreSheetDismissed; + dialog.Dispose(); + } + + void UpdateItemIconColor() + { + _newTabIconColors = null; + + if (IsBottomTabPlacement) + { + for (int i = 0; i < _bottomNavigationView.Menu.Size(); i++) { - for (int i = 0; i < _bottomNavigationView.Menu.Size(); i++) - { - var menuItem = _bottomNavigationView.Menu.GetItem(i); - var page = Element.Children[i]; - SetupBottomNavigationViewIconColor(page, menuItem, i); - } + var menuItem = _bottomNavigationView.Menu.GetItem(i); + var page = Element.Children[i]; + SetupBottomNavigationViewIconColor(page, menuItem, i); } - else + } + else + { + for (int i = 0; i < _tabLayout.TabCount; i++) { - for (int i = 0; i < _tabLayout.TabCount; i++) - { - TabLayout.Tab tab = _tabLayout.GetTabAt(i); - var page = Element.Children[i]; - this.SetIconColorFilter(page, tab); - } + TabLayout.Tab tab = _tabLayout.GetTabAt(i); + var page = Element.Children[i]; + this.SetIconColorFilter(page, tab); } } + } - void SetupBottomNavigationViewIconColor(Page page, IMenuItem menuItem, int i) - { - // Updating the icon color of each BottomNavigationView item individually works correctly. - // This is necessary because `ItemIconTintList` applies the color globally to all items, - // which doesn't allow for per-item customization. - // Currently, there is no modern API that provides the desired behavior. - // Therefore, the obsolete `BottomNavigationItemView` approach is used. + void SetupBottomNavigationViewIconColor(Page page, IMenuItem menuItem, int i) + { + // Updating the icon color of each BottomNavigationView item individually works correctly. + // This is necessary because `ItemIconTintList` applies the color globally to all items, + // which doesn't allow for per-item customization. + // Currently, there is no modern API that provides the desired behavior. + // Therefore, the obsolete `BottomNavigationItemView` approach is used. #pragma warning disable XAOBS001 // Type or member is obsolete - if (_bottomNavigationView.GetChildAt(0) is BottomNavigationMenuView menuView) - { - var itemView = menuView.GetChildAt(i) as BottomNavigationItemView; + if (_bottomNavigationView.GetChildAt(0) is BottomNavigationMenuView menuView) + { + var itemView = menuView.GetChildAt(i) as BottomNavigationItemView; - if (itemView != null && itemView.Id == menuItem.ItemId) - { - ColorStateList colors = GetItemIconTintColorState(page); + if (itemView != null && itemView.Id == menuItem.ItemId) + { + ColorStateList colors = GetItemIconTintColorState(page); - itemView.SetIconTintList(colors); - } + itemView.SetIconTintList(colors); } -#pragma warning restore XAOBS001 // Type or member is obsolete } +#pragma warning restore XAOBS001 // Type or member is obsolete + } - internal void UpdateTabItemStyle() + protected virtual void UpdateStyleForTabItem() + { + Color barItemColor = BarItemColor; + Color barTextColor = Element.BarTextColor; + Color barSelectedItemColor = BarSelectedItemColor; + + if (_tabItemStyleLoaded && + _currentBarItemColor == barItemColor && + _currentBarTextColor == barTextColor && + _currentBarSelectedItemColor == barSelectedItemColor) { - Color barItemColor = BarItemColor; - Color barTextColor = Element.BarTextColor; - Color barSelectedItemColor = BarSelectedItemColor; - - if (_tabItemStyleLoaded && - _currentBarItemColor == barItemColor && - _currentBarTextColor == barTextColor && - _currentBarSelectedItemColor == barSelectedItemColor) - { - return; - } + return; + } - _tabItemStyleLoaded = true; - _currentBarItemColor = BarItemColor; - _currentBarTextColor = Element.BarTextColor; - _currentBarSelectedItemColor = BarSelectedItemColor; + _tabItemStyleLoaded = true; + _currentBarItemColor = BarItemColor; + _currentBarTextColor = Element.BarTextColor; + _currentBarSelectedItemColor = BarSelectedItemColor; - UpdateBarTextColor(); - UpdateItemIconColor(); - } + UpdateBarTextColor(); + UpdateItemIconColor(); + } - void UpdateBarTextColor() - { - _newTabTextColors = null; + internal void UpdateTabItemStyle() + { + UpdateStyleForTabItem(); + } - _currentBarTextColorStateList = GetItemTextColorStates() ?? _originalTabTextColors; - if (IsBottomTabPlacement) - _bottomNavigationView.ItemTextColor = _currentBarTextColorStateList; - else - _tabLayout.TabTextColors = _currentBarTextColorStateList; - } + void UpdateBarTextColor() + { + _newTabTextColors = null; - void SetIconColorFilter(Page page, TabLayout.Tab tab) - { - SetIconColorFilter(page, tab, _tabLayout.GetTabAt(_tabLayout.SelectedTabPosition) == tab); - } + _currentBarTextColorStateList = GetItemTextColorStates() ?? _originalTabTextColors; + if (IsBottomTabPlacement) + _bottomNavigationView.ItemTextColor = _currentBarTextColorStateList; + else + _tabLayout.TabTextColors = _currentBarTextColorStateList; + } - void SetIconColorFilter(Page page, TabLayout.Tab tab, bool selected) + void SetIconColorFilter(Page page, TabLayout.Tab tab) + { + SetIconColorFilter(page, tab, _tabLayout.GetTabAt(_tabLayout.SelectedTabPosition) == tab); + } + + protected virtual void SetIconColorFilter(Page page, TabLayout.Tab tab, bool selected) + { + var icon = tab.Icon; + if (icon == null) + return; + + ColorStateList colors = GetItemIconTintColorState(page); + if (colors == null) + ADrawableCompat.SetTintList(icon, null); + else { - var icon = tab.Icon; - if (icon == null) - return; + int[] _stateSet = null; - ColorStateList colors = GetItemIconTintColorState(page); - if (colors == null) + if (selected) + _stateSet = GetSelectedStateSet(); + else + _stateSet = GetEmptyStateSet(); + + if (colors.GetColorForState(_stateSet, _defaultAndroidColor) == _defaultARGBColor) ADrawableCompat.SetTintList(icon, null); else { - int[] _stateSet = null; - - if (selected) - _stateSet = GetSelectedStateSet(); - else - _stateSet = GetEmptyStateSet(); - - if (colors.GetColorForState(_stateSet, _defaultAndroidColor) == _defaultARGBColor) - ADrawableCompat.SetTintList(icon, null); - else + var wrappedIcon = ADrawableCompat.Wrap(icon); + if (wrappedIcon != icon) { - var wrappedIcon = ADrawableCompat.Wrap(icon); - if (wrappedIcon != icon) - { - icon = wrappedIcon; - tab.SetIcon(wrappedIcon); - } - - icon.Mutate(); - icon.SetState(_stateSet); - ADrawableCompat.SetTintList(icon, colors); + icon = wrappedIcon; + tab.SetIcon(wrappedIcon); } + + icon.Mutate(); + icon.SetState(_stateSet); + ADrawableCompat.SetTintList(icon, colors); } - icon.InvalidateSelf(); } + icon.InvalidateSelf(); + } - int[] GetSelectedStateSet() + int[] GetSelectedStateSet() + { + if (IsBottomTabPlacement) { - if (IsBottomTabPlacement) - { - if (_checkedStateSet == null) - _checkedStateSet = new int[] { global::Android.Resource.Attribute.StateChecked }; - - return _checkedStateSet; - } - else - { - if (_selectedStateSet == null) - _selectedStateSet = GetStateSet(new TempView(_context.Context).SelectedStateSet); + if (_checkedStateSet == null) + _checkedStateSet = new int[] { global::Android.Resource.Attribute.StateChecked }; - return _selectedStateSet; - } + return _checkedStateSet; } - - int[] GetEmptyStateSet() + else { - if (_emptyStateSet == null) - _emptyStateSet = GetStateSet(new TempView(_context.Context).EmptyStateSet); + if (_selectedStateSet == null) + _selectedStateSet = GetStateSet(new TempView(_context.Context).SelectedStateSet); - return _emptyStateSet; + return _selectedStateSet; } + } + + int[] GetEmptyStateSet() + { + if (_emptyStateSet == null) + _emptyStateSet = GetStateSet(new TempView(_context.Context).EmptyStateSet); + + return _emptyStateSet; + } - class TempView : AView + class TempView : AView + { + // These are protected static so need to be inside a View Instance to retrieve these + public new IList EmptyStateSet => AView.EmptyStateSet; + public new IList SelectedStateSet => AView.SelectedStateSet; + public TempView(Context context) : base(context) { - // These are protected static so need to be inside a View Instance to retrieve these - public new IList EmptyStateSet => AView.EmptyStateSet; - public new IList SelectedStateSet => AView.SelectedStateSet; - public TempView(Context context) : base(context) - { - } } + } - int[] GetStateSet(IList stateSet) - { - var results = new int[stateSet.Count]; - for (int i = 0; i < results.Length; i++) - results[i] = stateSet[i]; + int[] GetStateSet(IList stateSet) + { + var results = new int[stateSet.Count]; + for (int i = 0; i < results.Length; i++) + results[i] = stateSet[i]; - return results; - } + return results; + } - ColorStateList GetColorStateList(int defaultColor, int checkedColor) - { - int[][] states = new int[2][]; - int[] colors = new int[2]; + ColorStateList GetColorStateList(int defaultColor, int checkedColor) + { + int[][] states = new int[2][]; + int[] colors = new int[2]; - states[0] = GetSelectedStateSet(); - colors[0] = checkedColor; - states[1] = GetEmptyStateSet(); - colors[1] = defaultColor; + states[0] = GetSelectedStateSet(); + colors[0] = checkedColor; + states[1] = GetEmptyStateSet(); + colors[1] = defaultColor; #pragma warning disable RS0030 - //TODO: port this usage to Java, if this becomes a performance concern - return new ColorStateList(states, colors); + //TODO: port this usage to Java, if this becomes a performance concern + return new ColorStateList(states, colors); #pragma warning restore RS0030 - } + } - class Listeners : ViewPager2.OnPageChangeCallback, + class Listeners : ViewPager2.OnPageChangeCallback, #pragma warning disable CS0618 // Type or member is obsolete - TabLayout.IOnTabSelectedListener, + TabLayout.IOnTabSelectedListener, #pragma warning restore CS0618 // Type or member is obsolete - NavigationBarView.IOnItemSelectedListener, - TabLayoutMediator.ITabConfigurationStrategy - { - readonly TabbedPageManager _tabbedPageManager; - - public Listeners(TabbedPageManager tabbedPageManager) - { - _tabbedPageManager = tabbedPageManager; - } - - public override void OnPageSelected(int position) - { - base.OnPageSelected(position); - - var Element = _tabbedPageManager.Element; + NavigationBarView.IOnItemSelectedListener, + TabLayoutMediator.ITabConfigurationStrategy + { + readonly TabbedPageManager _tabbedPageManager; - if (Element == null) - return; + public Listeners(TabbedPageManager tabbedPageManager) + { + _tabbedPageManager = tabbedPageManager; + } - var _previousPage = _tabbedPageManager._previousPage; - var IsBottomTabPlacement = _tabbedPageManager.IsBottomTabPlacement; - var _bottomNavigationView = _tabbedPageManager._bottomNavigationView; + public override void OnPageSelected(int position) + { + base.OnPageSelected(position); - if (_previousPage != Element.CurrentPage) - { - _previousPage?.SendDisappearing(); - _previousPage = Element.CurrentPage; - _tabbedPageManager._previousPage = Element.CurrentPage; - } + var Element = _tabbedPageManager.Element; - // This only happens if all the pages have been removed - if (Element.Children.Count > 0) - { - Element.CurrentPage = Element.Children[position]; - Element.CurrentPage.SendAppearing(); - } + if (Element == null) + return; - if (IsBottomTabPlacement) - _bottomNavigationView.SelectedItemId = position; - } + var _previousPage = _tabbedPageManager.previousPage; + var IsBottomTabPlacement = _tabbedPageManager.IsBottomTabPlacement; + var _bottomNavigationView = _tabbedPageManager._bottomNavigationView; - void TabLayoutMediator.ITabConfigurationStrategy.OnConfigureTab(TabLayout.Tab p0, int p1) + if (_previousPage != Element.CurrentPage) { - p0.SetText(_tabbedPageManager.Element.Children[p1].Title); + _previousPage?.SendDisappearing(); + _previousPage = Element.CurrentPage; + _tabbedPageManager.previousPage = Element.CurrentPage; } - bool NavigationBarView.IOnItemSelectedListener.OnNavigationItemSelected(IMenuItem item) + // This only happens if all the pages have been removed + if (Element.Children.Count > 0) { - if (_tabbedPageManager.Element == null) - return false; + Element.CurrentPage = Element.Children[position]; + Element.CurrentPage.SendAppearing(); + } - var id = item.ItemId; - if (id == BottomNavigationViewUtils.MoreTabId) - { - var items = _tabbedPageManager.CreateTabList(); - var bottomSheetDialog = BottomNavigationViewUtils.CreateMoreBottomSheet(_tabbedPageManager.OnMoreItemSelected, _tabbedPageManager.Element.FindMauiContext(), items, _tabbedPageManager._bottomNavigationView.MaxItemCount); - bottomSheetDialog.DismissEvent += _tabbedPageManager.OnMoreSheetDismissed; - bottomSheetDialog.Show(); - } - else - { - if (_tabbedPageManager._bottomNavigationView.SelectedItemId != item.ItemId && _tabbedPageManager.Element.Children.Count > item.ItemId) - _tabbedPageManager.Element.CurrentPage = _tabbedPageManager.Element.Children[item.ItemId]; - } + if (IsBottomTabPlacement) + _bottomNavigationView.SelectedItemId = position; + } - return true; - } + void TabLayoutMediator.ITabConfigurationStrategy.OnConfigureTab(TabLayout.Tab p0, int p1) + { + p0.SetText(_tabbedPageManager.Element.Children[p1].Title); + } + bool NavigationBarView.IOnItemSelectedListener.OnNavigationItemSelected(IMenuItem item) + { + if (_tabbedPageManager.Element == null) + return false; - void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab) + var id = item.ItemId; + if (id == BottomNavigationViewUtils.MoreTabId) { + var items = _tabbedPageManager.CreateTabList(); + var bottomSheetDialog = BottomNavigationViewUtils.CreateMoreBottomSheet(_tabbedPageManager.OnMoreItemSelected, _tabbedPageManager.Element.FindMauiContext(), items, _tabbedPageManager._bottomNavigationView.MaxItemCount); + bottomSheetDialog.DismissEvent += _tabbedPageManager.OnMoreSheetDismissed; + bottomSheetDialog.Show(); } - - void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab) + else { - _tabbedPageManager.TabSelected(tab); + if (_tabbedPageManager._bottomNavigationView.SelectedItemId != item.ItemId && _tabbedPageManager.Element.Children.Count > item.ItemId) + _tabbedPageManager.Element.CurrentPage = _tabbedPageManager.Element.Children[item.ItemId]; } - void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab) - { - _tabbedPageManager.SetIconColorFilter(_tabbedPageManager.Element.CurrentPage, tab, false); - } + return true; + } + + + void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab) + { + } + + void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab) + { + _tabbedPageManager.TabSelected(tab); + } + + void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab) + { + _tabbedPageManager.SetIconColorFilter(_tabbedPageManager.Element.CurrentPage, tab, false); } } } \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt index 82eb8ac184fc..1963656dc452 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -60,6 +60,20 @@ Microsoft.Maui.Controls.DatePickerOpenedEventArgs Microsoft.Maui.Controls.DatePickerOpenedEventArgs.DatePickerOpenedEventArgs() -> void ~Microsoft.Maui.Controls.Element.transientNamescope -> Microsoft.Maui.Controls.Internals.INameScope Microsoft.Maui.Controls.FlexLayout.CrossPlatformMeasure(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size +Microsoft.Maui.Controls.Handlers.TabbedPageManager +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.BarItemColor.get -> Microsoft.Maui.Graphics.Color +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.BarSelectedItemColor.get -> Microsoft.Maui.Graphics.Color +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.BottomNavigationView.get -> Google.Android.Material.BottomNavigation.BottomNavigationView +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.Element.get -> Microsoft.Maui.Controls.TabbedPage +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.Element.set -> void +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.FragmentManager.get -> AndroidX.Fragment.App.FragmentManager +Microsoft.Maui.Controls.Handlers.TabbedPageManager.IsBottomTabPlacement.get -> bool +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.NavigationRootManager.get -> Microsoft.Maui.Platform.NavigationRootManager +Microsoft.Maui.Controls.Handlers.TabbedPageManager.NotifyDataSetChanged() -> void +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.TabLayout.get -> Google.Android.Material.Tabs.TabLayout +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.TabbedPageManager(Microsoft.Maui.IMauiContext context) -> void +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.ViewPager.get -> AndroidX.ViewPager2.Widget.ViewPager2 +~Microsoft.Maui.Controls.Handlers.TabbedPageManager.previousPage -> Microsoft.Maui.Controls.Page Microsoft.Maui.Controls.HybridWebView.InvokeJavaScriptAsync(string! methodName, object?[]? paramValues = null, System.Text.Json.Serialization.Metadata.JsonTypeInfo?[]? paramJsonTypeInfos = null) -> System.Threading.Tasks.Task! Microsoft.Maui.Controls.HybridWebView.WebResourceRequested -> System.EventHandler? Microsoft.Maui.Controls.ICornerElement @@ -138,6 +152,8 @@ Microsoft.Maui.Controls.ShadowTypeConverter Microsoft.Maui.Controls.ShadowTypeConverter.ShadowTypeConverter() -> void ~Microsoft.Maui.Controls.Switch.OffColor.get -> Microsoft.Maui.Graphics.Color ~Microsoft.Maui.Controls.Switch.OffColor.set -> void +Microsoft.Maui.Controls.TabbedPage.TabbedPageManager.get -> Microsoft.Maui.Controls.Handlers.TabbedPageManager! +Microsoft.Maui.Controls.TabbedPage.TabbedPageManager.set -> void Microsoft.Maui.Controls.TemplatedView.CascadeInputTransparent.get -> bool Microsoft.Maui.Controls.TemplatedView.CascadeInputTransparent.set -> void Microsoft.Maui.Controls.TemplatedView.Children.get -> System.Collections.Generic.IReadOnlyList! @@ -341,6 +357,7 @@ override Microsoft.Maui.Controls.WebViewSourceTypeConverter.ConvertTo(System.Com *REMOVED*~static Microsoft.Maui.Controls.Accelerator.FromString(string text) -> Microsoft.Maui.Controls.Accelerator *REMOVED*~static Microsoft.Maui.Controls.Accelerator.implicit operator Microsoft.Maui.Controls.Accelerator(string accelerator) -> Microsoft.Maui.Controls.Accelerator ~static Microsoft.Maui.Controls.Button.MapRippleColor(Microsoft.Maui.Handlers.IButtonHandler handler, Microsoft.Maui.Controls.Button button) -> void +static Microsoft.Maui.Controls.Handlers.TabbedPageManager.IsDarkTheme.get -> bool static Microsoft.Maui.Controls.ImageButton.MapRippleColor(Microsoft.Maui.Handlers.IImageButtonHandler! handler, Microsoft.Maui.Controls.ImageButton! imageButton) -> void ~static Microsoft.Maui.Controls.Internals.TextTransformUtilities.GetTransformedText(string source, Microsoft.Maui.TextTransform textTransform) -> string ~static Microsoft.Maui.Controls.Internals.TextTransformUtilities.SetPlainText(Microsoft.Maui.Controls.InputView inputView, string platformText) -> void @@ -432,6 +449,26 @@ virtual Microsoft.Maui.Controls.BindableProperty.CreateDefaultValueDelegate bool ~virtual Microsoft.Maui.Controls.BindableProperty.ValidateValueDelegate.Invoke(Microsoft.Maui.Controls.BindableObject bindable, TPropertyType value) -> bool ~virtual Microsoft.Maui.Controls.CollectionSynchronizationCallback.Invoke(System.Collections.IEnumerable collection, object context, System.Action accessMethod, bool writeAccess) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.GetItemIconTintColorState(Microsoft.Maui.Controls.Page page) -> Android.Content.Res.ColorStateList +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.GetItemTextColorStates() -> Android.Content.Res.ColorStateList +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.OnChildrenCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.OnLayoutChanged(object sender, Android.Views.View.LayoutChangeEventArgs e) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.OnMoreItemSelected(int selectedIndex, Google.Android.Material.BottomSheet.BottomSheetDialog dialog) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.OnMoreSheetDismissed(object sender, System.EventArgs e) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.OnPagePropertyChanged(Microsoft.Maui.Controls.Page page, System.ComponentModel.PropertyChangedEventArgs e) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.OnTabbedPageAppearing(object sender, System.EventArgs e) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.OnTabbedPageDisappearing(object sender, System.EventArgs e) -> void +virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.RefreshBarBackground() -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.RootViewChanged(object sender, System.EventArgs e) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.SetElement(Microsoft.Maui.Controls.TabbedPage tabbedPage) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.SetIconColorFilter(Microsoft.Maui.Controls.Page page, Google.Android.Material.Tabs.TabLayout.Tab tab, bool selected) -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.SetTabIconImageSource(Microsoft.Maui.Controls.Page page, Google.Android.Material.Tabs.TabLayout.Tab tab, Android.Graphics.Drawables.Drawable icon) -> void +virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.SetupBottomNavigationView() -> void +~virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.TabSelected(Google.Android.Material.Tabs.TabLayout.Tab tab) -> void +virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.UpdateBarBackground() -> void +virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.UpdateBarBackgroundColor() -> void +virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.UpdateStyleForTabItem() -> void +virtual Microsoft.Maui.Controls.Handlers.TabbedPageManager.UpdateTabIcons() -> void ~virtual Microsoft.Maui.Controls.Internals.EvaluateJavaScriptDelegate.Invoke(string script) -> System.Threading.Tasks.Task ~virtual Microsoft.Maui.Controls.PropertyChangingEventHandler.Invoke(object sender, Microsoft.Maui.Controls.PropertyChangingEventArgs e) -> void ~virtual Microsoft.Maui.Controls.VisualElement.ComputeConstraintForView(Microsoft.Maui.Controls.View view) -> void diff --git a/src/Controls/src/Core/TabbedPage/TabbedPage.Android.cs b/src/Controls/src/Core/TabbedPage/TabbedPage.Android.cs index ea6322fb04e7..a454c66d6c8a 100644 --- a/src/Controls/src/Core/TabbedPage/TabbedPage.Android.cs +++ b/src/Controls/src/Core/TabbedPage/TabbedPage.Android.cs @@ -16,11 +16,24 @@ public partial class TabbedPage IMauiContext MauiContext => this.Handler?.MauiContext ?? throw new InvalidOperationException("MauiContext cannot be null here"); TabbedPageManager? _tabbedPageManager; + public TabbedPageManager TabbedPageManager + { + get => _tabbedPageManager ??= new (MauiContext); + set + { + if (_tabbedPageManager is not null && _tabbedPageManager != value) + { + throw new InvalidOperationException("TabbedPageManager cannot be assigned to new instance."); + } + + _tabbedPageManager = value ?? new(MauiContext); + } + } + ViewPager2 CreatePlatformView() { - _tabbedPageManager ??= new TabbedPageManager(MauiContext); - _tabbedPageManager.SetElement(this); - return _tabbedPageManager.ViewPager; + TabbedPageManager.SetElement(this); + return TabbedPageManager.ViewPager; } static AView? OnCreatePlatformView(ViewHandler arg)