ToolbarItem icon with BADGES could be possible (and with custom icon too)

slavachernikoffslavachernikoff ✭✭USMember ✭✭

Please help us to promote this ticket to prioritize fixing:
https://bugzilla.xamarin.com/show_bug.cgi?id=43569

Currently ToolbarItem hardcoded to use icons from resources. See the source code: https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs
https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.iOS/Extensions/ToolbarItemExtensions.cs

It's possible to draw custom icon (we prefer NGraphics, but native canvas is good to) but it's hard to use it. ToolbarItem can load icons ONLY from Resources.

Xamarin asked to promote this ticket for fast fixing :)

Please +1 to this thread if you have the same issue!

«1

Posts

  • kyurkchyankyurkchyan ✭✭ AMMember ✭✭

    +1

  • EmanueleSabettaEmanueleSabetta ✭✭✭ ITBeta ✭✭✭

    +1

  • slavachernikoffslavachernikoff ✭✭ USMember ✭✭
    edited September 2016

    Here is a sample of how to use NGraphics to draw a cart icon with badge:

    public static class CartIconHelper
    `{

        private static Graphic _svgGraphic = null;
        private const string ResourcePath = "Demo.Resources.cart.svg";
    
        private static PathOp[] RoundRect(NGraphics.Rect rect, double radius)
        {
            return new PathOp[]
                       {
                           new NGraphics.MoveTo(rect.X + radius, rect.Y),
                           new NGraphics.LineTo(rect.X + rect.Width - radius, rect.Y),
                           new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + rect.Width, rect.Y + radius)),
                           new NGraphics.LineTo(rect.X + rect.Width, rect.Y + rect.Height - radius),
                           new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + rect.Width - radius, rect.Y + rect.Height)),
                           new NGraphics.LineTo(rect.X + radius, rect.Y + rect.Height),
                           new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X, rect.Y + rect.Height - radius)),
                           new NGraphics.LineTo(rect.X, rect.Y + radius), new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + radius, rect.Y)),
                           new NGraphics.ClosePath()
                       };
        }
    
        public static void DrawCartIcon(int count, string path, IPlatform platform, double iconSize = 30, double scale = 2, string fontName = "Arial", double fontSize = 12, double textSpacing = 4)
        {
            var canvas = platform.CreateImageCanvas(new NGraphics.Size(iconSize, iconSize), scale);
    
            if (_svgGraphic == null) using (var stream = typeof(CartIconHelper).GetTypeInfo().Assembly.GetManifestResourceStream(ResourcePath))
                    _svgGraphic = new SvgReader(new StreamReader(stream)).Graphic;
    
            var minSvgScale = Math.Min(canvas.Size.Width / _svgGraphic.Size.Width, canvas.Size.Height / _svgGraphic.Size.Height) / 1.15;
    
            var w = _svgGraphic.Size.Width / minSvgScale;
            var h = _svgGraphic.Size.Height / minSvgScale;
    
            _svgGraphic.ViewBox = new NGraphics.Rect(0, -14, w, h);
            _svgGraphic.Draw(canvas);
    
            if (count > 0)
            {
                var text = count > 99 ? "99+" : count.ToString();
                var font = new NGraphics.Font(fontName, fontSize);
                var textSize = canvas.MeasureText(text, font);
    
                var textRect = new NGraphics.Rect(canvas.Size.Width - textSize.Width - textSpacing, textSpacing, textSize.Width, textSize.Height);
    
                if (count < 10)
                {
                    var side = Math.Max(textSize.Width, textSize.Height);
                    var elipseRect = new NGraphics.Rect(canvas.Size.Width - side - 2 * textSpacing, 0, side + 2 * textSpacing, side + 2 * textSpacing);
                    canvas.FillEllipse(elipseRect, NGraphics.Colors.Red);
                    textRect -= new NGraphics.Point(side - textSize.Width, side - textSize.Height) / 2.0;
                }
                else
                {
                    var elipseRect = new NGraphics.Rect(textRect.Left - textSpacing, textRect.Top - textSpacing, textRect.Width + 2 * textSpacing, textSize.Height + 2 * textSpacing);
                    canvas.FillPath(RoundRect(elipseRect, 6), NGraphics.Colors.Red);
                }
    
                canvas.DrawText(text, textRect + new NGraphics.Point(0, textSize.Height), font, NGraphics.TextAlignment.Center, NGraphics.Colors.White);
            }
    
            canvas.GetImage().SaveAsPng(path);
        }
    }`` 
    
  • maukurmaukur RUMember

    +1

  • AkhmedSherievAkhmedSheriev RUMember

    +1

  • EmanueleSabettaEmanueleSabetta ✭✭✭ ITBeta ✭✭✭
    edited September 2016

    I've attached a zip archive with a small project reproducing the bug.
    It is a reduced version of the excellent Toolbar Badge component of Gagik Kyurkchyan.
    Just compile and run both iOS and Android projects: the icons should be the same on both. But on iOS they are displayed, while on Android they are not. And that is caused by this bug.

    You can download it from Comment 5: https://bugzilla.xamarin.com/show_bug.cgi?id=43569#c5

  • KirillAshikhminKirillAshikhmin ✭✭ RUMember ✭✭

    +1

  • MichaelRumplerMichaelRumpler ✭✭✭✭✭ ATMember ✭✭✭✭✭

    I'd consider this a (very reasonable) feature request and not a bug.

    However, if you already found out, where the image is loaded and that it should be so easy, why don't you do a PR as Rui suggested in his comment?
    The Xamarin team will be faster just testing a PR than implementing and testing a new feature. Especially if they don't consider it very important themselves.

  • EmanueleSabettaEmanueleSabetta ✭✭✭ ITBeta ✭✭✭

    Doing a PR seems an option.
    But it is not just a Pull Request. There is a convoluted iter to follow. Here is a guide on how to do it: https://xamarinhelp.com/contributing-xamarin-forms/

  • slavachernikoffslavachernikoff ✭✭ USMember ✭✭

    I think that Xamarin guys will make it the best way. So I just try to attract more attention for ToolbarItem. Currently there are a lot of limitations :(

  • EmanueleSabettaEmanueleSabetta ✭✭✭ ITBeta ✭✭✭
    edited September 2016

    @slavachernikoff I think that a PR will not be difficult to do.

    We just need to edit this file:

    https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs

    The only change would be to replace this line:

    Drawable iconBitmap = context.Resources.GetDrawable(icon);
    

    With this line:

    Drawable iconBitmap = context.Resources.GetDrawable(new BitmapDrawable(context.Resources, Android.Graphics.BitmapFactory.DecodeFile(icon)));
    
  • slavachernikoffslavachernikoff ✭✭ USMember ✭✭
    edited September 2016

    @EmanueleSabetta I guess it's better to check the file path and if it is start with "/" it should be loaded from file.

    Android
    (https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs Line 698):

    Drawable iconBitmap = icon.StartsWith("/") ? Drawable.CreateFromPath(icon) : context.Resources.GetDrawable(icon);

    iOS
    (https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.iOS/Extensions/ToolbarItemExtensions.cs Line 86):
    var image = _item.Icon.StartsWith("/") ? UIImage.FromFile(_item.Icon) : UIImage.FromBundle(_item.Icon);

    There is also an option to check if path starts with "file://".

    What do you folks think?

  • EmanueleSabettaEmanueleSabetta ✭✭✭ ITBeta ✭✭✭
    edited September 2016

    @slavachernikoff Yes, that will be better. But to support all, we need to support both Uri and Streams.

    For example on Android you can use Drawable.CreateFromStream(inputStream,icon);

    Here is a method I found that switch from Streams to Path:

    private void resolveUri()
            {
                if (mDrawable != null)
                {
                    return;
                }
                android.content.res.Resources rsrc = getResources();
                if (rsrc == null)
                {
                    return;
                }
                android.graphics.drawable.Drawable d = null;
                if (mResource != 0)
                {
                    try
                    {
                        d = rsrc.getDrawable(mResource);
                    }
                    catch (System.Exception e)
                    {
                        android.util.Log.w("ImageView", "Unable to find resource: " + mResource, e);
                        // Don't try again.
                        mUri = null;
                    }
                }
                else
                {
                    if (mUri != null)
                    {
                        string scheme = mUri.Scheme;
                        if (android.content.ContentResolver.SCHEME_ANDROID_RESOURCE.Equals(scheme))
                        {
                            try
                            {
                                // Load drawable through Resources, to get the source density information
                                android.content.ContentResolver.OpenResourceIdResult r = mContext.getContentResolver
                                    ().getResourceId(mUri);
                                d = r.r.getDrawable(r.id);
                            }
                            catch (System.Exception e)
                            {
                                android.util.Log.w("ImageView", "Unable to open content: " + mUri, e);
                            }
                        }
                        else
                        {
                            if (android.content.ContentResolver.SCHEME_CONTENT.Equals(scheme) || android.content.ContentResolver
                                .SCHEME_FILE.Equals(scheme))
                            {
                                try
                                {
                                    d = android.graphics.drawable.Drawable.createFromStream(mContext.getContentResolver
                                        ().openInputStream(mUri), null);
                                }
                                catch (System.Exception e)
                                {
                                    android.util.Log.w("ImageView", "Unable to open content: " + mUri, e);
                                }
                            }
                            else
                            {
                                d = android.graphics.drawable.Drawable.createFromPath(mUri.ToString());
                            }
                        }
                        if (d == null)
                        {
                            java.io.Console.Out.println("resolveUri failed on bad bitmap uri: " + mUri);
                            // Don't try again.
                            mUri = null;
                        }
                    }
                    else
                    {
                        return;
                    }
                }
                updateDrawable(d);
            }
    

    It seems to make use of the Uri.Scheme property:

    https://developer.xamarin.com/api/property/Android.Net.Uri.Scheme/

  • EmanueleSabettaEmanueleSabetta ✭✭✭ ITBeta ✭✭✭

    @slavachernikoff It is also possible to use the Async versions of the same methods, as you can see:

    https://developer.xamarin.com/api/type/Android.Graphics.Drawables.Drawable/

  • moreirawebmastermoreirawebmaster ✭✭ BRMember ✭✭

    +1

  • aizefleraizefler ✭✭ BRUniversity ✭✭

    +1

  • YuraBabiyYuraBabiy ✭✭✭ UAMember ✭✭✭

    +1

  • joilson.cisnejoilson.cisne ✭✭ BRMember ✭✭

    +1

  • AndreAguiarAndreAguiar ✭✭ USMember ✭✭

    Please , give me a project link with a sample

  • LucioMSPLucioMSP ✭✭✭ MXUniversity ✭✭✭

    +1

  • AniruthGAniruthG ✭✭ USMember ✭✭

    +1

  • MirzaSikanderMirzaSikander USMember

    The bug seems to have been fixed. Can anybody post sample code on how to draw custom icon using this fix?

  • MirzaSikanderMirzaSikander USMember

    @slavachernikoff said:
    Here is a sample of how to use NGraphics to draw a cart icon with badge:

    public static class CartIconHelper
    `{

        private static Graphic _svgGraphic = null;
        private const string ResourcePath = "Demo.Resources.cart.svg";
    
        private static PathOp[] RoundRect(NGraphics.Rect rect, double radius)
        {
            return new PathOp[]
                       {
                           new NGraphics.MoveTo(rect.X + radius, rect.Y),
                           new NGraphics.LineTo(rect.X + rect.Width - radius, rect.Y),
                           new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + rect.Width, rect.Y + radius)),
                           new NGraphics.LineTo(rect.X + rect.Width, rect.Y + rect.Height - radius),
                           new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + rect.Width - radius, rect.Y + rect.Height)),
                           new NGraphics.LineTo(rect.X + radius, rect.Y + rect.Height),
                           new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X, rect.Y + rect.Height - radius)),
                           new NGraphics.LineTo(rect.X, rect.Y + radius), new NGraphics.ArcTo(new NGraphics.Size(radius, radius), true, false, new NGraphics.Point(rect.X + radius, rect.Y)),
                           new NGraphics.ClosePath()
                       };
        }
    
        public static void DrawCartIcon(int count, string path, IPlatform platform, double iconSize = 30, double scale = 2, string fontName = "Arial", double fontSize = 12, double textSpacing = 4)
        {
            var canvas = platform.CreateImageCanvas(new NGraphics.Size(iconSize, iconSize), scale);
    
            if (_svgGraphic == null) using (var stream = typeof(CartIconHelper).GetTypeInfo().Assembly.GetManifestResourceStream(ResourcePath))
                    _svgGraphic = new SvgReader(new StreamReader(stream)).Graphic;
    
            var minSvgScale = Math.Min(canvas.Size.Width / _svgGraphic.Size.Width, canvas.Size.Height / _svgGraphic.Size.Height) / 1.15;
    
            var w = _svgGraphic.Size.Width / minSvgScale;
            var h = _svgGraphic.Size.Height / minSvgScale;
    
            _svgGraphic.ViewBox = new NGraphics.Rect(0, -14, w, h);
            _svgGraphic.Draw(canvas);
    
            if (count > 0)
            {
                var text = count > 99 ? "99+" : count.ToString();
                var font = new NGraphics.Font(fontName, fontSize);
                var textSize = canvas.MeasureText(text, font);
    
                var textRect = new NGraphics.Rect(canvas.Size.Width - textSize.Width - textSpacing, textSpacing, textSize.Width, textSize.Height);
    
                if (count < 10)
                {
                    var side = Math.Max(textSize.Width, textSize.Height);
                    var elipseRect = new NGraphics.Rect(canvas.Size.Width - side - 2 * textSpacing, 0, side + 2 * textSpacing, side + 2 * textSpacing);
                    canvas.FillEllipse(elipseRect, NGraphics.Colors.Red);
                    textRect -= new NGraphics.Point(side - textSize.Width, side - textSize.Height) / 2.0;
                }
                else
                {
                    var elipseRect = new NGraphics.Rect(textRect.Left - textSpacing, textRect.Top - textSpacing, textRect.Width + 2 * textSpacing, textSize.Height + 2 * textSpacing);
                    canvas.FillPath(RoundRect(elipseRect, 6), NGraphics.Colors.Red);
                }
    
                canvas.DrawText(text, textRect + new NGraphics.Point(0, textSize.Height), font, NGraphics.TextAlignment.Center, NGraphics.Colors.White);
            }
    
            canvas.GetImage().SaveAsPng(path);
        }
    }`` 
    

    Slava had answered my question already.

  • LuisMatosLuisMatos ✭✭ USMember ✭✭

    +1

  • KapnioKapnio USMember

    +1

  • MarlonRibeiroMarlonRibeiro ✭✭✭ USMember ✭✭✭

    +1

  • nhathanhatha VNMember
    edited April 2017

    +1

  • nhathanhatha VNMember

    Hi @slavachernikoff how to use DrawCartIcon function with parameter Platform platform

  • YuraBabiyYuraBabiy ✭✭✭ UAMember ✭✭✭

    @EmanueleSabetta
    Hi. I was trying to run your sample, and because bug seems fixed, it works on Android perfectly, but on iOS I have the whole
    image and badge in one solid color, which is tint color. Because of it, I can't even see my count text. Can you please suggest something?

  • EmanueleSabettaEmanueleSabetta ✭✭✭ ITBeta ✭✭✭

    @YuraBabiy
    image and badge in one solid color, which is tint color. Because of it, I can't even see my count text. Can you please suggest something?

    On iOS you need a trick to force the image to be rendered in color. I'm going to post a new fixed version in a few days.

  • YuraBabiyYuraBabiy ✭✭✭ UAMember ✭✭✭

    @EmanueleSabetta
    It would be great. Please mention me wherever you will post it. Thank you

  • MarkErickson.91MarkErickson.91 ✭✭ USMember ✭✭

    @EmanueleSabetta Were you able to get past the single tint color issue?

  • ZaduddaZadudda ✭✭ INMember ✭✭
    edited May 2017

    +1

    Looking for Extending Xamarin.Forms.FileImageSource to accepting SVG files

  • satishk097satishk097 INMember

    I have created custom icon using NGraphics and save it into device internal storage in Android. It is working with image control but not working for Toolbar item. I know Toolbar Items can load the icons from Resources.
    Is there a way that Toolbar item can load icons from local file system ?

  • ZaduddaZadudda ✭✭ INMember ✭✭
    edited May 2017

    +1

  • MarkErickson.91MarkErickson.91 ✭✭ USMember ✭✭

    @Zadudda Are you still converting the svg to png within the DrawCartIcon method? If so, how are you getting the platform specific "path" to the new png file?

  • JohnMillerJohnMiller Xamurai USForum Administrator, Xamarin Team Xamurai

    Hey All,

    Make sure that any feature requests for Xamarin.Forms go into the Evolution forum, here: https://forums.xamarin.com/categories/xamarin-forms-evolution

    There is no way for us to track +1 comments on forum threads so please use the Evolution forum to create a feature request from the template. If you have a bug report, continue using Bugzilla. Thank you!

  • satishk097satishk097 INMember

    @MarkErickson.91 yes still converting svg image into png and saving it into device internal storage in android as png image. you can use the following sample code for saving and retrieving new png image from device storage.
    class CanvasService : IIconService
    {
    private readonly AndroidPlatform _platform;
    public CanvasService()
    {
    _platform = new AndroidPlatform();
    }

        public void SaveImage(IImage image)
        {
            var dir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
            var filePath = System.IO.Path.Combine(dir, "cartXX.png");
            var stream = new FileStream(filePath, FileMode.Create);
            image.SaveAsPng(stream);
            stream.Close();
        }
    
        public string GetImage()
        {
            var dir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
            var filePath = System.IO.Path.Combine(dir, "cartXX.png");
            using (var streamReader = new StreamReader(filePath))
            {
                string content = streamReader.ReadToEnd();
                System.Diagnostics.Debug.WriteLine(content);
            }
            return filePath;
        }
    
        public IImageCanvas GetCanvas()
        {
            NGraphics.Size size = new NGraphics.Size(90,90);
            return _platform.CreateImageCanvas(size);
        }
    
        public NGraphics.AndroidPlatform GetPlatform()
        {
            return _platform;
        }
    
  • ZaduddaZadudda ✭✭ INMember ✭✭

    @MarkErickson.91 After implement above methods, Call DrawCartIcon like below to show badges on Icon:

      var imagePath = CartIconHelper.DrawCartIcon(2, "ToolBarAndroidBadge.Resources.cartIcon.svg");
            string deviceSepecificFolderPath = Device.OnPlatform(null, imagePath, null);
            object convertedObject = new FileImageSourceConverter().ConvertFromInvariantString(deviceSepecificFolderPath);
            FileImageSource fileImageSource = (FileImageSource)convertedObject;                
    
            ToolbarItem cartItem = new ToolbarItem();
            cartItem.Text = "My Cart";
            cartItem.Order = ToolbarItemOrder.Primary;
            cartItem.Icon = fileImageSource;
    
    
    
    
            ToolbarItems.Add(cartItem);
    
  • ZaduddaZadudda ✭✭ INMember ✭✭

    Hi all, kindly look here, I have summarized all discussion and code and got a solution. May be it works for you.

    https://stackoverflow.com/questions/43843654/how-to-show-a-badges-count-of-toolbaritem-icon-in-xamarin-forms/44151505#44151505

Sign In or Register to comment.