Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d631ab1
Sandbox test sample
Tamilarasan-Paranthaman Mar 2, 2026
ef1f21f
Android Shell Handler
Tamilarasan-Paranthaman Mar 2, 2026
d319e88
Update ShellFlyoutTemplatedContentRenderer.cs
Tamilarasan-Paranthaman Mar 3, 2026
0de1817
Fix shell section switching issue
Tamilarasan-Paranthaman Mar 4, 2026
5892456
Fix shell handler issues
Tamilarasan-Paranthaman Mar 10, 2026
ab75cba
Updating Sandbox test sample
Tamilarasan-Paranthaman Mar 10, 2026
1431be3
Phase 2: Fix shell handler issues
Tamilarasan-Paranthaman Mar 10, 2026
d46337a
Phase 3: Fixed Shell item handler issues
Tamilarasan-Paranthaman Mar 11, 2026
a3ade70
Phase 4: Fix Shell handler issues
Tamilarasan-Paranthaman Mar 12, 2026
95074c9
Phase 5: Fix Shell item and section issues
Tamilarasan-Paranthaman Mar 13, 2026
b101529
Update ShellHandler.Android.cs
Tamilarasan-Paranthaman Mar 16, 2026
a3d3da9
Revamp Shell Structure
Tamilarasan-Paranthaman Mar 18, 2026
b0dcb9a
Fix ShellSection issues
Tamilarasan-Paranthaman Mar 23, 2026
7900b2f
Fix Shell issues
Tamilarasan-Paranthaman Mar 24, 2026
f3a4047
Implemented TabbedViewManager
Tamilarasan-Paranthaman Mar 25, 2026
8c4924a
Fix Shell related issues and refactor code
Tamilarasan-Paranthaman Mar 26, 2026
47dd71d
Consolidate Android Shell handler mappers
Tamilarasan-Paranthaman Mar 26, 2026
0603047
Refactor code and sync changes with the Unshipped file
Tamilarasan-Paranthaman Mar 26, 2026
7de6f0e
Fix Shell tab issues
Tamilarasan-Paranthaman Mar 27, 2026
e252e07
Replace PropertyChanged with mapper
Tamilarasan-Paranthaman Mar 30, 2026
b6dd067
Updated sandbox test sample
Tamilarasan-Paranthaman Mar 30, 2026
06d5f49
Fix issues in Shell handler
Tamilarasan-Paranthaman Mar 31, 2026
8500abc
Revert sandbox sample changes
Tamilarasan-Paranthaman Mar 31, 2026
22d79fc
Enable SH through build configuration
Tamilarasan-Paranthaman Mar 31, 2026
3e7bde8
Update StackNavigationManager.cs
Tamilarasan-Paranthaman Apr 1, 2026
b6433e8
Enable nullable annotations for Shell handlers
Tamilarasan-Paranthaman Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,10 @@
Condition="'$(UseMaterial3)' != ''"
Value="$(UseMaterial3)"
Trim="true" />
<RuntimeHostConfigurationOption Include="Microsoft.Maui.RuntimeFeature.UseAndroidShellHandlers"
Condition="'$(UseAndroidShellHandlers)' != ''"
Value="$(UseAndroidShellHandlers)"
Trim="true" />
</ItemGroup>
</Target>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ DataTemplate GetDataTemplate(int viewTypeId)

public override void OnViewRecycled(Java.Lang.Object holder)
{
if (holder is ElementViewHolder evh)
if (holder is ElementViewHolder evh && _listItems is not null)
{
// only clear out the Element if the item has been removed
bool found = false;
Expand Down Expand Up @@ -208,7 +208,9 @@ protected virtual void OnFlyoutItemsChanged(object sender, EventArgs e)
protected override void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

_disposed = true;

Expand All @@ -222,8 +224,15 @@ protected override void Dispose(bool disposing)

internal void Disconnect()
{
if (_shellContext is null)
{
return;
}

if (Shell is IShellController scc)
{
scc.FlyoutItemsChanged -= OnFlyoutItemsChanged;
}

_listItems = null;
_selectedCallback = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
#nullable disable
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics.Drawables;
using Android.Hardware.Lights;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
Expand All @@ -15,12 +11,9 @@
using AndroidX.RecyclerView.Widget;
using Google.Android.Material.AppBar;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform.Compatibility;
using Microsoft.Maui.Layouts;
using AView = Android.Views.View;
using LP = Android.Views.ViewGroup.LayoutParams;

namespace Microsoft.Maui.Controls.Platform.Compatibility
{
public class ShellFlyoutTemplatedContentRenderer : Java.Lang.Object, IShellFlyoutContentRenderer
Expand Down Expand Up @@ -211,6 +204,7 @@ protected virtual void LoadView(IShellContext shellContext)
MauiWindowInsetListener.SetupViewWithLocalListener(coordinator, _windowsListener);

UpdateFlyoutHeaderBehavior();

_shellContext.Shell.PropertyChanged += OnShellPropertyChanged;

UpdateFlyoutBackground();
Expand Down Expand Up @@ -267,31 +261,51 @@ protected void OnElementSelected(Element element)

protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// When using the new ShellHandler (not the compatibility ShellRenderer),
// all these properties are already handled by the handler's property mapper.
// Responding to PropertyChanged here would cause double updates.
if (_shellContext.Shell.Handler is Handlers.ShellHandler)
{
return;
}

if (e.PropertyName == Shell.FlyoutHeaderBehaviorProperty.PropertyName)
{
UpdateFlyoutHeaderBehavior();
}
else if (e.IsOneOf(
Shell.FlyoutBackgroundColorProperty,
Shell.FlyoutBackgroundProperty,
Shell.FlyoutBackgroundImageProperty,
Shell.FlyoutBackgroundImageAspectProperty))
{
UpdateFlyoutBackground();
}
else if (e.Is(Shell.FlyoutVerticalScrollModeProperty))
{
UpdateVerticalScrollMode();
}
else if (e.IsOneOf(
Shell.FlyoutHeaderProperty,
Shell.FlyoutHeaderTemplateProperty))
{
UpdateFlyoutHeader();
}
else if (e.IsOneOf(
Shell.FlyoutFooterProperty,
Shell.FlyoutFooterTemplateProperty))
{
UpdateFlyoutFooter();
}
else if (e.IsOneOf(
Shell.FlyoutContentProperty,
Shell.FlyoutContentTemplateProperty))
{
UpdateFlyoutContent();
}
}

protected virtual void UpdateFlyoutContent()
public virtual void UpdateFlyoutContent()
{
if (!_rootView.IsAlive())
return;
Expand Down Expand Up @@ -362,7 +376,7 @@ AView CreateFlyoutContent(ViewGroup rootView)
return _contentView.PlatformView;
}

protected virtual void UpdateFlyoutHeader()
public virtual void UpdateFlyoutHeader()
{
if (_headerView != null)
{
Expand All @@ -374,10 +388,7 @@ protected virtual void UpdateFlyoutHeader()
oldHeaderView.Dispose();
}

if (_flyoutHeader != null)
{
_flyoutHeader.MeasureInvalidated -= OnFlyoutHeaderMeasureInvalidated;
}
_flyoutHeader?.MeasureInvalidated -= OnFlyoutHeaderMeasureInvalidated;

_flyoutHeader = ((IShellController)_shellContext.Shell).FlyoutHeader;
if (_flyoutHeader != null)
Expand Down Expand Up @@ -414,7 +425,7 @@ void OnHeaderViewLayoutChange(object sender, AView.LayoutChangeEventArgs e)
UpdateContentPadding();
}

protected virtual void UpdateFlyoutFooter()
public virtual void UpdateFlyoutFooter()
{
if (_footerView != null)
{
Expand All @@ -427,7 +438,7 @@ protected virtual void UpdateFlyoutFooter()

var footer = ((IShellController)_shellContext.Shell).FlyoutFooter;

if (footer == null)
if (footer is null)
{
UpdateContentPadding();
return;
Expand Down Expand Up @@ -592,18 +603,17 @@ void OnFlyoutViewLayoutChanging()
}
}

void UpdateVerticalScrollMode()
public virtual void UpdateVerticalScrollMode()
{
if (_flyoutContentView is RecyclerView rv && rv.GetLayoutManager() is ScrollLayoutManager lm)
{
lm.ScrollVertically = _shellContext.Shell.FlyoutVerticalScrollMode;
}
}

protected virtual void UpdateFlyoutBackground()
public virtual void UpdateFlyoutBackground()
{
var brush = _shellContext.Shell.FlyoutBackground;

if (Brush.IsNullOrEmpty(brush))
{
var color = _shellContext.Shell.FlyoutBackgroundColor;
Expand Down Expand Up @@ -672,7 +682,7 @@ void UpdateFlyoutBgImageAsync()
});
}

protected virtual void UpdateFlyoutHeaderBehavior()
public virtual void UpdateFlyoutHeaderBehavior()
{
if (_headerView == null)
return;
Expand Down Expand Up @@ -737,8 +747,7 @@ internal void Disconnect()
if (_shellContext?.Shell != null)
_shellContext.Shell.PropertyChanged -= OnShellPropertyChanged;

if (_flyoutHeader != null)
_flyoutHeader.MeasureInvalidated -= OnFlyoutHeaderMeasureInvalidated;
_flyoutHeader?.MeasureInvalidated -= OnFlyoutHeaderMeasureInvalidated;

_flyoutHeader = null;

Expand Down Expand Up @@ -775,8 +784,7 @@ protected override void Dispose(bool disposing)
if (View != null && View is ShellFlyoutLayout sfl)
sfl.LayoutChanging -= OnFlyoutViewLayoutChanging;

if (_headerView != null)
_headerView.LayoutChange -= OnHeaderViewLayoutChange;
_headerView?.LayoutChange -= OnHeaderViewLayoutChange;

_contentView?.View = null;

Expand Down Expand Up @@ -814,8 +822,7 @@ public HeaderContainer(Context context, View view, IMauiContext mauiContext) : b

void Initialize(View view)
{
if (view != null)
view.PropertyChanged += OnViewPropertyChanged;
view?.PropertyChanged += OnViewPropertyChanged;
}

void OnViewPropertyChanged(object sender, PropertyChangedEventArgs e)
Expand Down Expand Up @@ -874,11 +881,8 @@ protected override void Dispose(bool disposing)

internal void Disconnect()
{
if (View != null)
{
View.PropertyChanged -= OnViewPropertyChanged;
View = null;
}
View?.PropertyChanged -= OnViewPropertyChanged;
View = null;
}

internal void SetFlyoutHeaderBehavior(FlyoutHeaderBehavior flyoutHeaderBehavior)
Expand Down Expand Up @@ -984,4 +988,4 @@ public override bool CanScrollVertically()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ class CustomFilter : Filter
{
private readonly BaseAdapter _adapter;

// Required by Android JNI bridge for native handle activation
protected CustomFilter(IntPtr javaReference, global::Android.Runtime.JniHandleOwnership transfer) : base(javaReference, transfer)
{
}

public CustomFilter(BaseAdapter adapter)
{
_adapter = adapter;
Expand All @@ -169,7 +174,7 @@ protected override FilterResults PerformFiltering(ICharSequence constraint)

protected override void PublishResults(ICharSequence constraint, FilterResults results)
{
_adapter.NotifyDataSetChanged();
_adapter?.NotifyDataSetChanged();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public ShellToolbarAppearanceTracker(IShellContext shellContext)

public virtual void SetAppearance(AToolbar toolbar, IShellToolbarTracker toolbarTracker, ShellAppearance appearance)
{
if (appearance is null)
{
return;
}

var foreground = appearance.ForegroundColor;
var background = appearance.BackgroundColor;
var titleColor = appearance.TitleColor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
using Paint = Android.Graphics.Paint;
using R = Android.Resource;

#pragma warning disable IDE0031 // Use null propagation
namespace Microsoft.Maui.Controls.Platform.Compatibility
{
public class ShellToolbarTracker : Java.Lang.Object, AView.IOnClickListener, IShellToolbarTracker, IFlyoutBehaviorObserver
Expand Down Expand Up @@ -102,7 +103,10 @@ public bool CanNavigateBack
{
get
{
if (_page?.Navigation?.NavigationStack?.Count > 1)
var navStackCount = _page?.Navigation?.NavigationStack?.Count ?? 0;
var canNavFromStack = navStackCount > 1;

if (canNavFromStack)
return true;

return _canNavigateBack;
Expand Down Expand Up @@ -478,7 +482,9 @@ protected virtual async void UpdateLeftBarButtonItem(Context context, AToolbar t
defaultDrawerArrowDrawable = true;
}

icon?.Progress = (CanNavigateBack) ? 1 : 0;
var canNav = CanNavigateBack;
var progress = canNav ? 1 : 0;
icon?.Progress = progress;

if (command != null || CanNavigateBack)
{
Expand Down Expand Up @@ -507,6 +513,11 @@ protected virtual async void UpdateLeftBarButtonItem(Context context, AToolbar t

_drawerToggle.SyncState();

// Re-apply icon Progress AFTER SyncState since SyncState resets it to 0
if (icon is not null)
{
icon.Progress = progress;
}

//this needs to be set after SyncState
UpdateToolbarIconAccessibilityText(toolbar, _shell);
Expand Down Expand Up @@ -651,6 +662,18 @@ protected virtual void UpdateToolbarItems(AToolbar toolbar, Page page)
if (SearchHandler != null && SearchHandler.SearchBoxVisibility != SearchBoxVisibility.Hidden)
{
var context = ShellContext.AndroidContext;

// If the SearchHandler changed (e.g., navigating between pages with different SearchHandlers),
// dispose the old search view so it gets recreated with the new handler's icons/settings.
if (_searchView != null && _searchView.SearchHandler != SearchHandler)
{
_searchView.View.RemoveFromParent();
_searchView.View.ViewAttachedToWindow -= OnSearchViewAttachedToWindow;
_searchView.SearchConfirmed -= OnSearchConfirmed;
_searchView.Dispose();
_searchView = null;
}

if (_searchView == null)
{
_searchView = GetSearchView(context);
Expand Down Expand Up @@ -691,6 +714,13 @@ protected virtual void UpdateToolbarItems(AToolbar toolbar, Page page)
}
else
{
// BUG FIX: Remove the collapsible search menu item when navigating to a page without SearchHandler
// Previously, only _searchView was cleaned up, but the menu item remained visible
if (menu.FindItem(_placeholderMenuItemId) is not null)
{
menu.RemoveItem(_placeholderMenuItemId);
}

if (_searchView != null)
{
_searchView.View.RemoveFromParent();
Expand Down
Loading
Loading