[Shell] Add FlyoutIcon (#5567) fixes #4766 fixes #4767 fixes #4845 fixes #5219
authorRui Marinho <me@ruimarinho.net>
Wed, 27 Mar 2019 17:05:21 +0000 (17:05 +0000)
committerGitHub <noreply@github.com>
Wed, 27 Mar 2019 17:05:21 +0000 (17:05 +0000)
* [Controls]  Add repo for shell issue

* [iOS] Allow specify SetPaddingInsets on the ShellContent

* [iOS,Shell] Fix issue when disposing ToolbarItems  of old page

* [Controls] Add demo repo for #5466

* [Shell,Core] Fix navigating to a registered route

* [Shell,Core] Add better exception messages for wrong or non existing content fixes #5081

* [Core,Shell,iOS,Android] Add FlyoutIcon property

* [Controls] Make shell sample work on Android

* [Controls,Android] Add ImageSource support to FlyoutIcon

* [Android]Allow to set text on the back button

* [Android] Create default text back button

* [Controls] Add example to push with back button behavior

* [Android] Fix back button tint color

* [Android] Cleanup and refactor UpdateDrawerArrow

* Update Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs

Co-Authored-By: rmarinho <me@ruimarinho.net>
* [iOS,Shell] Fix go back (Pop) when proving BackButtonBehavior

* [iOS] Check the ParentViewController since we were push to it

* [Android,iOS,Shell] Remove extra code implement feedback

* removed old code

* minor cleanup

12 files changed:
Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj
Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj
Xamarin.Forms.Controls/XamStore/StoreShell.xaml
Xamarin.Forms.Controls/XamStore/StoreShell.xaml.cs
Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml
Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml.cs
Xamarin.Forms.Controls/XamStore/Views/StorePages.cs
Xamarin.Forms.Core/Shell/IShellController.cs
Xamarin.Forms.Core/Shell/Shell.cs
Xamarin.Forms.Platform.Android/Forms.cs
Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs
Xamarin.Forms.Platform.iOS/Renderers/ShellPageRendererTracker.cs

index e1b9be3..bdd26b3 100644 (file)
     </CreateItem>
     <Copy SourceFiles="@(MapsKey)" DestinationFiles="Properties\MapsKey.cs" Condition="!Exists('Properties\MapsKey.cs')" />
   </Target>
-</Project>
\ No newline at end of file
+</Project>
index 082f7f1..fb00422 100644 (file)
     <Folder Include="Resources\Fonts\" />
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
-</Project>
\ No newline at end of file
+</Project>
index 0ab999e..a36ddd6 100644 (file)
                <MenuItem Text="Parent Guide" />
                <MenuItem Text="About Xam Store" />
        </Shell.MenuItems>
-</Shell>
\ No newline at end of file
+</Shell>
index 8b78210..42cbc0a 100644 (file)
@@ -12,11 +12,30 @@ namespace Xamarin.Forms.Controls.XamStore
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class StoreShell : Shell
        {
-               public StoreShell ()
+               public StoreShell()
                {
-                       InitializeComponent ();
-
-                       CurrentItem =  _storeItem;
+                       InitializeComponent();
+                       var fontFamily = "";
+                       switch (Device.RuntimePlatform)
+                       {
+                               case Device.iOS:
+                                       fontFamily = "Ionicons";
+                                       break;
+                               case Device.UWP:
+                                       fontFamily = "Assets/Fonts/ionicons.ttf#ionicons";
+                                       break;
+                               case Device.Android:
+                               default:
+                                       fontFamily = "fonts/ionicons.ttf#";
+                                       break;
+                       }
+                       FlyoutIcon = new FontImageSource
+                       {
+                               Glyph = "\uf2fb",
+                               FontFamily = fontFamily,
+                               Size = 20
+                       };
+                       CurrentItem = _storeItem;
                        Routing.RegisterRoute("demo", typeof(DemoShellPage));
                        Routing.RegisterRoute("demo/demo", typeof(DemoShellPage));
                }
index 0867326..735df9f 100644 (file)
@@ -35,7 +35,7 @@
                     StyleClass="MainTab"
                     HorizontalOptions="FillAndExpand"
                     VerticalOptions="Fill"
-                    Image="button_bookmark.png"
+                    Image="icon_bookmark.png"
                     Command="{Binding ToggleCommand}" 
                     CommandParameter="bookmarked">
                     <Button.Triggers>
index 337f5a1..0f744e0 100644 (file)
@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.ComponentModel;
 using System.Globalization;
 using System.Linq;
index d7b0b3c..c4d6926 100644 (file)
@@ -20,7 +20,7 @@ namespace Xamarin.Forms.Controls.XamStore
                public BasePage(string title, Color tint)
                {
                        Title = title;
-
+                       Shell.SetShellForegroundColor(this, tint);
                        var grid = new Grid()
                        {
                                Padding = 20,
@@ -209,7 +209,7 @@ namespace Xamarin.Forms.Controls.XamStore
                                                Navigation.PushAsync(page);
                                        }),
                                2, 14);
-
+                       
                        grid.Children.Add(MakeButton("Show Alert",
                                async () => {
                                        var result = await DisplayAlert("Title", "Message", "Ok", "Cancel");
@@ -220,6 +220,19 @@ namespace Xamarin.Forms.Controls.XamStore
                                async () => await Shell.CurrentShell.GoToAsync("demo", true)),
                        1, 15);
 
+                       grid.Children.Add(MakeButton("Go Back with Text",
+                       async () => {
+                                       var page = (Page)Activator.CreateInstance(GetType());
+                                       Shell.SetShellForegroundColor(page, Color.Pink);
+                                       Shell.SetBackButtonBehavior(page, new BackButtonBehavior()
+                                       {
+                                               //IconOverride = "calculator.png",
+                                               
+                                               TextOverride = "back"
+                                       });
+                                       await Navigation.PushAsync(page);
+                               }),2, 15);
+
                        grid.Children.Add(new Label {
                                Text = "Navigate to",
                                VerticalOptions = LayoutOptions.CenterAndExpand
index fd178f2..8179d84 100644 (file)
@@ -21,6 +21,8 @@ namespace Xamarin.Forms
 
                View FlyoutHeader { get; }
 
+               ImageSource FlyoutIcon { get; }
+
                void AddAppearanceObserver(IAppearanceObserver observer, Element pivot);
 
                void AddFlyoutBehaviorObserver(IFlyoutBehaviorObserver observer);
index c3e48b9..29e2e51 100644 (file)
@@ -566,6 +566,9 @@ namespace Xamarin.Forms
                public static readonly BindableProperty MenuItemTemplateProperty =
                        BindableProperty.Create(nameof(MenuItemTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime);
 
+               public static readonly BindableProperty FlyoutIconProperty =
+                       BindableProperty.Create(nameof(FlyoutIcon), typeof(ImageSource), typeof(Shell), null);
+
                ShellNavigatedEventArgs _accumulatedEvent;
                bool _accumulateNavigatedEvents;
                View _flyoutHeaderView;
@@ -587,6 +590,13 @@ namespace Xamarin.Forms
                public event EventHandler<ShellNavigatedEventArgs> Navigated;
                public event EventHandler<ShellNavigatingEventArgs> Navigating;
 
+
+               public ImageSource FlyoutIcon
+               {
+                       get => (ImageSource)GetValue(FlyoutIconProperty);
+                       set => SetValue(FlyoutIconProperty, value);
+               }
+
                public ShellItem CurrentItem {
                        get => (ShellItem)GetValue(CurrentItemProperty);
                        set => SetValue(CurrentItemProperty, value);
index 7776b4a..b3e56f2 100644 (file)
@@ -66,6 +66,25 @@ namespace Xamarin.Forms
                        }
                }
 
+               public static float GetFontSizeNormal(Context context)
+               {
+                       float size = 50;
+                       if (!IsLollipopOrNewer)
+                               return size;
+
+                       // Android 5.0+
+                       //this doesn't seem to work
+                       using (var value = new TypedValue())
+                       {
+                               if (context.Theme.ResolveAttribute(Resource.Attribute.TextSize, value, true)) 
+                               {
+                                       size = value.Data;
+                               }
+                       }
+
+                       return size;
+               }
+
                public static Color GetColorButtonNormal(Context context)
                {
                        if (!_ColorButtonNormalSet)
index 24e025f..af086ce 100644 (file)
@@ -9,6 +9,7 @@ using Android.Views;
 using System;
 using System.Collections.Specialized;
 using System.ComponentModel;
+using System.Threading.Tasks;
 using ActionBarDrawerToggle = Android.Support.V7.App.ActionBarDrawerToggle;
 using AView = Android.Views.View;
 using LP = Android.Views.ViewGroup.LayoutParams;
@@ -130,7 +131,7 @@ namespace Xamarin.Forms.Platform.Android
                                {
                                        UpdateTitleView(_shellContext.AndroidContext, _toolbar, null);
 
-                                       _drawerToggle.Dispose();
+                                       _drawerToggle?.Dispose();
                                        if (_searchView != null)
                                        {
                                                _searchView.View.RemoveFromParent();
@@ -235,51 +236,113 @@ namespace Xamarin.Forms.Platform.Android
                        var backButtonHandler = Shell.GetBackButtonBehavior(page);
                        toolbar.SetNavigationOnClickListener(this);
 
+                       var activity = (FormsAppCompatActivity)context;
+
                        if (backButtonHandler != null)
                        {
-                               using (var icon = await context.GetFormsDrawable(backButtonHandler.IconOverride))
-                               using (var mutatedIcon = icon.GetConstantState().NewDrawable().Mutate())
+                               await UpdateDrawerArrowFromBackButtonBehavior(context, toolbar, drawerLayout, backButtonHandler, activity);
+                       }
+                       else
+                       {
+                               await UpdateDrawerArrow(context, toolbar, drawerLayout, activity);
+                       }
+               }
+
+               protected virtual async Task UpdateDrawerArrow(Context context, Toolbar toolbar, DrawerLayout drawerLayout, FormsAppCompatActivity activity)
+               {
+                       if (_drawerToggle == null)
+                       {
+                               _drawerToggle = new ActionBarDrawerToggle((Activity)context, drawerLayout, toolbar,
+                                       R.String.Ok, R.String.Ok)
                                {
-                                       mutatedIcon.SetColorFilter(TintColor.ToAndroid(Color.White), PorterDuff.Mode.SrcAtop);
-                                       toolbar.NavigationIcon = mutatedIcon;
+                                       ToolbarNavigationClickListener = this,
+                               };
+
+                               await UpdateDrawerArrowFromFlyoutIcon(context, _drawerToggle);
+
+                               _drawerToggle.DrawerSlideAnimationEnabled = false;
+                               drawerLayout.AddDrawerListener(_drawerToggle);
+                       }
+
+                       if (CanNavigateBack)
+                       {
+                               _drawerToggle.DrawerIndicatorEnabled = false;
+                               using (var icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext))
+                               {
+                                       icon.SetColorFilter(TintColor.ToAndroid(Color.White), PorterDuff.Mode.SrcAtop);
+                                       icon.Progress = 1;
+                                       toolbar.NavigationIcon = icon;
                                }
                        }
+                       else if (_flyoutBehavior == FlyoutBehavior.Flyout)
+                       {
+                               toolbar.NavigationIcon = null;
+                               var drawable = _drawerToggle.DrawerArrowDrawable;
+                               drawable.SetColorFilter(TintColor.ToAndroid(Color.White), PorterDuff.Mode.SrcAtop);
+                               _drawerToggle.DrawerIndicatorEnabled = true;
+                       }
                        else
                        {
-                               var activity = (FormsAppCompatActivity)context;
-                               if (_drawerToggle == null)
+                               _drawerToggle.DrawerIndicatorEnabled = false;
+                       }
+                       _drawerToggle.SyncState();
+               }
+
+               protected virtual async Task UpdateDrawerArrowFromBackButtonBehavior(Context context, Toolbar toolbar, DrawerLayout drawerLayout, BackButtonBehavior backButtonHandler, FormsAppCompatActivity activity)
+               {
+                       var behavior = backButtonHandler;
+                       var command = behavior.Command;
+                       var commandParameter = behavior.CommandParameter;
+                       var image = behavior.IconOverride;
+                       var text = behavior.TextOverride;
+                       var enabled = behavior.IsEnabled;
+
+                       Drawable icon = null;
+
+                       if (image != null)
+                               icon = await context.GetFormsDrawable(image);
+
+                       if (text != null)
+                               icon = new FlyoutIconDrawerDrawable(context, TintColor, icon, text);
+
+                       if (CanNavigateBack && icon == null)
+                       {
+                               icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext);
+                               (icon as DrawerArrowDrawable).Progress = 1;
+                       }
+
+                       var iconState = icon.GetConstantState();
+                       if (iconState != null)
+                       {
+                               using (var mutatedIcon = iconState.NewDrawable().Mutate())
                                {
-                                       _drawerToggle = new ActionBarDrawerToggle((Activity)context, drawerLayout, toolbar,
-                                               R.String.Ok, R.String.Ok)
-                                       {
-                                               ToolbarNavigationClickListener = this,
-                                       };
-                                       _drawerToggle.DrawerSlideAnimationEnabled = false;
-                                       drawerLayout.AddDrawerListener(_drawerToggle);
+                                       mutatedIcon.SetColorFilter(TintColor.ToAndroid(Color.White), PorterDuff.Mode.SrcAtop);
+                                       toolbar.NavigationIcon = mutatedIcon;
                                }
+                       }
+                       else
+                       {
+                               toolbar.NavigationIcon = icon;
+                       }
+               }
 
-                               if (CanNavigateBack)
+               protected virtual async Task UpdateDrawerArrowFromFlyoutIcon(Context context, ActionBarDrawerToggle actionBarDrawerToggle)
+               {
+                       Element item = Page;
+                       ImageSource icon = null;
+                       while (!Application.IsApplicationOrNull(item))
+                       {
+                               if (item is IShellController shell)
                                {
-                                       _drawerToggle.DrawerIndicatorEnabled = false;
-                                       using (var icon = new DrawerArrowDrawable(activity.SupportActionBar.ThemedContext))
+                                       icon = shell.FlyoutIcon;
+                                       if (icon != null)
                                        {
-                                               icon.SetColorFilter(TintColor.ToAndroid(Color.White), PorterDuff.Mode.SrcAtop);
-                                               icon.Progress = 1;
-                                               toolbar.NavigationIcon = icon;
+                                               var drawable = await context.GetFormsDrawable(icon);
+                                               actionBarDrawerToggle.DrawerArrowDrawable = new FlyoutIconDrawerDrawable(context, TintColor, drawable, null);
                                        }
+                                       return;
                                }
-                               else if (_flyoutBehavior == FlyoutBehavior.Flyout)
-                               {
-                                       toolbar.NavigationIcon = null;
-                                       using (var drawable = _drawerToggle.DrawerArrowDrawable)
-                                               drawable.SetColorFilter(TintColor.ToAndroid(Color.White), PorterDuff.Mode.SrcAtop);
-                                       _drawerToggle.DrawerIndicatorEnabled = true;
-                               }
-                               else
-                               {
-                                       _drawerToggle.DrawerIndicatorEnabled = false;
-                               }
-                               _drawerToggle.SyncState();
+                               item = item?.Parent;
                        }
                }
 
@@ -452,5 +515,51 @@ namespace Xamarin.Forms.Platform.Android
                {
                        UpdateToolbarItems(_toolbar, Page);
                }
+
+               class FlyoutIconDrawerDrawable : DrawerArrowDrawable
+               {
+                       Drawable _iconBitmap;
+                       string _text;
+                       Color _defaultColor;
+                       float _defaultSize;
+
+                       Color _pressedBackgroundColor => _defaultColor.AddLuminosity(-.12);//<item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
+
+                       protected override void Dispose(bool disposing)
+                       {
+                               base.Dispose(disposing);
+                               if (disposing && _iconBitmap != null)
+                               {
+                                       _iconBitmap.Dispose();
+                               }
+                       }
+
+                       public FlyoutIconDrawerDrawable(Context context, Color defaultColor, Drawable icon, string text) : base(context)
+                       {
+                               _defaultColor = defaultColor;
+                               _defaultSize = Forms.GetFontSizeNormal(context);
+                               _iconBitmap = icon;
+                               _text = text;
+                       }
+
+                       public override void Draw(Canvas canvas)
+                       {
+                               bool pressed = false;
+                               if (_iconBitmap != null)
+                               {
+                                       _iconBitmap.SetBounds(Bounds.Left, Bounds.Top, Bounds.Right, Bounds.Bottom);
+                                       _iconBitmap.Draw(canvas);
+                               }
+                               else if (!string.IsNullOrEmpty(_text))
+                               {
+                                       var paint = new Paint { AntiAlias = true };
+                                       paint.TextSize = _defaultSize;
+                                       paint.Color = pressed ? _pressedBackgroundColor.ToAndroid() : _defaultColor.ToAndroid();
+                                       paint.SetStyle(Paint.Style.Fill);
+                                       var y = (Bounds.Height() + paint.TextSize) / 2;
+                                       canvas.DrawText(_text, 0, y, paint);
+                               }
+                       }
+               }
        }
-}
\ No newline at end of file
+}
index 59f411d..46d6dcd 100644 (file)
@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.Threading.Tasks;
+using System.Windows.Input;
 using UIKit;
 
 namespace Xamarin.Forms.Platform.iOS
@@ -61,7 +62,7 @@ namespace Xamarin.Forms.Platform.iOS
                {
                        _context = context;
                }
-               
+
                public async void OnFlyoutBehaviorChanged(FlyoutBehavior behavior)
                {
                        _flyoutBehavior = behavior;
@@ -198,24 +199,24 @@ namespace Xamarin.Forms.Platform.iOS
                                var commandParameter = behavior.CommandParameter;
                                var image = behavior.IconOverride;
                                var enabled = behavior.IsEnabled;
+
                                if (image == null)
                                {
                                        var text = BackButtonBehavior.TextOverride;
                                        NavigationItem.LeftBarButtonItem =
-                                               new UIBarButtonItem(text, UIBarButtonItemStyle.Plain, (s, e) => command?.Execute(commandParameter)) { Enabled = enabled };
+                                               new UIBarButtonItem(text, UIBarButtonItemStyle.Plain, (s, e) => LeftBarButtonItemHandler(ViewController, command, commandParameter, IsRootPage)) { Enabled = enabled };
                                }
                                else
                                {
                                        var icon = await image.GetNativeImageAsync();
                                        NavigationItem.LeftBarButtonItem =
-                                               new UIBarButtonItem(icon, UIBarButtonItemStyle.Plain, (s, e) => command?.Execute(commandParameter)) { Enabled = enabled };
+                                               new UIBarButtonItem(icon, UIBarButtonItemStyle.Plain, (s, e) => LeftBarButtonItemHandler(ViewController, command, commandParameter, IsRootPage)) { Enabled = enabled };
                                }
                        }
                        else if (IsRootPage && _flyoutBehavior == FlyoutBehavior.Flyout)
                        {
-                               ImageSource image = "3bar.png";
-                               var icon = await image.GetNativeImageAsync();
-                               NavigationItem.LeftBarButtonItem = new UIBarButtonItem(icon, UIBarButtonItemStyle.Plain, OnMenuButtonPressed);
+
+                               await SetDrawerArrowDrawableFromFlyoutIcon();
                        }
                        else
                        {
@@ -223,6 +224,35 @@ namespace Xamarin.Forms.Platform.iOS
                        }
                }
 
+               static void LeftBarButtonItemHandler(UIViewController controller, ICommand command, object commandParameter, bool isRootPage)
+               {
+                       if (command == null && !isRootPage && controller?.ParentViewController is UINavigationController navigationController)
+                       {
+                               navigationController.PopViewController(true);
+                               return;
+                       }
+                       command?.Execute(commandParameter);
+               }
+
+               async Task SetDrawerArrowDrawableFromFlyoutIcon()
+               {
+                       Element item = Page;
+                       ImageSource image = null;
+                       while (!Application.IsApplicationOrNull(item))
+                       {
+                               if (item is IShellController shell)
+                               {
+                                       image = shell.FlyoutIcon;
+                                       item = null;
+                               }
+                               item = item?.Parent;
+                       }
+                       if (image == null)
+                               image = "3bar.png";
+                       var icon = await image.GetNativeImageAsync();
+                       NavigationItem.LeftBarButtonItem = new UIBarButtonItem(icon, UIBarButtonItemStyle.Plain, OnMenuButtonPressed);
+               }
+
                void OnMenuButtonPressed(object sender, EventArgs e)
                {
                        _context.Shell.SetValueFromRenderer(Shell.FlyoutIsPresentedProperty, true);