Skip to content
Open
195 changes: 195 additions & 0 deletions src/Controls/samples/Controls.Sample/Pages/Core/ToolbarBadgePage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
using System;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Maui.Controls.Sample.Pages.Base;

namespace Maui.Controls.Sample.Pages;

public class ToolbarBadgePage : BasePage
{
readonly ToolbarItem _numericItem;
readonly ToolbarItem _textItem;
readonly ToolbarItem _colorItem;
readonly Label _statusLabel;
int _count;

public ToolbarBadgePage()
{
// Remove default Settings toolbar item from BasePage and re-add with badge
ToolbarItems.Clear();

_numericItem = new ToolbarItem
{
Text = "Alerts",
IconImageSource = new FontImageSource
{
FontFamily = "Ionicons",
Glyph = "\uf2e3",
Color = Colors.Black
},
BadgeText = "3"
};
_numericItem.Clicked += (s, e) => _statusLabel.Text = "Tapped: Alerts";

_textItem = new ToolbarItem
{
Text = "Messages",
IconImageSource = new FontImageSource
{
FontFamily = "Ionicons",
Glyph = "\uf30c",
Color = Colors.Black
},
BadgeText = "New"
};
_textItem.Clicked += (s, e) => _statusLabel.Text = "Tapped: Messages";

_colorItem = new ToolbarItem
{
Text = "Cart",
IconImageSource = new FontImageSource
{
FontFamily = "Ionicons",
Glyph = "\uf30d",
Color = Colors.Black
},
BadgeText = "2",
BadgeColor = Colors.Green
};
_colorItem.Clicked += (s, e) => _statusLabel.Text = "Tapped: Cart";

ToolbarItems.Add(_numericItem);
ToolbarItems.Add(_textItem);
ToolbarItems.Add(_colorItem);

_count = 3;

_statusLabel = new Label
{
Text = "Toolbar items above have badges. Use buttons to interact.",
FontSize = 16,
Margin = new Thickness(0, 0, 0, 20)
};

Content = new ScrollView
{
Content = new VerticalStackLayout
{
Spacing = 12,
Padding = 20,
Children =
{
_statusLabel,
CreateSection("Badge Count",
new Button { Text = "Increment Count", Command = new Command(IncrementCount) },
new Button { Text = "Decrement Count", Command = new Command(DecrementCount) },
new Button { Text = "Set Large Count (99+)", Command = new Command(() => SetBadgeText(_numericItem, "99+")) }
),
CreateSection("Badge Text",
new Button { Text = "Set 'New'", Command = new Command(() => SetBadgeText(_textItem, "New")) },
new Button { Text = "Set '!'", Command = new Command(() => SetBadgeText(_textItem, "!")) },
new Button { Text = "Set Empty (dot badge)", Command = new Command(() => SetBadgeText(_textItem, "")) }
),
CreateSection("Badge Color",
new Button { Text = "Red", Command = new Command(() => SetBadgeColor(Colors.Red)) },
new Button { Text = "Blue", Command = new Command(() => SetBadgeColor(Colors.Blue)) },
new Button { Text = "Green", Command = new Command(() => SetBadgeColor(Colors.Green)) },
new Button { Text = "Platform Default (null)", Command = new Command(() => SetBadgeColor(null)) }
),
CreateSection("Badge Text Color",
new Button { Text = "White Text", Command = new Command(() => SetBadgeTextColor(Colors.White)) },
new Button { Text = "Black Text", Command = new Command(() => SetBadgeTextColor(Colors.Black)) },
new Button { Text = "Yellow Text", Command = new Command(() => SetBadgeTextColor(Colors.Yellow)) },
new Button { Text = "Platform Default (null)", Command = new Command(() => SetBadgeTextColor(null)) }
),
CreateSection("Visibility",
new Button { Text = "Clear All Badges", Command = new Command(ClearAll) },
new Button { Text = "Restore All Badges", Command = new Command(RestoreAll) }
),
new Label
{
Text = "Platform Notes:\n" +
"• Android: Full support via Material BadgeDrawable\n" +
"• iOS/macOS: Requires iOS 26+ / macOS 26+\n" +
"• Windows: Non-numeric text shows as dot indicator",
FontSize = 12,
TextColor = Colors.Gray,
Margin = new Thickness(0, 20, 0, 0)
}
}
}
};
}

static Border CreateSection(string title, params View[] children)
{
var stack = new VerticalStackLayout { Spacing = 8 };
stack.Children.Add(new Label { Text = title, FontAttributes = FontAttributes.Bold, FontSize = 14 });
foreach (var child in children)
stack.Children.Add(child);

return new Border
{
Content = stack,
Padding = 12,
Margin = new Thickness(0, 4),
StrokeShape = new Microsoft.Maui.Controls.Shapes.RoundRectangle { CornerRadius = 8 },
Stroke = Colors.LightGray
};
}

void IncrementCount()
{
_count++;
_numericItem.BadgeText = _count.ToString();
_statusLabel.Text = $"Count: {_count}";
}

void DecrementCount()
{
_count = Math.Max(0, _count - 1);
_numericItem.BadgeText = _count > 0 ? _count.ToString() : null;
_statusLabel.Text = _count > 0 ? $"Count: {_count}" : "Count badge cleared (0)";
}

void SetBadgeText(ToolbarItem item, string text)
{
item.BadgeText = text;
_statusLabel.Text = string.IsNullOrEmpty(text) ? "Badge text: (empty/dot)" : $"Badge text: '{text}'";
}

void SetBadgeColor(Color? color)
{
_colorItem.BadgeColor = color;
_statusLabel.Text = color is null ? "Badge color: platform default" : $"Badge color: {color}";
}

void SetBadgeTextColor(Color? color)
{
// Apply text color to all toolbar items to demonstrate the effect
_numericItem.BadgeTextColor = color;
_textItem.BadgeTextColor = color;
_colorItem.BadgeTextColor = color;
_statusLabel.Text = color is null ? "Badge text color: platform default" : $"Badge text color: {color}";
}

void ClearAll()
{
_numericItem.BadgeText = null;
_textItem.BadgeText = null;
_colorItem.BadgeText = null;
_count = 0;
_statusLabel.Text = "All badges cleared";
}

void RestoreAll()
{
_count = 3;
_numericItem.BadgeText = "3";
_textItem.BadgeText = "New";
_colorItem.BadgeText = "2";
_colorItem.BadgeColor = Colors.Green;
_statusLabel.Text = "Badges restored";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
new SectionModel(typeof(ToolbarPage), "Toolbar",
"Toolbar items are buttons that are typically displayed in the navigation bar."),

new SectionModel(typeof(ToolbarBadgePage), "Toolbar Badges",
"Badge notifications on toolbar items using BadgeText and BadgeColor properties."),

new SectionModel(typeof(TransformationsPage), "Transformations",
"Apply scale transformations, rotation, etc. to a View."),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public PrimaryToolbarItem(ToolbarItem item, bool forceName)

Clicked += OnClicked;
item.PropertyChanged += OnPropertyChanged;
UpdateBadge(item);

if (item != null && !string.IsNullOrEmpty(item.AutomationId))
AccessibilityIdentifier = item.AutomationId;
Expand Down Expand Up @@ -152,6 +153,8 @@ void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
UpdateTextAndStyle(item);
}
}
else if (e.PropertyName == nameof(ToolbarItem.BadgeText) || e.PropertyName == nameof(ToolbarItem.BadgeColor) || e.PropertyName == nameof(ToolbarItem.BadgeTextColor))
UpdateBadge(item);
#pragma warning disable CS0618 // Type or member is obsolete
else if (e.PropertyName == AutomationProperties.HelpTextProperty.PropertyName)
this.SetAccessibilityHint(item);
Expand Down Expand Up @@ -195,6 +198,41 @@ void UpdateTextAndStyle(ToolbarItem item)
#pragma warning restore CA1416, CA1422
Image = null;
}

void UpdateBadge(ToolbarItem item)
{
// UIBarButtonItem.Badge is only available on iOS 26+ / MacCatalyst 26+
if (!OperatingSystem.IsIOSVersionAtLeast(26) && !OperatingSystem.IsMacCatalystVersionAtLeast(26))
return;

var badgeText = item.BadgeText;

if (badgeText is null)
{
#pragma warning disable CA1416 // Validate platform compatibility
this.Badge = null;
#pragma warning restore CA1416
return;
}

#pragma warning disable CA1416 // Validate platform compatibility
UIBarButtonItemBadge badge;
if (badgeText.Length == 0)
badge = UIBarButtonItemBadge.Create(0); // Empty string shows as dot indicator
else if (int.TryParse(badgeText, out var count) && count >= 0)
badge = UIBarButtonItemBadge.Create((nuint)count);
else
badge = UIBarButtonItemBadge.Create(badgeText);

if (item.BadgeColor is not null)
badge.BackgroundColor = item.BadgeColor.ToPlatform();

if (item.BadgeTextColor is not null)
badge.ForegroundColor = item.BadgeTextColor.ToPlatform();

this.Badge = badge;
#pragma warning restore CA1416
}
}

internal sealed class SecondarySubToolbarItem
Expand Down
Loading
Loading