diff --git a/README.md b/README.md index 16478a2..dae0339 100644 --- a/README.md +++ b/README.md @@ -1 +1,484 @@ -# SimpleShell \ No newline at end of file +# SimpleToolkit + +SimpleToolkit is a .NET MAUI library of helpers and simple, easily customizable controls. + +The library consists of three packages: + +- [SimpleToolkit.Core](#simpletoolkitcore) + +- [SimpleToolkit.SimpleShell](#simpletoolkitsimpleshell) + +- [SimpleToolkit.SimpleShell.Controls](#simpletoolkitsimpleshellcontrols) + +> ⚠ **Warning:** Long-term support is not guaranteed. Use at your own risk. + +## Samples + +Here are some of my samples that were built using this library: + +

+ +

+

+ Gadget Store App +

+

+ +         + +

+

+ Navbar Animation #1 +

+

+ +         + +

+

+ Navbar Animation #2 +

+ +## SimpleToolkit.Core + +[![NuGet](https://img.shields.io/nuget/v/SimpleToolkit.Core.svg?label=NuGet)](https://www.nuget.org/packages/SimpleToolkit.Core/) + +The *SimpleToolkit.Core* package is a set of simple .NET MAUI controls and helpers. + +### Getting Started + +In order to use *SimpleToolkit.Core*, you need to call the `UseSimpleToolkit()` extension method in your `MauiProgram.cs` file: + +```csharp +builder.UseSimpleToolkit(); +``` + +### Icon + +Thanks to the `Icon` control, you are able to display a tinted image: + +```xml + + + +``` + +Output: + +

+ +

+ +#### Implementation details + +The `Icon` class is inherited from the .NET MAUI `Image` class, but behind the scenes it is implemented in the same way as .NET MAUI `Image` only on Android and iOS. WinUI implementation is based on `BitmapIcon` and `FontIcon` controls. Because of that, the control supports only these image sources on Windows: + +- `FileImageSource` +- `UriImageSource` +- `FontImageSource` + +These `Image` properties are not supported at all: + +- `Aspect` - the default behavior is `AspectFit` +- `IsAnimationPlaying` +- `IsLoading` +- `IsOpaque` + +### ContentButton + +`ContentButton` is just a button that can hold whatever content you want: + +```xml + + + + + + + + + + +``` + +Output: + +

+ +

+ +#### Implementation details + +The `ContentButton` class is inherited from the .NET MAUI `ContentView` control. `ContentButton` has these events in addition to `ContentView`s events and properties: + +- `Clicked` - an event that fires when the button is clicked +- `Pressed` - an event that fires when the button is pressed +- `Released` - an event that fires when the button is released + +### Popover + +`Popover` allows you to display custom popovers (flyouts) anchored to any control: + +```xml + +``` + +Code behind: + +```csharp +private void ButtonClicked(object sender, EventArgs e) +{ + var button = sender as Button; + + button.ShowAttachedPopover(); +} +``` + +Output: + +

+ + + + + + + + + + + +
+

Android

+
+

iOS

+
+

Windows

+
+ + + + + +
+

+ +#### Implementation details + +The `Popover` class is inherited from the .NET MAUI `Element` class. `Popover` offers these properties and methods in addition to `Element`s properties and methods: + +- `Content` - the popover content of type `View` +- `Show()` - shows the popover anchored to a view you pass as a parameter +- `Hide()` - hides the popover + +Use of the methods mentioned above: + +```csharp +popover.Show(anchorView); +popover.Hide(); +``` + +Popover can be attached to a view using the `AttachedPopover` attached property. Such a popover can be displayed or hidden (dismissed) by calling the `ShowAttachedPopover()` and `HideAttachedPopover()` extension methods on the view: + +```csharp +button.ShowAttachedPopover(); +button.HideAttachedPopover(); +``` + +## SimpleToolkit.SimpleShell + +[![NuGet](https://img.shields.io/nuget/v/SimpleToolkit.SimpleShell.svg?label=NuGet)](https://www.nuget.org/packages/SimpleToolkit.SimpleShell/) + +The *SimpleToolkit.SimpleShell* package provides you with a simplified implementation of .NET MAUI `Shell` that lets you easily create a custom navigation experience in your .NET MAUI applications. + +### Getting Started + +In order to use *SimpleToolkit.SimpleShell*, you need to call the `UseSimpleShell()` extension method in your `MauiProgram.cs` file: + +```csharp +builder.UseSimpleShell(); +``` + +### SimpleShell + +`SimpleShell` is a simplified implementation of .NET MAUI `Shell`. All `SimpleShell` is is just a simple container for your content with the ability to put the hosting area for pages wherever you want. Thanks to that, you are able to add custom tab bars, navigation bars, flyouts, etc. to your `Shell` application while using great `Shell` URI-based navigation. + +```xml + + + + + + + + + + + - - - - + + + + + + + - - - - - + \ No newline at end of file diff --git a/src/SimpleToolkit.Playground/Views/Pages/PopoverPage.xaml.cs b/src/SimpleToolkit.Playground/Views/Pages/PopoverPage.xaml.cs new file mode 100644 index 0000000..d74d939 --- /dev/null +++ b/src/SimpleToolkit.Playground/Views/Pages/PopoverPage.xaml.cs @@ -0,0 +1,19 @@ +using SimpleToolkit.Core; + +namespace SimpleToolkit.SimpleShell.Playground.Views.Pages +{ + public partial class PopoverPage : ContentPage + { + public PopoverPage() + { + InitializeComponent(); + } + + private void ButtonClicked(object sender, EventArgs e) + { + var button = sender as Button; + + button.ShowAttachedPopover(); + } + } +} diff --git a/src/SimpleToolkit.SimpleShell.Controls/AttachedProperties.cs b/src/SimpleToolkit.SimpleShell.Controls/AttachedProperties.cs index fd32f78..5e62b7b 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/AttachedProperties.cs +++ b/src/SimpleToolkit.SimpleShell.Controls/AttachedProperties.cs @@ -1,18 +1,30 @@ namespace SimpleToolkit.SimpleShell.Controls { - public static class SimpleIcon + public static class SimpleShellIcon { public static readonly BindableProperty SelectedIconProperty = BindableProperty.CreateAttached("SelectedIcon", typeof(ImageSource), typeof(BaseShellItem), null); - public static ImageSource GetSelectedIcon(BindableObject view) + /// + /// Returns an of an image that should be displayed when the item is selected. + /// + /// The item to which the is attached. + /// The that is attached to the item. + public static ImageSource GetSelectedIcon(BindableObject item) { - return (ImageSource)view.GetValue(SelectedIconProperty); + _ = item ?? throw new ArgumentNullException(nameof(item)); + return (ImageSource)item.GetValue(SelectedIconProperty); } - public static void SetSelectedIcon(BindableObject view, ImageSource value) + /// + /// Attaches to the item an of an image that should be displayed when the item is selected. + /// + /// The item to which the will be attached. + /// The of an image that will be attached to the item. + public static void SetSelectedIcon(BindableObject item, ImageSource value) { - view.SetValue(SelectedIconProperty, value); + _ = item ?? throw new ArgumentNullException(nameof(item)); + item.SetValue(SelectedIconProperty, value); } } } diff --git a/src/SimpleToolkit.SimpleShell.Controls/DesignLanguage.cs b/src/SimpleToolkit.SimpleShell.Controls/DesignLanguage.cs index 84de18d..97da670 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/DesignLanguage.cs +++ b/src/SimpleToolkit.SimpleShell.Controls/DesignLanguage.cs @@ -1,5 +1,8 @@ namespace SimpleToolkit.SimpleShell.Controls { + /// + /// Enumeration of supported design languages + /// public enum DesignLanguage { Material3, Cupertino, Fluent diff --git a/src/SimpleToolkit.SimpleShell.Controls/Extensions/ColorExtensions.cs b/src/SimpleToolkit.SimpleShell.Controls/Extensions/ColorExtensions.cs index 9098f86..e55c734 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/Extensions/ColorExtensions.cs +++ b/src/SimpleToolkit.SimpleShell.Controls/Extensions/ColorExtensions.cs @@ -6,7 +6,7 @@ public static Brush OffsetBrushColorValue(this Brush brush, float valueOffset) { if (brush is SolidColorBrush solidColorBrush) { - return new SolidColorBrush(solidColorBrush.Color.OffsetColorValue(valueOffset)); + return new SolidColorBrush(solidColorBrush.Color?.OffsetColorValue(valueOffset)); } else if (brush is LinearGradientBrush linearGradientBrush) { @@ -26,7 +26,7 @@ public static GradientStopCollection OffsetStopsCollectionColorValue(this Gradie foreach (var stop in stops) { - stops.Add(new GradientStop(stop.Color.OffsetColorValue(valueOffset), stop.Offset)); + stops.Add(new GradientStop(stop.Color?.OffsetColorValue(valueOffset), stop.Offset)); } return newStops; diff --git a/src/SimpleToolkit.SimpleShell.Controls/ListPopoverItemClickedEventArgs.cs b/src/SimpleToolkit.SimpleShell.Controls/ListPopoverItemClickedEventArgs.cs index 908a411..f76b6e5 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/ListPopoverItemClickedEventArgs.cs +++ b/src/SimpleToolkit.SimpleShell.Controls/ListPopoverItemClickedEventArgs.cs @@ -2,8 +2,14 @@ { public delegate void ListPopoverItemSelectedEventHandler(object sender, ListPopoverItemSelectedEventArgs e); + /// + /// Arguments of selection event. + /// public class ListPopoverItemSelectedEventArgs : EventArgs { + /// + /// The selected item. + /// public object Item { get; internal set; } } } \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell.Controls/SimpleToolkit - Backup.SimpleShell.Controls.csproj b/src/SimpleToolkit.SimpleShell.Controls/SimpleToolkit - Backup.SimpleShell.Controls.csproj new file mode 100644 index 0000000..bdfeef9 --- /dev/null +++ b/src/SimpleToolkit.SimpleShell.Controls/SimpleToolkit - Backup.SimpleShell.Controls.csproj @@ -0,0 +1,62 @@ + + + + net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst + $(TargetFrameworks);net6.0-windows10.0.19041.0 + + + true + true + enable + + 14.2 + 14.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + 6.5 + + SimpleToolkit.SimpleShell.Controls + SimpleToolkit.SimpleShell.Controls + LICENSE + MAUI, Controls, Shell, Navigation + RadekVyM + ..\..\packages + 1.0.0 + Collection of ready-to-use, navigation-related controls that is part of SimpleToolkit library. These controls work well with SimpleShell. + https://github.com/RadekVyM/SimpleToolkit + https://github.com/RadekVyM/SimpleToolkit + git + logo_with_background.png + + + + + + + + + + + + + + + + + + + + + + + True + \ + + + True + \ + + + + diff --git a/src/SimpleToolkit.SimpleShell.Controls/SimpleToolkit.SimpleShell.Controls.csproj b/src/SimpleToolkit.SimpleShell.Controls/SimpleToolkit.SimpleShell.Controls.csproj index 4786023..7b7e29f 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/SimpleToolkit.SimpleShell.Controls.csproj +++ b/src/SimpleToolkit.SimpleShell.Controls/SimpleToolkit.SimpleShell.Controls.csproj @@ -15,20 +15,51 @@ 10.0.17763.0 10.0.17763.0 6.5 + + SimpleToolkit.SimpleShell.Controls + SimpleToolkit.SimpleShell.Controls + LICENSE + MAUI, Controls, Shell, Navigation + RadekVyM + ..\..\packages + 1.0.0 + -preview1 + $(VersionPrefix)$(VersionSuffix) + Collection of ready-to-use, navigation-related controls that is part of SimpleToolkit library. These controls work well with SimpleShell. + https://github.com/RadekVyM/SimpleToolkit + https://github.com/RadekVyM/SimpleToolkit + git + logo_with_background.png + Copyright © RadekVyM and contributors - - - - - - - + + + + + + + + + + + + + + + - + + True + \ + + + True + \ + diff --git a/src/SimpleToolkit.SimpleShell.Controls/TabItemSelectedEventArgs.cs b/src/SimpleToolkit.SimpleShell.Controls/TabItemSelectedEventArgs.cs index 8da9c00..915491a 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/TabItemSelectedEventArgs.cs +++ b/src/SimpleToolkit.SimpleShell.Controls/TabItemSelectedEventArgs.cs @@ -2,8 +2,14 @@ { public delegate void TabItemSelectedEventHandler(object sender, TabItemSelectedEventArgs e); + /// + /// Arguments of a tab selection event. + /// public class TabItemSelectedEventArgs : EventArgs { + /// + /// The selected item. + /// public BaseShellItem ShellItem { get; internal set; } } } diff --git a/src/SimpleToolkit.SimpleShell.Controls/Views/ListPopover/IListPopover.cs b/src/SimpleToolkit.SimpleShell.Controls/Views/ListPopover/IListPopover.cs new file mode 100644 index 0000000..3bf31d4 --- /dev/null +++ b/src/SimpleToolkit.SimpleShell.Controls/Views/ListPopover/IListPopover.cs @@ -0,0 +1,27 @@ +using SimpleToolkit.Core; + +namespace SimpleToolkit.SimpleShell.Controls +{ + /// + /// Popover containing a list of items that is styled according to the selected . + /// + public interface IListPopover : IPopover + { + IEnumerable Items { get; set; } + DesignLanguage DesignLanguage { get; set; } + Brush Background { get; set; } + Color IconColor { get; set; } + Color IconSelectionColor { get; set; } + double MaximumWidthRequest { get; set; } + double MinimumWidthRequest { get; set; } + object SelectedItem { get; set; } + Brush SelectionBrush { get; set; } + Color TextColor { get; set; } + Color TextSelectionColor { get; set; } + + /// + /// Event that fires when an item is selected. + /// + event ListPopoverItemSelectedEventHandler ItemSelected; + } +} \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell.Controls/Views/ListPopover/ListPopover.Shared.cs b/src/SimpleToolkit.SimpleShell.Controls/Views/ListPopover/ListPopover.Shared.cs index 8094b87..710b209 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/Views/ListPopover/ListPopover.Shared.cs +++ b/src/SimpleToolkit.SimpleShell.Controls/Views/ListPopover/ListPopover.Shared.cs @@ -3,10 +3,11 @@ namespace SimpleToolkit.SimpleShell.Controls { - // TODO: A11y - + /// + /// containing a list of items that is styled according to the selected . + /// [ContentProperty(nameof(Items))] - public partial class ListPopover : Popover + public partial class ListPopover : Popover, IListPopover { private Border rootBorder; private Grid rootGrid; @@ -168,8 +169,6 @@ private void SetUpBaseStructure() { VerticalOptions = LayoutOptions.Start, HorizontalOptions = LayoutOptions.Start, - StrokeThickness = 0, - Stroke = Colors.Transparent, Background = Background, Shadow = null, StrokeShape = new RoundRectangle @@ -260,7 +259,7 @@ private ContentButton CreateButton(ListItem item) Style = new Style(typeof(ContentButton)), BindingContext = item, }; - + // TODO: Try to remove this one Grid var grid = new Grid { ColumnDefinitions = new ColumnDefinitionCollection( @@ -270,7 +269,6 @@ private ContentButton CreateButton(ListItem item) Style = new Style(typeof(Grid)), BindingContext = item, }; - var innerGrid = new Grid { ColumnDefinitions = new ColumnDefinitionCollection( @@ -281,7 +279,6 @@ private ContentButton CreateButton(ListItem item) Style = new Style(typeof(Grid)), BindingContext = item, }; - var label = new Label { Text = item.Title, @@ -316,7 +313,9 @@ private ContentButton CreateButton(ListItem item) button.Content = grid; - //CompressedLayout.SetIsHeadless(grid, true); + SemanticProperties.SetDescription(button, item.Title); + + CompressedLayout.SetIsHeadless(grid, true); CompressedLayout.SetIsHeadless(innerGrid, true); return button; @@ -490,6 +489,8 @@ private void ItemPropertyChanged(object sender, System.ComponentModel.PropertyCh label.Text = titleIcon.Title; image.Source = titleIcon.Icon; + + SemanticProperties.SetDescription(item, titleIcon.Title); } UpdateIconsVisibility(listItems); @@ -574,7 +575,7 @@ private static void OnTextColorChanged(BindableObject bindable, object oldValue, var grid = item.Content as Grid; var innerGrid = grid.Children[0] as Grid; var label = innerGrid.Children[1] as Label; - + if (newValue is not null) label.TextColor = newValue as Color; } @@ -625,7 +626,10 @@ private static void OnTextSelectionColorChanged(BindableObject bindable, object private static void OnBackgroundChanged(BindableObject bindable, object oldValue, object newValue) { var listPopover = bindable as ListPopover; - listPopover.rootBorder.Background = newValue as Brush; + var newBrush = newValue as Brush; + + listPopover.rootBorder.Stroke = newBrush.OffsetBrushColorValue(-0.1f); + listPopover.rootBorder.Background = newBrush; listPopover.InvalidateGraphicsView(); } diff --git a/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/ITabBar.cs b/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/ITabBar.cs new file mode 100644 index 0000000..968c1e2 --- /dev/null +++ b/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/ITabBar.cs @@ -0,0 +1,35 @@ +namespace SimpleToolkit.SimpleShell.Controls +{ + /// + /// Tab bar that is styled according to the selected . + /// + public interface ITabBar + { + IEnumerable Items { get; set; } + IReadOnlyList HiddenItems { get; } + BaseShellItem SelectedItem { get; set; } + DesignLanguage DesignLanguage { get; set; } + Color TextColor { get; set; } + Color TextSelectionColor { get; set; } + Color IconColor { get; set; } + Color IconSelectionColor { get; set; } + Brush PrimaryBrush { get; set; } + string MoreTitle { get; set; } + ImageSource MoreIcon { get; set; } + double ItemWidthRequest { get; set; } + bool IsScrollable { get; set; } + LayoutAlignment ItemsAlignment { get; set; } + bool ShowButtonWhenMoreItemsDoNotFit { get; set; } + bool ShowMenuOnMoreButtonClick { get; set; } + + /// + /// Event that fires when an item is selected. + /// + event TabItemSelectedEventHandler ItemSelected; + + /// + /// Event that fires when the "More" button is clicked. + /// + event EventHandler MoreButtonClicked; + } +} \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/TabBar.Fluent.cs b/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/TabBar.Fluent.cs index 77a4832..b290001 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/TabBar.Fluent.cs +++ b/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/TabBar.Fluent.cs @@ -46,6 +46,8 @@ private void UpdateDrawableToFluent() drawable.IsSelectedHiddenItem = isMoreButtonShown && selectedIndex == stackLayout.Count - 1; drawable.IconSize = iconSize; drawable.IconMargin = iconMargin; + drawable.AnimationProgressDone = 0; + drawable.AnimationProgressRest = 0; } private async Task AnimateFluentToSelected() @@ -73,13 +75,20 @@ private async Task AnimateFluentToSelected() if (toPosition < stackLayout.Count - 1) drawable.IsSelectedHiddenItem = false; + var needsToBeTraveled = toPosition - fromPosition; + drawable.AnimationDirection = needsToBeTraveled > 0; + needsToBeTraveled = Math.Abs(needsToBeTraveled); + var animation = new Animation(v => { drawable.SelectedItemRelativePosition = v; - drawable.AnimationProgress = Math.Abs((toPosition - fromPosition) / v); + drawable.AnimationProgressDone = Math.Abs(v - fromPosition); + drawable.AnimationProgressRest = needsToBeTraveled - drawable.AnimationProgressDone; + graphicsView.Invalidate(); }, fromPosition, toPosition); + graphicsView.AbortAnimation("FluentLineAnimation"); animation.Commit(graphicsView, "FluentLineAnimation", length: animationLength, easing: Easing.SinInOut); await Task.Delay((int)animationLength); @@ -92,7 +101,9 @@ private class FluentDrawable : IDrawable, IDisposable private float lineThickness = 4f; private float defaultLineWidth = 16f; - public double AnimationProgress { get; set; } + public bool AnimationDirection { get; set; } // left == false + public double AnimationProgressDone { get; set; } + public double AnimationProgressRest { get; set; } public Color IconColor { get; set; } public Brush LineBrush { get; set; } public IList Views { get; set; } @@ -157,6 +168,8 @@ public void Draw(ICanvas canvas, RectF dirtyRect) canvas.RestoreState(); } + RectF lastLineRect; + private void DrawLine(ICanvas canvas, RectF dirtyRect, float leftPadding) { double leftItemsWidth = 0; @@ -177,9 +190,11 @@ private void DrawLine(ICanvas canvas, RectF dirtyRect, float leftPadding) var selectedView = Views[flooredPosition] as View; var itemWidth = selectedView.Width; - var left = (float)(leftItemsWidth + ((itemWidth - defaultLineWidth) / 2) - ScrollPosition + leftPadding + ((SelectedItemRelativePosition - flooredPosition) * itemWidth)); + var defaultLeft = (float)(leftItemsWidth + ((itemWidth - defaultLineWidth) / 2) - ScrollPosition + leftPadding); + var left = (float)(defaultLeft + ((SelectedItemRelativePosition - flooredPosition) * itemWidth)); + var width = defaultLineWidth; - var lineRect = new RectF(left, dirtyRect.Height - bottomPadding - lineThickness, defaultLineWidth, lineThickness); + var lineRect = lastLineRect = new RectF(left, dirtyRect.Height - bottomPadding - lineThickness, width, lineThickness); canvas.SetFillPaint(LineBrush ?? Colors.Black, lineRect); diff --git a/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/TabBar.Shared.cs b/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/TabBar.Shared.cs index 7289ee0..352ef80 100644 --- a/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/TabBar.Shared.cs +++ b/src/SimpleToolkit.SimpleShell.Controls/Views/TabBar/TabBar.Shared.cs @@ -2,9 +2,10 @@ namespace SimpleToolkit.SimpleShell.Controls { - // TODO: A11y - - public partial class TabBar : ContentView, IView + /// + /// Tab bar that is styled according to the selected . + /// + public partial class TabBar : ContentView, IView, ITabBar { private HorizontalStackLayout stackLayout; private Grid rootGrid; @@ -224,11 +225,8 @@ private int GetSelectedItemIndex() return i; } - // TODO: Update selection of more button - if hidden item is selected, show more button as selected private bool IsSelected(BindableObject bindableObject) { - // (bindableObject == moreButton && hiddenItems.Any(h => h.BindingContext == SelectedItem)) - return bindableObject.BindingContext == SelectedItem || bindableObject == SelectedItem; } @@ -388,6 +386,8 @@ private ContentButton CreateButton(BaseShellItem item) button.Content = grid; + SemanticProperties.SetDescription(button, item.Title); + CompressedLayout.SetIsHeadless(stackLayout, true); CompressedLayout.SetIsHeadless(grid, true); @@ -518,7 +518,7 @@ private void UpdateButton(ContentButton button) if (IsSelected(shellItem)) { - var selectedIcon = SimpleIcon.GetSelectedIcon(shellItem); + var selectedIcon = SimpleShellIcon.GetSelectedIcon(shellItem); if (selectedIcon is not null && image.Source != selectedIcon) image.Source = selectedIcon; } @@ -837,7 +837,7 @@ private void ShellItemPropertyChanged(object sender, System.ComponentModel.Prope { if (e.PropertyName != BaseShellItem.TitleProperty.PropertyName && e.PropertyName != BaseShellItem.IconProperty.PropertyName) return; - + if (stackLayout is null) return; @@ -854,6 +854,8 @@ private void ShellItemPropertyChanged(object sender, System.ComponentModel.Prope image.Source = shellItem.Icon; image.TintColor = IsSelected(item) ? IconSelectionColor : IconColor; + + SemanticProperties.SetDescription(item, shellItem.Title); } } diff --git a/src/SimpleToolkit.SimpleShell/Extensions/AppHostBuilderExtensions.cs b/src/SimpleToolkit.SimpleShell/Extensions/AppHostBuilderExtensions.cs index 99faa2a..e3b6514 100644 --- a/src/SimpleToolkit.SimpleShell/Extensions/AppHostBuilderExtensions.cs +++ b/src/SimpleToolkit.SimpleShell/Extensions/AppHostBuilderExtensions.cs @@ -4,6 +4,11 @@ namespace SimpleToolkit.SimpleShell { public static class AppHostBuilderExtensions { + /// + /// Configures the SimpleToolkit.SimpleShell package. + /// + /// + /// public static MauiAppBuilder UseSimpleShell(this MauiAppBuilder builder) { builder.ConfigureMauiHandlers(handlers => diff --git a/src/SimpleToolkit.SimpleShell/Extensions/Extensions.cs b/src/SimpleToolkit.SimpleShell/Extensions/Extensions.cs index 623268c..a39a573 100644 --- a/src/SimpleToolkit.SimpleShell/Extensions/Extensions.cs +++ b/src/SimpleToolkit.SimpleShell/Extensions/Extensions.cs @@ -1,6 +1,6 @@ -namespace SimpleToolkit.SimpleShell +namespace SimpleToolkit.SimpleShell.Extensions { - internal static class Extensions + internal static class ElementExtensions { public static SimpleNavigationHost FindSimpleNavigationHost(this IView view) { diff --git a/src/SimpleToolkit.SimpleShell/Extensions/RoutingExtensions.cs b/src/SimpleToolkit.SimpleShell/Extensions/RoutingExtensions.cs index ac5fd06..afd5684 100644 --- a/src/SimpleToolkit.SimpleShell/Extensions/RoutingExtensions.cs +++ b/src/SimpleToolkit.SimpleShell/Extensions/RoutingExtensions.cs @@ -2,9 +2,8 @@ { internal static class RoutingExtensions { - const string ImplicitPrefix = "IMPL_"; - const string DefaultPrefix = "D_FAULT_"; - internal const string PathSeparator = "/"; + private const string ImplicitPrefix = "IMPL_"; + private const string DefaultPrefix = "D_FAULT_"; public static bool IsImplicit(BindableObject source) { diff --git a/src/SimpleToolkit.SimpleShell/Extensions/SimpleShellExtensions.cs b/src/SimpleToolkit.SimpleShell/Extensions/SimpleShellExtensions.cs new file mode 100644 index 0000000..0ac9553 --- /dev/null +++ b/src/SimpleToolkit.SimpleShell/Extensions/SimpleShellExtensions.cs @@ -0,0 +1,52 @@ +namespace SimpleToolkit.SimpleShell.Extensions +{ + internal static class SimpleShellExtensions + { + public static IEnumerable GetShellSections(this BaseShellItem baseShellItem) + { + var list = new HashSet(); + + if (baseShellItem is ShellSection shellSection) + { + list.Add(shellSection); + } + else if (baseShellItem is ShellItem shellItem) + { + foreach (var item in shellItem.Items) + { + var shellSections = GetShellSections(item); + foreach (var section in shellSections) + list.Add(section); + } + } + + return list; + } + + public static IEnumerable GetShellContents(this BaseShellItem baseShellItem) + { + var list = new HashSet(); + + if (baseShellItem is ShellContent shellContent) + { + list.Add(shellContent); + } + else if (baseShellItem is ShellItem shellItem) + { + foreach (var item in shellItem.Items) + { + var shellContents = GetShellContents(item); + foreach (var content in shellContents) + list.Add(content); + } + } + else if (baseShellItem is ShellSection shellSection) + { + foreach (var content in shellSection.Items) + list.Add(content); + } + + return list; + } + } +} diff --git a/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.Android.cs b/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.Android.cs index e369b17..bd36984 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.Android.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.Android.cs @@ -16,6 +16,9 @@ public partial class SimpleNavigationHostHandler : ViewHandler + { + public SimpleNavigationHostHandler(IPropertyMapper mapper, CommandMapper commandMapper) + : base(mapper, commandMapper) + { + } + + protected override System.Object CreatePlatformElement() + { + throw new NotImplementedException(); + } + } +} + +#endif \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.Windows.cs b/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.Windows.cs index 87c993d..455da46 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.Windows.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.Windows.cs @@ -1,13 +1,11 @@ - +#if WINDOWS + using Microsoft.Maui.Handlers; -#if WINDOWS using WBorder = Microsoft.UI.Xaml.Controls.Border; using WFrameworkElement = Microsoft.UI.Xaml.FrameworkElement; -#endif namespace SimpleToolkit.SimpleShell.Handlers { -#if WINDOWS public partial class SimpleNavigationHostHandler : ViewHandler { public static IPropertyMapper Mapper = new PropertyMapper(ViewHandler.ViewMapper) @@ -18,6 +16,9 @@ public partial class SimpleNavigationHostHandler : ViewHandler - { - public SimpleNavigationHostHandler(IPropertyMapper mapper, CommandMapper commandMapper) - : base(mapper, commandMapper) - { - } - - protected override System.Object CreatePlatformElement() - { - throw new NotImplementedException(); - } - } -#endif -} \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.iOS.cs b/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.iOS.cs index f6358b9..9545c25 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.iOS.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/NavigationHost/SimpleNavigationHostHandler.iOS.cs @@ -16,6 +16,9 @@ public partial class SimpleNavigationHostHandler : ViewHandler { protected virtual AView GetNavigationHostContent() { - return (navigationHost?.Handler as SimpleNavigationHostHandler)?.Container.GetChildAt(0); + return (navigationHost?.Handler as SimpleNavigationHostHandler)?.Container?.GetChildAt(0); } } } diff --git a/src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.Shared.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Shared.cs similarity index 77% rename from src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.Shared.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Shared.cs index 0d24f98..8290d50 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.Shared.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Shared.cs @@ -1,19 +1,20 @@ -using Microsoft.Maui.Handlers; +#if ANDROID || IOS || MACCATALYST || WINDOWS + +using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; +using SimpleToolkit.SimpleShell.Extensions; #if ANDROID using PlatformShell = Android.Views.View; -#elif __IOS__ || MACCATALYST +#elif IOS || MACCATALYST using PlatformShell = UIKit.UIView; #elif WINDOWS using PlatformShell = Microsoft.UI.Xaml.FrameworkElement; -#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID && !TIZEN) +#else using PlatformShell = System.Object; #endif namespace SimpleToolkit.SimpleShell.Handlers { -#if ANDROID || __IOS__ || MACCATALYST || WINDOWS - public partial class SimpleShellHandler { public static IPropertyMapper Mapper = new PropertyMapper(ViewHandler.ViewMapper) @@ -43,10 +44,7 @@ public SimpleShellHandler() protected override PlatformShell CreatePlatformView() { - if (VirtualView.Content is null) - { - throw new ArgumentNullException("Content property cannot be null"); - } + _ = VirtualView.Content ?? throw new ArgumentNullException("Content property cannot be null"); var content = VirtualView.Content.ToPlatform(MauiContext); @@ -90,26 +88,11 @@ protected virtual SimpleShellItemHandler CreateShellItemHandler() return itemHandler; } - private static void MapCurrentItem(SimpleShellHandler handler, ISimpleShell shell) + public static void MapCurrentItem(SimpleShellHandler handler, ISimpleShell shell) { handler.SwitchShellItem(shell.CurrentItem, true); } } - -#else - - public partial class SimpleShellHandler : ElementHandler - { - public SimpleShellHandler(IPropertyMapper mapper, CommandMapper commandMapper) - : base(mapper, commandMapper) - { - } - - protected override System.Object CreatePlatformElement() - { - throw new NotImplementedException(); - } - } - -#endif } + +#endif \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Standard.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Standard.cs new file mode 100644 index 0000000..5ca2c6c --- /dev/null +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Standard.cs @@ -0,0 +1,21 @@ +#if !(ANDROID || __IOS__ || MACCATALYST || WINDOWS) + +using Microsoft.Maui.Handlers; + +namespace SimpleToolkit.SimpleShell.Handlers +{ + public partial class SimpleShellHandler : ElementHandler + { + public SimpleShellHandler(IPropertyMapper mapper, CommandMapper commandMapper) + : base(mapper, commandMapper) + { + } + + protected override System.Object CreatePlatformElement() + { + throw new NotImplementedException(); + } + } +} + +#endif \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.Windows.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Windows.cs similarity index 93% rename from src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.Windows.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Windows.cs index 9c8660f..7062564 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.Windows.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.Windows.cs @@ -1,7 +1,6 @@ #if WINDOWS using Microsoft.Maui.Handlers; -using Microsoft.Maui.Platform; using WFrameworkElement = Microsoft.UI.Xaml.FrameworkElement; namespace SimpleToolkit.SimpleShell.Handlers diff --git a/src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.iOS.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.iOS.cs similarity index 92% rename from src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.iOS.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.iOS.cs index ba456d0..47c9cf4 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/Shell/SimpleShellHandler.iOS.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShell/SimpleShellHandler.iOS.cs @@ -1,4 +1,4 @@ -#if __IOS__ || MACCATALYST +#if IOS || MACCATALYST using Microsoft.Maui.Handlers; using UIKit; diff --git a/src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Android.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Android.cs similarity index 91% rename from src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Android.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Android.cs index db950cb..04d5d7e 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Android.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Android.cs @@ -6,7 +6,7 @@ namespace SimpleToolkit.SimpleShell.Handlers { - public partial class SimpleShellItemHandler : ElementHandler, IAppearanceObserver + public partial class SimpleShellItemHandler : ElementHandler { protected override CustomFrameLayout CreatePlatformElement() { diff --git a/src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Shared.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Shared.cs similarity index 54% rename from src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Shared.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Shared.cs index 2717b04..4440b6e 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Shared.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Shared.cs @@ -1,10 +1,7 @@ -using Microsoft.Maui.Handlers; -using Microsoft.Maui.Platform; +using Microsoft.Maui.Platform; #if ANDROID -using Microsoft.Maui.Controls.Platform.Compatibility; -using Google.Android.Material.Navigation; using SectionContainer = Microsoft.Maui.Controls.Platform.Compatibility.CustomFrameLayout; -#elif __IOS__ || MACCATALYST +#elif IOS || MACCATALYST using SectionContainer = UIKit.UIView; #elif WINDOWS using SectionContainer = Microsoft.UI.Xaml.Controls.Border; @@ -14,9 +11,7 @@ namespace SimpleToolkit.SimpleShell.Handlers { -#if ANDROID || __IOS__ || MACCATALYST || WINDOWS - - public partial class SimpleShellItemHandler + public partial class SimpleShellItemHandler : IAppearanceObserver { protected SectionContainer shellSectionContainer; protected ShellSection currentShellSection; @@ -44,38 +39,25 @@ public SimpleShellItemHandler() } + public virtual void OnAppearanceChanged(ShellAppearance appearance) + { + } + protected void UpdateCurrentItem() { if (currentShellSection == VirtualView.CurrentItem) return; - if (currentShellSection != null) - { + if (currentShellSection is not null) currentShellSection.PropertyChanged -= OnCurrentShellSectionPropertyChanged; - } currentShellSection = VirtualView.CurrentItem; - if (VirtualView.CurrentItem != null) + if (VirtualView.CurrentItem is not null) { currentShellSectionHandler ??= (SimpleShellSectionHandler)VirtualView.CurrentItem.ToHandler(MauiContext); -#if ANDROID - if (PlatformView != shellSectionContainer.GetChildAt(0)) - { - shellSectionContainer.RemoveAllViews(); - shellSectionContainer.AddView(currentShellSectionHandler.PlatformView); - } -#elif __IOS__ || MACCATALYST - if (PlatformView != (UIKit.UIView)shellSectionContainer.Subviews.FirstOrDefault()) - { - shellSectionContainer.ClearSubviews(); - shellSectionContainer.AddSubview(currentShellSectionHandler.PlatformView); - } -#elif WINDOWS - if (PlatformView != (Microsoft.UI.Xaml.Controls.Frame)shellSectionContainer.Child) - shellSectionContainer.Child = currentShellSectionHandler.PlatformView; -#endif + UpdateShellSectionContainerContent(); if (currentShellSectionHandler.VirtualView != VirtualView.CurrentItem) currentShellSectionHandler.SetVirtualView(VirtualView.CurrentItem); @@ -84,44 +66,41 @@ protected void UpdateCurrentItem() //UpdateSearchHandler(); //MapMenuItems(); - if (currentShellSection != null) - { + if (currentShellSection is not null) currentShellSection.PropertyChanged += OnCurrentShellSectionPropertyChanged; - } } - void OnCurrentShellSectionPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + private void UpdateShellSectionContainerContent() { - +#if ANDROID + if (PlatformView != shellSectionContainer.GetChildAt(0)) + { + shellSectionContainer.RemoveAllViews(); + shellSectionContainer.AddView(currentShellSectionHandler.PlatformView); + } +#elif IOS || MACCATALYST + if (PlatformView != (UIKit.UIView)shellSectionContainer.Subviews.FirstOrDefault()) + { + shellSectionContainer.ClearSubviews(); + shellSectionContainer.AddSubview(currentShellSectionHandler.PlatformView); + } +#elif WINDOWS + if (PlatformView != (Microsoft.UI.Xaml.Controls.Frame)shellSectionContainer.Child) + shellSectionContainer.Child = currentShellSectionHandler.PlatformView; +#endif } - public void OnAppearanceChanged(ShellAppearance appearance) + private void OnCurrentShellSectionPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { } - private static void MapTabBarIsVisible(SimpleShellItemHandler handler, ShellItem item) + public static void MapTabBarIsVisible(SimpleShellItemHandler handler, ShellItem item) { } - private static void MapCurrentItem(SimpleShellItemHandler handler, ShellItem item) + public static void MapCurrentItem(SimpleShellItemHandler handler, ShellItem item) { handler.UpdateCurrentItem(); } } - -#else - - public partial class SimpleShellItemHandler : ElementHandler - { - public SimpleShellItemHandler(IPropertyMapper mapper, CommandMapper commandMapper) - : base(mapper, commandMapper) - { - } - - protected override System.Object CreatePlatformElement() - { - throw new NotImplementedException(); - } - } -#endif } diff --git a/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Standard.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Standard.cs new file mode 100644 index 0000000..5e211bd --- /dev/null +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Standard.cs @@ -0,0 +1,16 @@ +#if !(ANDROID || IOS || MACCATALYST || WINDOWS) + +using Microsoft.Maui.Handlers; + +namespace SimpleToolkit.SimpleShell.Handlers +{ + public partial class SimpleShellItemHandler : ElementHandler + { + protected override System.Object CreatePlatformElement() + { + throw new NotImplementedException(); + } + } +} + +#endif \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Windows.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Windows.cs similarity index 75% rename from src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Windows.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Windows.cs index 7d30403..e4f62f8 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.Windows.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.Windows.cs @@ -1,12 +1,11 @@ #if WINDOWS -// || (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID && !TIZEN) using Microsoft.Maui.Handlers; using WBorder = Microsoft.UI.Xaml.Controls.Border; namespace SimpleToolkit.SimpleShell.Handlers { - public partial class SimpleShellItemHandler : ElementHandler, IAppearanceObserver + public partial class SimpleShellItemHandler : ElementHandler { protected override WBorder CreatePlatformElement() { diff --git a/src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.iOS.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.iOS.cs similarity index 83% rename from src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.iOS.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.iOS.cs index a426ac5..c103cc9 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/ShellItem/SimpleShellItemHandler.iOS.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellItem/SimpleShellItemHandler.iOS.cs @@ -1,11 +1,11 @@ -#if __IOS__ || MACCATALYST +#if IOS || MACCATALYST using Microsoft.Maui.Handlers; using UIKit; namespace SimpleToolkit.SimpleShell.Handlers { - public partial class SimpleShellItemHandler : ElementHandler, IAppearanceObserver + public partial class SimpleShellItemHandler : ElementHandler { protected override UIView CreatePlatformElement() { diff --git a/src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Android.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Android.cs similarity index 90% rename from src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Android.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Android.cs index 49f147d..1e01397 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Android.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Android.cs @@ -6,7 +6,7 @@ namespace SimpleToolkit.SimpleShell.Handlers { - public partial class SimpleShellSectionHandler : ElementHandler, IAppearanceObserver + public partial class SimpleShellSectionHandler : ElementHandler { protected override CustomFrameLayout CreatePlatformElement() { diff --git a/src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Shared.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Shared.cs similarity index 70% rename from src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Shared.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Shared.cs index ecaa475..e7f60a2 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Shared.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Shared.cs @@ -1,4 +1,4 @@ -using Microsoft.Maui.Handlers; +using SimpleToolkit.SimpleShell.Extensions; using SimpleToolkit.SimpleShell.NavigationManager; #if ANDROID using PageContainer = Microsoft.Maui.Controls.Platform.Compatibility.CustomFrameLayout; @@ -12,31 +12,29 @@ namespace SimpleToolkit.SimpleShell.Handlers { -#if ANDROID || __IOS__ || MACCATALYST || WINDOWS - - public partial class SimpleShellSectionHandler + public partial class SimpleShellSectionHandler : IAppearanceObserver { - protected SimpleStackNavigationManager navigationManager; - protected ShellSection shellSection; - public static PropertyMapper Mapper = - new PropertyMapper(ElementMapper) - { - [nameof(ShellSection.CurrentItem)] = MapCurrentItem, - }; + new PropertyMapper(ElementMapper) + { + [nameof(ShellSection.CurrentItem)] = MapCurrentItem, + }; public static CommandMapper CommandMapper = - new CommandMapper(ElementCommandMapper) - { - [nameof(IStackNavigation.RequestNavigation)] = RequestNavigation - }; + new CommandMapper(ElementCommandMapper) + { + [nameof(IStackNavigation.RequestNavigation)] = RequestNavigation + }; + + protected SimpleStackNavigationManager navigationManager; + protected ShellSection shellSection; + public SimpleShellSectionHandler(IPropertyMapper mapper, CommandMapper commandMapper) : base(mapper ?? Mapper, commandMapper ?? CommandMapper) { } - public SimpleShellSectionHandler() : base(Mapper, CommandMapper) { } @@ -44,7 +42,7 @@ public SimpleShellSectionHandler() : base(Mapper, CommandMapper) public override void SetVirtualView(IElement view) { - if (shellSection != null) + if (shellSection is not null) { ((IShellSectionController)shellSection).NavigationRequested -= OnNavigationRequested; ((IShellController)shellSection.FindParentOfType()).RemoveAppearanceObserver(this); @@ -53,7 +51,7 @@ public override void SetVirtualView(IElement view) // If we've already connected to the navigation manager // then we need to make sure to disconnect and connect up to // the new incoming virtual view - if (navigationManager?.StackNavigation != null && + if (navigationManager?.StackNavigation is not null && navigationManager.StackNavigation != view) { navigationManager.Disconnect(navigationManager.StackNavigation, PlatformView); @@ -67,13 +65,17 @@ public override void SetVirtualView(IElement view) base.SetVirtualView(view); shellSection = (ShellSection)view; - if (shellSection != null) + if (shellSection is not null) { ((IShellSectionController)shellSection).NavigationRequested += OnNavigationRequested; ((IShellController)shellSection.FindParentOfType()).AddAppearanceObserver(this, shellSection); } } + public virtual void OnAppearanceChanged(ShellAppearance appearance) + { + } + protected override void ConnectHandler(PageContainer platformView) { navigationManager?.Connect(VirtualView, platformView); @@ -96,40 +98,31 @@ void OnNavigationRequested(object sender, object e) protected virtual void SyncNavigationStack(bool animated) { - // TODO: There is another bug. Maybe try to compare the NavigationStack with current Shell (or VirtualView) state - //var shell = VirtualView.FindParentOfType(); - //var state = shell.CurrentState; - - List pageStack = new List() + var pageStack = new List() { (VirtualView.CurrentItem as IShellContentController).GetOrCreateContent() }; - // When navigating from subtab with a navigation stack to another subtab in the same tab, there is NavigationStack of previous subtab in VirtualView.Navigation + // When navigating from a subtab with a navigation stack to another subtab in the same tab, there is a NavigationStack of the previous subtab in VirtualView.Navigation if (currentShellContent == VirtualView.CurrentItem && !navigationStackCanBeAdded) // This is just a workaround of the bug in Shell for (var i = 1; i < VirtualView.Navigation.NavigationStack.Count; i++) { pageStack.Add(VirtualView.Navigation.NavigationStack[i]); } - navigationStackCanBeAdded = currentShellContent != null && VirtualView.Navigation.NavigationStack.Count > 1 && currentShellContent != VirtualView.CurrentItem; // This is just a workaround of the bug in Shell + navigationStackCanBeAdded = currentShellContent is not null && VirtualView.Navigation.NavigationStack.Count > 1 && currentShellContent != VirtualView.CurrentItem; // This is just a workaround of the bug in Shell currentShellContent = VirtualView.CurrentItem; // This is just a workaround of the bug in Shell // The point of this is to push the shell navigation over to using the INavigationStack // work flow. Ideally we rewrite all the push/pop/etc.. parts inside ShellSection.cs // to just use INavigationStack but that will be easier once all platforms are using // ShellHandler - (VirtualView as IStackNavigation) - .RequestNavigation(new NavigationRequest(pageStack, animated)); + (VirtualView as IStackNavigation).RequestNavigation(new NavigationRequest(pageStack, animated)); } protected virtual SimpleStackNavigationManager CreateNavigationManager() => navigationManager ??= new SimpleStackNavigationManager(MauiContext ?? throw new InvalidOperationException("MauiContext cannot be null")); - public void OnAppearanceChanged(ShellAppearance appearance) - { - } - public static void RequestNavigation(SimpleShellSectionHandler handler, IStackNavigation view, object arg3) { if (arg3 is NavigationRequest nr) @@ -147,21 +140,4 @@ public static void MapCurrentItem(SimpleShellSectionHandler handler, ShellSectio handler.SyncNavigationStack(false); } } - -#else - - public partial class SimpleShellSectionHandler : ElementHandler - { - public SimpleShellSectionHandler(IPropertyMapper mapper, CommandMapper commandMapper) - : base(mapper, commandMapper) - { - } - - protected override System.Object CreatePlatformElement() - { - throw new NotImplementedException(); - } - } - -#endif } diff --git a/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Standard.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Standard.cs new file mode 100644 index 0000000..8e1febe --- /dev/null +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Standard.cs @@ -0,0 +1,16 @@ +#if !(ANDROID || IOS || MACCATALYST || WINDOWS) + +using Microsoft.Maui.Handlers; + +namespace SimpleToolkit.SimpleShell.Handlers +{ + public partial class SimpleShellSectionHandler : ElementHandler + { + protected override System.Object CreatePlatformElement() + { + throw new NotImplementedException(); + } + } +} + +#endif \ No newline at end of file diff --git a/src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Windows.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Windows.cs similarity index 89% rename from src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Windows.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Windows.cs index f3f35c8..71a7a9a 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.Windows.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.Windows.cs @@ -5,7 +5,7 @@ namespace SimpleToolkit.SimpleShell.Handlers { - public partial class SimpleShellSectionHandler : ElementHandler, IAppearanceObserver + public partial class SimpleShellSectionHandler : ElementHandler { protected override WFrame CreatePlatformElement() { diff --git a/src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.iOS.cs b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.iOS.cs similarity index 83% rename from src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.iOS.cs rename to src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.iOS.cs index 6b3bd7d..b63c5c3 100644 --- a/src/SimpleToolkit.SimpleShell/Handlers/ShellSection/SimpleShellSectionHandler.iOS.cs +++ b/src/SimpleToolkit.SimpleShell/Handlers/SimpleShellSection/SimpleShellSectionHandler.iOS.cs @@ -1,11 +1,11 @@ -#if __IOS__ || MACCATALYST +#if IOS || MACCATALYST using Microsoft.Maui.Handlers; using UIKit; namespace SimpleToolkit.SimpleShell.Handlers { - public partial class SimpleShellSectionHandler : ElementHandler, IAppearanceObserver + public partial class SimpleShellSectionHandler : ElementHandler { protected override UIView CreatePlatformElement() { diff --git a/src/SimpleToolkit.SimpleShell/NavigationManager/SimpleStackNavigationManager.cs b/src/SimpleToolkit.SimpleShell/NavigationManager/SimpleStackNavigationManager.cs index f836cb7..a2b99d7 100644 --- a/src/SimpleToolkit.SimpleShell/NavigationManager/SimpleStackNavigationManager.cs +++ b/src/SimpleToolkit.SimpleShell/NavigationManager/SimpleStackNavigationManager.cs @@ -25,11 +25,13 @@ public class SimpleStackNavigationManager public IStackNavigation StackNavigation { get; protected set; } public IReadOnlyList NavigationStack { get; protected set; } = new List(); + public SimpleStackNavigationManager(IMauiContext mauiContext) { this.mauiContext = mauiContext; } + public virtual void Connect(IStackNavigation navigationView, NavFrame navigationFrame) { this.navigationFrame = navigationFrame; @@ -102,7 +104,7 @@ protected virtual PlatformPage GetPageView(IView page) return pageView; } - void FireNavigationFinished() + private void FireNavigationFinished() { StackNavigation?.NavigationFinished(NavigationStack); } diff --git a/src/SimpleToolkit.SimpleShell/SimpleToolkit.SimpleShell.csproj b/src/SimpleToolkit.SimpleShell/SimpleToolkit.SimpleShell.csproj index 6475a3d..009d077 100644 --- a/src/SimpleToolkit.SimpleShell/SimpleToolkit.SimpleShell.csproj +++ b/src/SimpleToolkit.SimpleShell/SimpleToolkit.SimpleShell.csproj @@ -1,4 +1,4 @@ - + net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst @@ -15,6 +15,22 @@ 10.0.17763.0 10.0.17763.0 6.5 + + SimpleToolkit.SimpleShell + SimpleToolkit.SimpleShell + LICENSE + MAUI, Controls, Shell, Navigation + RadekVyM + ..\..\packages + 1.0.0 + -preview1 + $(VersionPrefix)$(VersionSuffix) + Simplified implementation of .NET MAUI Shell that is part of SimpleToolkit library. + https://github.com/RadekVyM/SimpleToolkit + https://github.com/RadekVyM/SimpleToolkit + git + logo_with_background.png + Copyright © RadekVyM and contributors @@ -24,4 +40,15 @@ + + + True + \ + + + True + \ + + + diff --git a/src/SimpleToolkit.SimpleShell/Views/ISimpleShell.cs b/src/SimpleToolkit.SimpleShell/Views/ISimpleShell.cs deleted file mode 100644 index 6d804c8..0000000 --- a/src/SimpleToolkit.SimpleShell/Views/ISimpleShell.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SimpleToolkit.SimpleShell -{ - public interface ISimpleShell : IFlyoutView, IView, IElement, ITransform, IShellController, IPageController, IVisualElementController, IElementController, IPageContainer - { - IView Content { get; set; } - ShellItem CurrentItem { get; set; } - } -} diff --git a/src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost/ISimpleNavigationHost.cs b/src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost/ISimpleNavigationHost.cs new file mode 100644 index 0000000..7c11fed --- /dev/null +++ b/src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost/ISimpleNavigationHost.cs @@ -0,0 +1,9 @@ +namespace SimpleToolkit.SimpleShell +{ + /// + /// A container for pages in a . + /// + public interface ISimpleNavigationHost : IView + { + } +} diff --git a/src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost.cs b/src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost/SimpleNavigationHost.cs similarity index 55% rename from src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost.cs rename to src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost/SimpleNavigationHost.cs index c324b08..b7bb9f1 100644 --- a/src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost.cs +++ b/src/SimpleToolkit.SimpleShell/Views/SimpleNavigationHost/SimpleNavigationHost.cs @@ -1,9 +1,8 @@ namespace SimpleToolkit.SimpleShell { - public interface ISimpleNavigationHost : IView - { - } - + /// + /// A container for pages in a . + /// public class SimpleNavigationHost : View, ISimpleNavigationHost { } diff --git a/src/SimpleToolkit.SimpleShell/Views/SimpleShell/ISimpleShell.cs b/src/SimpleToolkit.SimpleShell/Views/SimpleShell/ISimpleShell.cs new file mode 100644 index 0000000..3b1ae36 --- /dev/null +++ b/src/SimpleToolkit.SimpleShell/Views/SimpleShell/ISimpleShell.cs @@ -0,0 +1,37 @@ +namespace SimpleToolkit.SimpleShell +{ + /// + /// A shell that lets you define your custom navigation experience. + /// + public interface ISimpleShell : IFlyoutView, IView, IElement, ITransform, IShellController, IPageController, IVisualElementController, IElementController, IPageContainer + { + /// + /// Gets or sets the content of this shell. + /// + IView Content { get; set; } + /// + /// The currently selected . + /// + new Page CurrentPage { get; } + /// + /// The currently selected or . + /// + ShellItem CurrentItem { get; set; } + /// + /// The currently selected . + /// + ShellContent CurrentShellContent { get; } + /// + /// The currently selected . + /// + ShellSection CurrentShellSection { get; } + /// + /// All items of this shell. + /// + IReadOnlyList ShellSections { get; } + /// + /// All items of this shell. + /// + IReadOnlyList ShellContents { get; } + } +} diff --git a/src/SimpleToolkit.SimpleShell/Views/SimpleShell.cs b/src/SimpleToolkit.SimpleShell/Views/SimpleShell/SimpleShell.cs similarity index 79% rename from src/SimpleToolkit.SimpleShell/Views/SimpleShell.cs rename to src/SimpleToolkit.SimpleShell/Views/SimpleShell/SimpleShell.cs index a21b5ff..2ff88e2 100644 --- a/src/SimpleToolkit.SimpleShell/Views/SimpleShell.cs +++ b/src/SimpleToolkit.SimpleShell/Views/SimpleShell/SimpleShell.cs @@ -1,11 +1,16 @@ -namespace SimpleToolkit.SimpleShell +using SimpleToolkit.SimpleShell.Extensions; + +namespace SimpleToolkit.SimpleShell { // TODO: It looks like the toolbar is part of ShellSection on iOS. So it is missing in my implementation. How to solve it? // 1) Lets hope that the toolbar will be handled as on Android and Windows when Shell totally transitions to handler architecture // 2) Implement it myself in my SimpleShellSectionHandler - This can be a waste of time if 1) comes true // I am waiting for transition to handler architecture and then I will see // I have set default visibility of the toolbar to hidden for now - + + /// + /// An implementation of that lets you define your custom navigation experience. + /// public class SimpleShell : Shell, ISimpleShell { const string PageStateNamePrefix = "SimplePageState"; @@ -26,6 +31,8 @@ public class SimpleShell : Shell, ISimpleShell public static readonly BindableProperty ShellSectionsProperty = BindableProperty.Create(nameof(ShellSections), typeof(IReadOnlyList), typeof(SimpleShell), defaultBindingMode: BindingMode.OneWay); public static readonly BindableProperty ShellContentsProperty = BindableProperty.Create(nameof(ShellContents), typeof(IReadOnlyList), typeof(SimpleShell), defaultBindingMode: BindingMode.OneWay); + public static new SimpleShell Current => Shell.Current as SimpleShell; + public virtual IView Content { get => (IView)GetValue(ContentProperty); @@ -35,35 +42,33 @@ public virtual IView Content public new Page CurrentPage { get => (Page)GetValue(CurrentPageProperty); - private set => SetValue(CurrentPageProperty, value); + protected set => SetValue(CurrentPageProperty, value); } public ShellContent CurrentShellContent { get => (ShellContent)GetValue(CurrentShellContentProperty); - private set => SetValue(CurrentShellContentProperty, value); + protected set => SetValue(CurrentShellContentProperty, value); } public ShellSection CurrentShellSection { get => (ShellSection)GetValue(CurrentShellSectionProperty); - private set => SetValue(CurrentShellSectionProperty, value); + protected set => SetValue(CurrentShellSectionProperty, value); } public IReadOnlyList ShellSections { get => (IReadOnlyList)GetValue(ShellSectionsProperty); - private set => SetValue(ShellSectionsProperty, value); + protected set => SetValue(ShellSectionsProperty, value); } public IReadOnlyList ShellContents { get => (IReadOnlyList)GetValue(ShellContentsProperty); - private set => SetValue(ShellContentsProperty, value); + protected set => SetValue(ShellContentsProperty, value); } - public new SimpleShell Current => Shell.Current as SimpleShell; - public SimpleShell() { @@ -104,47 +109,6 @@ private void UpdateVisualState(string state, string statePrefix, string groupNam VisualStateManager.GoToState(this, statePrefix); } - private void SimpleShellDescendantChanged(object sender, ElementEventArgs e) - { - // Update collections if logical structure of Shell is changed - if (e.Element is BaseShellItem) - { - ShellSections = GetShellSections().ToList(); - ShellContents = GetShellContents().ToList(); - } - } - - private void SimpleShellLoaded(object sender, EventArgs e) - { - SetDefaultShellPropertyValues(); - UpdateVisualStates(); - - ShellSections = GetShellSections().ToList(); - ShellContents = GetShellContents().ToList(); - DescendantAdded += SimpleShellDescendantChanged; - DescendantRemoved += SimpleShellDescendantChanged; - } - - private void SimpleShellNavigated(object sender, ShellNavigatedEventArgs e) - { - SetDefaultShellPropertyValues(); - - CurrentPage = base.CurrentPage; - CurrentShellSection = CurrentItem?.CurrentItem; - CurrentShellContent = CurrentItem?.CurrentItem?.CurrentItem; - // If BackButtonBehavior is not set on CurrentPage, set BackButtonBehavior of the Shell - if (!CurrentPage.IsSet(Shell.BackButtonBehaviorProperty)) - { - Shell.SetBackButtonBehavior(CurrentPage, Shell.GetBackButtonBehavior(this)); - } - if (!CurrentPage.IsSet(Shell.NavBarIsVisibleProperty)) - { - Shell.SetNavBarIsVisible(CurrentPage, Shell.GetNavBarIsVisible(this)); - } - - UpdateVisualStates(); - } - private void SetDefaultShellPropertyValues() { if (defaultShellPropertyValuesSet) @@ -162,30 +126,13 @@ private void SetDefaultShellPropertyValues() defaultShellPropertyValuesSet = true; } - private void SimpleShellUnloaded(object sender, EventArgs e) - { - Navigated -= SimpleShellNavigated; - Unloaded -= SimpleShellUnloaded; - Loaded -= SimpleShellLoaded; - DescendantAdded -= SimpleShellDescendantChanged; - DescendantRemoved -= SimpleShellDescendantChanged; - } - - private static void OnContentChanged(BindableObject bindable, object oldValue, object newValue) - { - if (bindable is SimpleShell simpleShell) - { - simpleShell.UpdateVisualStates(); - } - } - private IEnumerable GetShellSections() { var list = new HashSet(); foreach (var shellItem in Items) { - var shellSections = GetShellSections(shellItem); + var shellSections = shellItem.GetShellSections(); foreach (var section in shellSections) list.Add(section); } @@ -193,34 +140,13 @@ private IEnumerable GetShellSections() return list; } - private IEnumerable GetShellSections(BaseShellItem baseShellItem) - { - var list = new HashSet(); - - if (baseShellItem is ShellSection shellSection) - { - list.Add(shellSection); - } - else if (baseShellItem is ShellItem shellItem) - { - foreach (var item in shellItem.Items) - { - var shellSections = GetShellSections(item); - foreach (var section in shellSections) - list.Add(section); - } - } - - return list; - } - private IEnumerable GetShellContents() { var list = new HashSet(); foreach (var shellItem in Items) { - var shellContents = GetShellContents(shellItem); + var shellContents = shellItem.GetShellContents(); foreach (var content in shellContents) list.Add(content); } @@ -228,30 +154,62 @@ private IEnumerable GetShellContents() return list; } - private IEnumerable GetShellContents(BaseShellItem baseShellItem) + private void SimpleShellNavigated(object sender, ShellNavigatedEventArgs e) { - var list = new HashSet(); + SetDefaultShellPropertyValues(); - if (baseShellItem is ShellContent shellContent) + CurrentPage = base.CurrentPage; + CurrentShellSection = CurrentItem?.CurrentItem; + CurrentShellContent = CurrentItem?.CurrentItem?.CurrentItem; + // If BackButtonBehavior is not set on CurrentPage, set BackButtonBehavior of the Shell + if (!CurrentPage.IsSet(Shell.BackButtonBehaviorProperty)) { - list.Add(shellContent); + Shell.SetBackButtonBehavior(CurrentPage, Shell.GetBackButtonBehavior(this)); } - else if (baseShellItem is ShellItem shellItem) + if (!CurrentPage.IsSet(Shell.NavBarIsVisibleProperty)) { - foreach (var item in shellItem.Items) - { - var shellContents = GetShellContents(item); - foreach (var content in shellContents) - list.Add(content); - } + Shell.SetNavBarIsVisible(CurrentPage, Shell.GetNavBarIsVisible(this)); } - else if (baseShellItem is ShellSection shellSection) + + UpdateVisualStates(); + } + + private void SimpleShellDescendantChanged(object sender, ElementEventArgs e) + { + // Update collections if logical structure of the shell changes + if (e.Element is BaseShellItem) { - foreach (var content in shellSection.Items) - list.Add(content); + ShellSections = GetShellSections().ToList(); + ShellContents = GetShellContents().ToList(); } + } - return list; + private void SimpleShellLoaded(object sender, EventArgs e) + { + SetDefaultShellPropertyValues(); + UpdateVisualStates(); + + ShellSections = GetShellSections().ToList(); + ShellContents = GetShellContents().ToList(); + DescendantAdded += SimpleShellDescendantChanged; + DescendantRemoved += SimpleShellDescendantChanged; + } + + private void SimpleShellUnloaded(object sender, EventArgs e) + { + Navigated -= SimpleShellNavigated; + Unloaded -= SimpleShellUnloaded; + Loaded -= SimpleShellLoaded; + DescendantAdded -= SimpleShellDescendantChanged; + DescendantRemoved -= SimpleShellDescendantChanged; + } + + private static void OnContentChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is SimpleShell simpleShell) + { + simpleShell.UpdateVisualStates(); + } } } }