From db7e702ecff64f43da02506df5c99e908de3d1cf Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 27 Mar 2019 17:05:21 +0000 Subject: [PATCH] =?utf8?q?[Shell]=C2=A0Add=20FlyoutIcon=20=20(#5567)=20fix?= =?utf8?q?es=20#4766=20fixes=20#4767=20fixes=20#4845=20fixes=20#5219?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * [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 * [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 --- .../Xamarin.Forms.ControlGallery.Android.csproj | 2 +- .../Xamarin.Forms.ControlGallery.iOS.csproj | 2 +- Xamarin.Forms.Controls/XamStore/StoreShell.xaml | 2 +- Xamarin.Forms.Controls/XamStore/StoreShell.xaml.cs | 27 +++- .../XamStore/Views/DemoShellPage.xaml | 2 +- .../XamStore/Views/DemoShellPage.xaml.cs | 2 +- .../XamStore/Views/StorePages.cs | 17 +- Xamarin.Forms.Core/Shell/IShellController.cs | 2 + Xamarin.Forms.Core/Shell/Shell.cs | 10 ++ Xamarin.Forms.Platform.Android/Forms.cs | 19 +++ .../Renderers/ShellToolbarTracker.cs | 175 +++++++++++++++++---- .../Renderers/ShellPageRendererTracker.cs | 42 ++++- 12 files changed, 252 insertions(+), 50 deletions(-) diff --git a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj index e1b9be3..bdd26b3 100644 --- a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj +++ b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj @@ -467,4 +467,4 @@ - \ No newline at end of file + diff --git a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj index 082f7f1..fb00422 100644 --- a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj +++ b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj @@ -402,4 +402,4 @@ - \ No newline at end of file + diff --git a/Xamarin.Forms.Controls/XamStore/StoreShell.xaml b/Xamarin.Forms.Controls/XamStore/StoreShell.xaml index 0ab999e..a36ddd6 100644 --- a/Xamarin.Forms.Controls/XamStore/StoreShell.xaml +++ b/Xamarin.Forms.Controls/XamStore/StoreShell.xaml @@ -100,4 +100,4 @@ - \ No newline at end of file + diff --git a/Xamarin.Forms.Controls/XamStore/StoreShell.xaml.cs b/Xamarin.Forms.Controls/XamStore/StoreShell.xaml.cs index 8b78210..42cbc0a 100644 --- a/Xamarin.Forms.Controls/XamStore/StoreShell.xaml.cs +++ b/Xamarin.Forms.Controls/XamStore/StoreShell.xaml.cs @@ -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)); } diff --git a/Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml b/Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml index 0867326..735df9f 100644 --- a/Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml +++ b/Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml @@ -35,7 +35,7 @@ StyleClass="MainTab" HorizontalOptions="FillAndExpand" VerticalOptions="Fill" - Image="button_bookmark.png" + Image="icon_bookmark.png" Command="{Binding ToggleCommand}" CommandParameter="bookmarked"> diff --git a/Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml.cs b/Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml.cs index 337f5a1..0f744e0 100644 --- a/Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml.cs +++ b/Xamarin.Forms.Controls/XamStore/Views/DemoShellPage.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Globalization; using System.Linq; diff --git a/Xamarin.Forms.Controls/XamStore/Views/StorePages.cs b/Xamarin.Forms.Controls/XamStore/Views/StorePages.cs index d7b0b3c..c4d6926 100644 --- a/Xamarin.Forms.Controls/XamStore/Views/StorePages.cs +++ b/Xamarin.Forms.Controls/XamStore/Views/StorePages.cs @@ -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 diff --git a/Xamarin.Forms.Core/Shell/IShellController.cs b/Xamarin.Forms.Core/Shell/IShellController.cs index fd178f2..8179d84 100644 --- a/Xamarin.Forms.Core/Shell/IShellController.cs +++ b/Xamarin.Forms.Core/Shell/IShellController.cs @@ -21,6 +21,8 @@ namespace Xamarin.Forms View FlyoutHeader { get; } + ImageSource FlyoutIcon { get; } + void AddAppearanceObserver(IAppearanceObserver observer, Element pivot); void AddFlyoutBehaviorObserver(IFlyoutBehaviorObserver observer); diff --git a/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms.Core/Shell/Shell.cs index c3e48b9..29e2e51 100644 --- a/Xamarin.Forms.Core/Shell/Shell.cs +++ b/Xamarin.Forms.Core/Shell/Shell.cs @@ -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 Navigated; public event EventHandler Navigating; + + public ImageSource FlyoutIcon + { + get => (ImageSource)GetValue(FlyoutIconProperty); + set => SetValue(FlyoutIconProperty, value); + } + public ShellItem CurrentItem { get => (ShellItem)GetValue(CurrentItemProperty); set => SetValue(CurrentItemProperty, value); diff --git a/Xamarin.Forms.Platform.Android/Forms.cs b/Xamarin.Forms.Platform.Android/Forms.cs index 7776b4a..b3e56f2 100644 --- a/Xamarin.Forms.Platform.Android/Forms.cs +++ b/Xamarin.Forms.Platform.Android/Forms.cs @@ -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) diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs index 24e025f..af086ce 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs @@ -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);//0.12 + + 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 +} diff --git a/Xamarin.Forms.Platform.iOS/Renderers/ShellPageRendererTracker.cs b/Xamarin.Forms.Platform.iOS/Renderers/ShellPageRendererTracker.cs index 59f411d..46d6dcd 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/ShellPageRendererTracker.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/ShellPageRendererTracker.cs @@ -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); -- 2.7.4