Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## [55.3.0]
- [AlertView] Improved layout performance by avoiding redundant measure and Remove/Add cycles on every size change.
- [ListItem] Improved layout performance by reusing dividers with visibility toggling instead of recreating them on property changes.
- [Tab][iOS] Improved layout performance by flattening nested StackLayouts into a single Grid.
- [ListItem][AlertView][NavigationListItem][BottomSheetHeader] Added InputTransparent to decorative elements to reduce hit-testing overhead.

## [55.2.2]
- [iOS26][Tip] Added more padding.

Expand Down
82 changes: 59 additions & 23 deletions src/library/DIPS.Mobile.UI/Components/Alerting/Alert/AlertView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public partial class AlertView : Grid
private Image? m_icon;
private ImageButton? m_closeIcon;
private CustomTruncationTextView? m_titleAndDescriptionLabel;

private double m_lastAllocatedWidth = -1;
private bool m_buttonsContainerAdded;
private int m_lastButtonColumn = -1;
private int m_lastButtonRow = -1;

public AlertView()
{
Expand Down Expand Up @@ -71,6 +76,11 @@ protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);

const double widthChangeTolerance = 0.001;
if (Math.Abs(width - m_lastAllocatedWidth) < widthChangeTolerance)
return;

m_lastAllocatedWidth = width;
UpdateButtonAlignment();
}

Expand All @@ -79,38 +89,59 @@ private void UpdateButtonAlignment()
if (LeftButtonCommand is null && RightButtonCommand is null)
return;

Remove(m_buttonsContainer);
int targetColumn;
int targetRow;

if (IsLargeAlert)
{
m_buttonsContainer.Margin = new Thickness(0, Sizes.GetSize(SizeName.content_margin_small), 0, 0);
this.Add(m_buttonsContainer, 1, 1);
return;
m_buttonsContainer.HorizontalOptions = LayoutOptions.Start;
targetColumn = 1;
targetRow = 1;
}
else
{
// Temporarily hide buttons so they don't affect the alert width measurement
var wasVisible = m_buttonsContainer.IsVisible;
m_buttonsContainer.IsVisible = false;

var maxWidth = Measure(int.MaxValue, int.MaxValue).Width;
var buttonsWidth = m_buttonsContainer.Measure(int.MaxValue, int.MaxValue).Width;
var remainingWidth = Width - maxWidth - buttonsWidth;

m_buttonsContainer.IsVisible = wasVisible;

var buttonsWillFit = remainingWidth >= (Sizes.GetSize(SizeName.content_margin_small));

if (buttonsWillFit)
{
m_buttonsContainer.Margin = new Thickness(0, 0, 0, 0);
m_buttonsContainer.HorizontalOptions = LayoutOptions.End;
targetColumn = 2;
targetRow = 0;
}
else
{
m_buttonsContainer.Margin = new Thickness(0, Sizes.GetSize(SizeName.content_margin_small), 0, 0);
m_buttonsContainer.HorizontalOptions = LayoutOptions.Start;
targetColumn = 1;
targetRow = 1;
}
}

var maxWidth = Measure(int.MaxValue, int.MaxValue).Width;
var buttonsWidth = m_buttonsContainer.Measure(int.MaxValue, int.MaxValue).Width;
var remainingWidth = Width - maxWidth - buttonsWidth;

var buttonsWillFit = remainingWidth >= (Sizes.GetSize(SizeName.content_margin_small));

if (buttonsWillFit)
if (!m_buttonsContainerAdded)
{
m_buttonsContainer.Margin = new Thickness(0, 0, 0, 0);
m_buttonsContainer.HorizontalOptions = LayoutOptions.End;
this.Add(m_buttonsContainer, 2);
this.Add(m_buttonsContainer, targetColumn, targetRow);
m_buttonsContainerAdded = true;
}
else
else if (targetColumn != m_lastButtonColumn || targetRow != m_lastButtonRow)
{
m_buttonsContainer.Margin = new Thickness(0, Sizes.GetSize(SizeName.content_margin_small), 0, 0);
m_buttonsContainer.HorizontalOptions = LayoutOptions.Start;
this.Add(m_buttonsContainer, 1, 1);
Grid.SetColumn(m_buttonsContainer, targetColumn);
Grid.SetRow(m_buttonsContainer, targetRow);
}

#if __ANDROID__
// Workaround for Android layout issue where buttons is not visible in some cases
InvalidateMeasure();
#endif

m_lastButtonColumn = targetColumn;
m_lastButtonRow = targetRow;
}

private void OnButtonChanged()
Expand All @@ -132,6 +163,8 @@ private void OnButtonChanged()
m_buttonsContainer.Add(CreateButton(RightButtonText, RightButtonCommand, RightButtonCommandParameter, "RightButton".ToDUIAutomationId<AlertView>()));
}

// Reset width tracking to force recalculation of button alignment
m_lastAllocatedWidth = -1;
UpdateButtonAlignment();
UpdateAccessibility();
}
Expand Down Expand Up @@ -206,6 +239,8 @@ private void OnTitleOrDescriptionChanged()

this.Add(m_titleAndDescriptionLabel, 1);

// Reset width tracking to force recalculation of button alignment
m_lastAllocatedWidth = -1;
UpdateButtonAlignment();
UpdateAccessibility();
}
Expand Down Expand Up @@ -317,7 +352,8 @@ private void OnIconChanged()
HeightRequest = Sizes.GetSize(SizeName.size_6),
WidthRequest = Sizes.GetSize(SizeName.size_6),
VerticalOptions = IsLargeAlert ? LayoutOptions.Start : LayoutOptions.Center,
Source = Icon
Source = Icon,
InputTransparent = true
};

// Hide icon from screen readers since alert type is already announced
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ private void AddBackButtonAndTitleLabel()
Source = Icons.GetIcon(IconName.chevron_left_line),
WidthRequest = Sizes.GetSize(SizeName.size_4),
HeightRequest = Sizes.GetSize(SizeName.size_4),
Margin = new Thickness(0, 0, Sizes.GetSize(SizeName.content_margin_medium), 0)
Margin = new Thickness(0, 0, Sizes.GetSize(SizeName.content_margin_medium), 0),
InputTransparent = true
};

var titleLabel = new Label
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public NavigationListItem()
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.End,
TintColor = Colors.GetColor(ColorName.color_icon_subtle),
InputTransparent = true
}, 1);

TitleOptions = new TitleOptions()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,13 @@ public bool DisableInternalAccessibility
nameof(HasTopDivider),
typeof(bool),
typeof(ListItem),
propertyChanged: ((bindable, _, _) => ((ListItem)bindable).AddDivider(true)));
propertyChanged: ((bindable, _, _) => ((ListItem)bindable).UpdateDivider(true)));

public static readonly BindableProperty HasBottomDividerProperty = BindableProperty.Create(
nameof(HasBottomDivider),
typeof(bool),
typeof(ListItem),
propertyChanged: ((bindable, _, _) => ((ListItem)bindable).AddDivider(false)));
propertyChanged: ((bindable, _, _) => ((ListItem)bindable).UpdateDivider(false)));

public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
nameof(CommandParameter),
Expand Down
35 changes: 22 additions & 13 deletions src/library/DIPS.Mobile.UI/Components/ListItems/ListItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public partial class ListItem : Grid
{
AutomationId = "TitleAndSubtitleContainer".ToDUIAutomationId<ListItem>(),
VerticalOptions = LayoutOptions.Center,
InputTransparent = true,
RowDefinitions =
new RowDefinitionCollection(new RowDefinition(GridLength.Star), new RowDefinition(GridLength.Auto))
};
Expand Down Expand Up @@ -94,7 +95,7 @@ private void AddIcon()
if (ImageIcon is not null)
return;

ImageIcon = new Image();
ImageIcon = new Image { InputTransparent = true };
ImageIcon.SetBinding(Microsoft.Maui.Controls.Image.SourceProperty, static (ListItem listItem) => listItem.Icon, source: this);

SetDefaultValuesOrBindToOptions(IconOptions, () =>
Expand Down Expand Up @@ -142,25 +143,31 @@ private void AddUnderlyingContent()
m_oldUnderlyingContent = UnderlyingContent;
}

private void AddDivider(bool top)
private void UpdateDivider(bool top)
{
var divider = new Divider();
var shouldShow = top ? HasTopDivider : HasBottomDivider;

if (top)
{
if (Contains(TopDivider))
Remove(TopDivider);

TopDivider = divider;
TopDivider.VerticalOptions = LayoutOptions.Start;
if (TopDivider is null)
{
TopDivider = CreateAndAddDivider(LayoutOptions.Start);
}
TopDivider.IsVisible = shouldShow;
}
else
{
if (Contains(BottomDivider))
Remove(BottomDivider);

BottomDivider = divider;
BottomDivider.VerticalOptions = LayoutOptions.End;
if (BottomDivider is null)
{
BottomDivider = CreateAndAddDivider(LayoutOptions.End);
}
BottomDivider.IsVisible = shouldShow;
}
}

private Divider CreateAndAddDivider(LayoutOptions verticalOptions)
{
var divider = new Divider { VerticalOptions = verticalOptions };

this.SetRowSpan(divider, RowDefinitions.Count);
this.SetColumnSpan(divider, ColumnDefinitions.Count);
Expand All @@ -171,6 +178,8 @@ private void AddDivider(bool top)
{
Options.Dividers.DividersOptions.SetupDefaults(this);
});

return divider;
}

private void AddTouch()
Expand Down
39 changes: 28 additions & 11 deletions src/library/DIPS.Mobile.UI/Components/Tabs/iOS/Tab.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using DIPS.Mobile.UI.Converters.ValueConverters;
using DIPS.Mobile.UI.Effects.Touch;
using Colors = DIPS.Mobile.UI.Resources.Colors.Colors;
using VerticalStackLayout = DIPS.Mobile.UI.Components.Lists.VerticalStackLayout;

namespace DIPS.Mobile.UI.Components.Tabs
{
Expand All @@ -18,19 +17,36 @@ public Tab()

internal void ConstructView()
{
var container = new VerticalStackLayout();
var titleContainer = new HorizontalStackLayout()
// Single Grid replaces nested VerticalStackLayout + HorizontalStackLayout
var container = new Grid
{
Spacing = Sizes.GetSize(SizeName.size_1),
HorizontalOptions = LayoutOptions.Center,
Padding = new Thickness(Sizes.GetSize(SizeName.size_3), Sizes.GetSize(SizeName.size_4), Sizes.GetSize(SizeName.size_3), Sizes.GetSize(SizeName.size_1))
ColumnDefinitions =
[
new ColumnDefinition(GridLength.Star),
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(GridLength.Auto),
new ColumnDefinition(GridLength.Star)
],
RowDefinitions =
[
new RowDefinition(GridLength.Star),
new RowDefinition(GridLength.Auto)
]
};

m_titleLabel = CreateTitleLabel();
m_counterLabel = CreateCounterLabel();

titleContainer.Add(m_titleLabel);
titleContainer.Add(m_counterLabel);
container.Add(titleContainer);

// Bottom margin combines the original HorizontalStackLayout's bottom padding (size_1)
// with VerticalStackLayout's inter-child spacing (content_margin_xsmall)
var bottomMargin = Sizes.GetSize(SizeName.size_1) + Sizes.GetSize(SizeName.content_margin_xsmall);
// Top margin replicates the original HorizontalStackLayout's top padding (size_4)
// Counter left margin replicates the original HorizontalStackLayout's Spacing (size_1)
m_titleLabel.Margin = new Thickness(0, Sizes.GetSize(SizeName.size_4), 0, bottomMargin);
m_counterLabel.Margin = new Thickness(Sizes.GetSize(SizeName.size_1), Sizes.GetSize(SizeName.size_4), 0, bottomMargin);

container.Add(m_titleLabel, 1, 0);
container.Add(m_counterLabel, 2, 0);

var boxView = new BoxView
{
Expand All @@ -39,7 +55,8 @@ internal void ConstructView()
BackgroundColor = Colors.GetColor(ColorName.color_border_button_active)
};
boxView.SetBinding(IsVisibleProperty, static (Tab tab) => tab.IsSelected, source: this);
container.Add(boxView);
container.Add(boxView, 0, 1);
Grid.SetColumnSpan(boxView, 4);

m_border = new Border
{
Expand Down