This is a migrated thread and some comments may be shown as answers.
RadListView dynamic Row height issue
10 Answers 70 Views
This is a migrated thread and some comments may be shown as answers.
Nidhi
Top achievements
Rank 1
Nidhi asked on 27 Jun 2019, 10:04 AM

I'm facing another issue with RadListView and expander, if my rows are of uneven height, then upon scrolling it resizes each cell randomly. It might be due to the Recycle Cashing Strategy. The cells are being reused and they reuse the cell created for previously visible rows and takes their size regardless of the actual content of the cell. It is similar to the issue we face in Xamarin.iOS while using "CellForReuse".

For example, if my first cell has 10 lines of data, and there are 5 cells visible on screen at a time, then scrolling to the next set of cells will result in reuse of previous cells and 6th cell will take same height as first cell even though there is only single line data in it. 

 

Please suggest how to overcome this bug, as the list row height should be consistent according to the content in each cell.

 

Thanks & Regards,

Nidhi Sood

10 Answers, 1 is accepted

Sort by
0
Yana
Telerik team
answered on 01 Jul 2019, 12:52 PM
Hi Nidhi,

Could you please send us the ListView definition as I would like to test and research the exact case you have? Also, in what parent containers is the ListView placed? Do you reproduce this on both Android and iOS? In general, RadListView does not extend the Xamarin.Forms ListView component, so it does not provide recycle caching strategy.

I look forward to your reply.

Regards,
Yana
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Nidhi
Top achievements
Rank 1
answered on 02 Jul 2019, 10:18 AM

Hi Yana,

This is my RadListView Implementatio:

    <telerikDataControls:RadListView x:Name="tradeItems" Grid.Row="1"
             Margin="0" ItemsSource="{Binding ObservablePricebookItems}"
             TranslationY="-10"
             GroupHeaderTemplate="{StaticResource ListViewGroupHeaderTemplate}">
            <telerikDataControls:RadListView.GroupDescriptors>
                <telerikListView:PropertyGroupDescriptor PropertyName="CategoryPath" />
            </telerikDataControls:RadListView.GroupDescriptors>
            <telerikDataControls:RadListView.ItemTemplate>
                <DataTemplate>
                    <telerikListView:ListViewTemplateCell>
                        <telerikListView:ListViewTemplateCell.View>
                            <cell:PriceBookSelectorDetailCell ShowQOH="false"
                                 IsPriceVisible="true"
                                 TextChangedCommand="{Binding Source={x:Reference PricebookSearch},Path=BindingContext.OnTextChangedCommand}"
                                 SearchText="{Binding Source={x:Reference PricebookSearch},Path=BindingContext.SearchText}" />
                        </telerikListView:ListViewTemplateCell.View>
                    </telerikListView:ListViewTemplateCell>
                </DataTemplate>
            </telerikDataControls:RadListView.ItemTemplate>
            <telerikDataControls:RadListView.ItemStyle>
                <telerikListView:ListViewItemStyle BorderLocation="None" />
            </telerikDataControls:RadListView.ItemStyle>
            <telerikDataControls:RadListView.SelectedItemStyle>
                <telerikListView:ListViewItemStyle BackgroundColor="Transparent"
                     BorderLocation="None" />
            </telerikDataControls:RadListView.SelectedItemStyle>
            <telerikDataControls:RadListView.PressedItemStyle>
                <telerikListView:ListViewItemStyle BackgroundColor="Transparent"
                     BorderLocation="None" />
            </telerikDataControls:RadListView.PressedItemStyle>
            <telerikDataControls:RadListView.Commands>
                <telerikListViewCommands:ListViewUserCommand Id="ItemTap"
                     Command="{Binding ItemTapCommand}" />
            </telerikDataControls:RadListView.Commands>
        </telerikDataControls:RadListView>

This is incorporated  inside a grid :

<Grid RowSpacing="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

</Grid>

At first row, there's a separate search template serving the search purpose.

Please check if you can find anything in the code. Also, I'll be sharing the corresponding ItemCell code in couple of seconds.

 

Thanks & Regards,

Nidhi Sood

0
Nidhi
Top achievements
Rank 1
answered on 02 Jul 2019, 10:24 AM

Here is the code for custom ItemCell view:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              x:Class="FSMobileApp.Views.Templates.Cells.PriceBookSelectorDetailCell"
              xmlns:renderer="clr-namespace:FSMobileApp.Renderer"
              xmlns:telerikPrimitives="clr-namespace:Telerik.XamarinForms.Primitives;assembly=Telerik.XamarinForms.Primitives"
              xmlns:telerikListView="clr-namespace:Telerik.XamarinForms.DataControls.ListView;assembly=Telerik.XamarinForms.DataControls"
              xmlns:local="clr-namespace:FSMobileApp.ViewModels.Base"
              xmlns:viewModelBase="clr-namespace:FSMobileApp.ViewModels.Base;assembly=FSMobileApp"
              xmlns:i18n="clr-namespace:FSMobileApp.Extension;assembly=FSMobileApp"
              xmlns:behaviors="clr-namespace:FSMobileApp.Behaviors"
              xmlns:templates="clr-namespace:FSMobileApp.Views.Templates"
              x:Name="PriceBookSelectorDetail">
    <ContentView.Content>
        <Grid VerticalOptions="FillAndExpand">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <renderer:MaterialFrame Grid.Row="0"
                                     ShadowColor="{StaticResource ShadowBlueColor}"
                                     OutlineColor="{StaticResource ShadowBlueColor}"
                                     Padding="0,8" Margin="0,4">
                <Grid ColumnSpacing="10" Margin="10,0,15,0"
                       BackgroundColor="{StaticResource WhiteColor}" Padding="0"
                       RowSpacing="0">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="60" />
                        <ColumnDefinition Width="6.4*" />
                        <ColumnDefinition Width="1.1*" />
                        <ColumnDefinition Width="1.5*" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Image BackgroundColor="Transparent" Aspect="AspectFit"
                            Source="{Binding Item.ItemImageUrl}" Grid.Column="0"
                            HeightRequest="55" Margin="5,0,0,0">
                    </Image>
                    <!--<StackLayout Grid.Column="1" Orientation="Vertical"
                     VerticalOptions="Center" Margin="0">-->
                    <Grid Grid.Column="1" VerticalOptions="CenterAndExpand"
                           Margin="0,5">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="30" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Label Grid.Row="0"
                                FormattedText="{Binding Item.HighlightedItemNo}"
                                VerticalTextAlignment="End"
                                HorizontalTextAlignment="Start"
                                FontSize="{StaticResource MidMediumSize}"
                                TextColor="{StaticResource BlueColor}" />
                        <!-- highlighter:TextHighlightBehavior.HighlightedText="{Binding SearchText, Source={x:Reference PriceBookSelectorDetail}}"
                                highlighter:TextHighlightBehavior.FullText="{Binding  Item.ItemNo}"
                                highlighter:TextHighlightBehavior.Foreground="{StaticResource BlueColor}"
                                highlighter:TextHighlightBehavior.Background="{StaticResource YellowColor}" />-->
                        <!--<templates:DropDownEntry FormattedText="{Binding Item.HighlightedDescription}"
                                                  Placeholder="Enter"
                                                  MaxLines="1024"
                                                  FrameElevation="0" />-->
                        <Label Grid.Row="1"
                                FormattedText="{Binding Item.HighlightedDescription}"
                                VerticalTextAlignment="Center"
                                HorizontalTextAlignment="Start"
                                TextColor="{StaticResource DarkestGrayColor}"
                                FontSize="{StaticResource MediumSize}"
                                MaxLines="1024" LineBreakMode="WordWrap" />
                        <!--  highlighter:TextHighlightBehavior.HighlightedText="{Binding SearchText, Source={x:Reference PriceBookSelectorDetail}}"
                                highlighter:TextHighlightBehavior.FullText="{Binding  Item.Description}"
                                highlighter:TextHighlightBehavior.Foreground="{StaticResource DarkestGrayColor}"
                                highlighter:TextHighlightBehavior.Background="{StaticResource YellowColor}" />-->
                    </Grid>
                    <StackLayout Grid.Column="2" Orientation="Vertical"
                                  IsVisible="{Binding Item.IsQOHVisible}"
                                  VerticalOptions="Center"
                                  HorizontalOptions="CenterAndExpand" Margin="0">
                        <Label x:Name="qohLabel" Text="{i18n:Translate QOH}"
                                FontSize="{StaticResource MidMediumSize}"
                                VerticalTextAlignment="End"
                                HorizontalTextAlignment="Center"
                                IsVisible="{Binding ShowQOH ,Source={x:Reference PriceBookSelectorDetail}}" />
                        <Label Text="{Binding Item.Quantity}" WidthRequest="52"
                                TextColor="{StaticResource WhiteColor}"
                                VerticalTextAlignment="Center"
                                HorizontalTextAlignment="Center"
                                BackgroundColor="{StaticResource Turquoise}"
                                IsVisible="{Binding Item.IsQOHVisible}" />
                    </StackLayout>
                    <Label Grid.Column="3"
                            Text="{Binding Item.StandardPrice,StringFormat='{0:C}'}"
                            WidthRequest="35"
                            TextColor="{StaticResource DarkestGrayColor}"
                            FontSize="{StaticResource MediumSize}"
                            IsVisible="{Binding IsPriceVisible ,Source={x:Reference PriceBookSelectorDetail}}"
                            HorizontalTextAlignment="Center"
                            VerticalTextAlignment="Center" />
                    <renderer:MaterialEntry Grid.Column="4" Margin="0"
                                             Text="{Binding Item.QuantityValue, StringFormat='{0:#0.###;\'\';\'\'}'}}"
                                             HorizontalTextAlignment="Center"
                                             ClassId="{Binding Item.ItemType}"
                                             VerticalOptions="CenterAndExpand"
                                             Placeholder="0"
                                             HorizontalOptions="CenterAndExpand"
                                             WidthRequest="46"
                                             Keyboard="Telephone"
                                             FontSize="{StaticResource LargeSize}"
                                             TextColor="{StaticResource DarkestGrayColor}"
                                             TextChanged="OnTextChanged">
                        <renderer:MaterialEditor.Behaviors>
                            <behaviors:NumericValidationBehavior MaxLength="1000" />
                        </renderer:MaterialEditor.Behaviors>
                        <!--<renderer:MaterialEntry.Behaviors>
                        <behaviors:EventToBoolBehavior x:Name="eventToBoolBehavior"
                             IsSaveEnabled="{Binding Item.IsSelected,Mode=TwoWay}" />
                    </renderer:MaterialEntry.Behaviors>-->
                    </renderer:MaterialEntry>
                </Grid>
            </renderer:MaterialFrame>
        </Grid>
    </ContentView.Content>
</ContentView>

Let me know in case you face any issue.

 

Thanks & Regards,

Nidhi Sood

0
Yana
Telerik team
answered on 03 Jul 2019, 11:43 AM
Hi Nidhi,

Thank you for sending the xaml, however, I am afraid I didn't manage to replicate the reported issue as it has too many missing references. 

I would need a running example, so I could test and investigate the case locally.  Is it possible for you to try to isolate it in a sample project and send it to us? You would need to open a support ticket and attach it there as in the forums only image attachments are allowed.

I look forward to your reply.

Regards,
Yana
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Marginpoint
Top achievements
Rank 1
answered on 04 Jul 2019, 12:22 PM

Hi Yana,

My client is not available this week, so won't be able to renew the license and create a support ticket to provide you individual code.

But, I have created an individual sample, can share its code here that you can import into a new project and test it directly without much effort.

Now, the issue I'm facing is I'm trying to highlight the search keyword in the list that user enters in the search bar, but it's not updating the UI(Telerik Listview), until the list is being scrolled up & down. Another, issue is the list is not scrolling completely till the end and jumps up when user scrolls to top.

I'm sharing the classes, please import them in a new project and see if you can find these issues.

 

HighLightText.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns                       ="http://xamarin.com/schemas/2014/forms"
              xmlns:x                      ="http://schemas.microsoft.com/winfx/2009/xaml"
              xmlns:telerikDataControls    ="clr-namespace:Telerik.XamarinForms.DataControls;assembly=Telerik.XamarinForms.DataControls"
              xmlns:telerikListView        ="clr-namespace:Telerik.XamarinForms.DataControls.ListView;assembly=Telerik.XamarinForms.DataControls"
              xmlns:telerikListViewCommands="clr-namespace:Telerik.XamarinForms.DataControls.ListView.Commands;assembly=Telerik.XamarinForms.DataControls"
              xmlns:telerikPrimitives      ="clr-namespace:Telerik.XamarinForms.Primitives;assembly=Telerik.XamarinForms.Primitives"
              x:Class                      ="Test.HighLightText">
    <StackLayout Padding="0,40,0,0">
        <SearchBar x:Name       ="searchbar"
                    Text         ="{Binding SearchText}"
                    SearchCommand="{Binding SearchCommand}">
        </SearchBar>
        <telerikDataControls:RadListView ItemsSource="{Binding MockContacts}">
            <telerikDataControls:RadListView.ItemTemplate>
                <DataTemplate>
                    <telerikListView:ListViewTemplateCell>
                        <telerikListView:ListViewTemplateCell.View>
                            <StackLayout Padding="10">
                                <Label FormattedText="{Binding HighlightedString}"
                                        TextColor    ="Black"
                                        FontSize     ="Large" />
                                <Label FormattedText="{Binding HighlightedStringNum}"
                                        TextColor    ="Gray"
                                        FontSize     ="Medium" />
                                <Label FormattedText="{Binding Description}"
                                        TextColor    ="Teal"
                                        FontSize     ="Medium" />
                            </StackLayout>
                        </telerikListView:ListViewTemplateCell.View>
                    </telerikListView:ListViewTemplateCell>
                </DataTemplate>
            </telerikDataControls:RadListView.ItemTemplate>
        </telerikDataControls:RadListView>
    </StackLayout>
</ContentPage>

 

HighLightText.xaml.cs

using Xamarin.Forms;
namespace Test
{
    public partial class HighLightText : ContentPage
    {
        public HighLightText()
        {
            InitializeComponent();

            BindingContext = new CountryViewModel();
        }
    }
}

 

Contacts.cs

using Xamarin.Forms;
namespace Test
{
    public class Contacts
    {
        public string Name { get; set; }
        public string Num { get; set; }
        public string Description { get; set; }
        public string imgsource { get; set; }
        public FormattedString HighlightedString { get; set; }
        public FormattedString HighlightedStringNum { get; set; }
    }
}

 

0
Marginpoint
Top achievements
Rank 1
answered on 04 Jul 2019, 12:24 PM

This is the ViewModel Class which has the main logic :

 

CountryViewModel.cs

 

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;

namespace Test
{
    public class CountryViewModel : ExtendedBindableObject
    {
        public ObservableCollection<Contacts> AllContacts;
        public ICommand SearchCommand => new Command(Search);

        private ObservableCollection<Contacts> mockContacts;
        public ObservableCollection<Contacts> MockContacts
        {
            get => mockContacts;
            set
            {
                mockContacts = value;
                RaisePropertyChanged(() => MockContacts);
            }
        }

        private string searchText;
        public string SearchText
        {
            get => searchText;
            set
            {
                searchText = value;
                RaisePropertyChanged(() => SearchText);
                MockContacts = QueryMockContacts(searchText);
            }
        }

        public CountryViewModel()
        {
            AllContacts = GetMockContacts();
            MockContacts = AllContacts.ToObservableCollection();
        }

        public ObservableCollection<Contacts> QueryMockContacts(string text)
        {
            var query = text.ToLower();
            return AllContacts.ToObservableCollection(text);
        }

        public ObservableCollection<Contacts> GetMockContacts()
        {
            return new ObservableCollection<Contacts> {
                new Contacts(){ Name = "Afghanistan", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "AAlbania", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Algeria", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Andorra", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Angola", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Anguilla", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "Antigua & Barbuda", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Argentina", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Armenia", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "Australia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Austria", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Azerbaijan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Bahamas", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "Bahrain", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Bangladesh", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Barbados", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Belarus", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Belgium", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "Belize", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Benin", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Bermuda", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Bhutan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Bolivia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Bosnia & Herzegovina", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Botswana", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Brazil", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Brunei Darussalam", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Bulgaria", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Burkina Faso", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Burundi", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "Cambodia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Cameroon", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Canada", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Cape Verde", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Cayman Islands", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Central African Republic", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Chad", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Chile", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "China", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "China - Hong Kong / Macau", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "Colombia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Comoros", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Congo", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Congo, Democratic Republic of (DRC)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Costa Rica", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Croatia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Cuba", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Cyprus", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Czech Republic", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "Denmark", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Djibouti", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Dominica", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Dominican Republic", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Ecuador", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Egypt", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "El Salvador", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Equatorial Guinea", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Eritrea", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Estonia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Eswatini", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Ethiopia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Fiji", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Finland", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "France", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "French Guiana", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Gabon", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Gambia, Republic of The", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Georgia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Germany", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Ghana", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Great Britain", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Greece", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Grenada", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Guadeloupe", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Guatemala", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Guinea", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Guinea-Bissau", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Guyana", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Haiti", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Honduras", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Hungary", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Iceland", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "India", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Indonesia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Iran", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Iraq", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Israel and the Occupied Territories", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Italy", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Ivory Coast (Cote d'Ivoire)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Jamaica", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Japan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Jordan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Kazakhstan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Kenya", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Korea, Democratic Republic of (North Korea)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Korea, Republic of (South Korea)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Kosovo", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Kuwait", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Kyrgyz Republic (Kyrgyzstan)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Laos", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Latvia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Lebanon", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Lesotho", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Liberia", Num = Convert.ToString(Guid.NewGuid()),Description="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."},
                new Contacts(){ Name = "Libya", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Liechtenstein", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Lithuania", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Luxembourg", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Madagascar", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Malawi", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Malaysia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Maldives", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Mali", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Malta", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Martinique", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Mauritania", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Mauritius", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Mayotte", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Mexico", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Moldova, Republic of", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Monaco", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Mongolia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Montenegro", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Montserrat", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Morocco", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Mozambique", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Myanmar/Burma", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Namibia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Nepal", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "New Zealand", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Nicaragua", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Niger", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Nigeria", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "North Macedonia, Republic of", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Norway", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Oman", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Pacific Islands", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Pakistan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Panama", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Papua New Guinea", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Paraguay", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Peru", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Philippines", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Poland", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Portugal", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Puerto Rico", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Qatar", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Reunion", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Romania", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Russian Federation", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Rwanda", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Saint Kitts and Nevis", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Saint Lucia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Saint Vincent and the Grenadines", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Samoa", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Sao Tome and Principe", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Saudi Arabia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Senegal", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Serbia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Seychelles", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Sierra Leone", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Singapore", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Slovak Republic (Slovakia)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Slovenia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Solomon Islands", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Somaliaaa", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "South Africa", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "South Sudan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Spain", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Sri Lanka", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Sudan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Suriname", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Sweden", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Switzerland", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Syria", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Tajikistan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Tanzania", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Thailand", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Netherlands", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Timor Leste", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Togo", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Trinidad & Tobago", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Tunisia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Turkey", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Turkmenistan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Turks & Caicos Islands", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Uganda", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Ukraine", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "United Arab Emirates", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "United States of America (USA)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Uruguay", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Uzbekistan", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Venezuela", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Vietnam", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Virgin Islands (UK)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Virgin Islands (US)", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Yemen", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Zambia", Num = Convert.ToString(Guid.NewGuid())},
                new Contacts(){ Name = "Zimbabwea", Num = Convert.ToString(Guid.NewGuid())}
            };
        }

        private void Search(object obj)
        {
            MockContacts = AllContacts.Where(x => x.Name.ToLower().Contains(SearchText?.ToLower())).ToObservableCollection(SearchText);
        }
    }

    public abstract class ExtendedBindableObject : BindableObject
    {
        public void RaisePropertyChanged<T>(Expression<Func<T>> property)
        {
            var name = GetMemberInfo(property).Name;
            OnPropertyChanged(name);
        }

        private MemberInfo GetMemberInfo(Expression expression)
        {
            MemberExpression operand;
            LambdaExpression lambdaExpression = (LambdaExpression)expression;
            if (lambdaExpression.Body as UnaryExpression != null)
            {
                UnaryExpression body = (UnaryExpression)lambdaExpression.Body;
                operand = (MemberExpression)body.Operand;
            }
            else
            {
                operand = (MemberExpression)lambdaExpression.Body;
            }
            return operand.Member;
        }
    }
}

0
Marginpoint
Top achievements
Rank 1
answered on 04 Jul 2019, 12:26 PM

There are two more extension classes for highlighting feature:

ObservableExtension.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xamarin.Forms;

namespace Test
{
    public static class ObservableExtension
    {
        public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> source, string highLightText = "")
        {
            ObservableCollection<T> collection = new ObservableCollection<T>();

            var hlLabel = new HighlightLabel();

            foreach (T item in source)
            {
                if (item is Contacts)
                {
                    var contact = item as Contacts;

                    var text = new FormattedString();
                    var textNum = new FormattedString();

                    if (!string.IsNullOrEmpty(highLightText))
                    {
                        text = hlLabel.FormattedString(highLightText, Convert.ToString(contact.Name));

                        textNum = hlLabel.FormattedString(highLightText, Convert.ToString(contact.Num));
                    }
                    else
                    {
                        text.Spans.Add(new Span() { Text = contact.Name });
                        textNum.Spans.Add(new Span() { Text = contact.Num });
                    }

                    contact.HighlightedString = text;
                    contact.HighlightedStringNum = textNum;
                    collection.Add(item);
                }
                else
                {
                    collection.Add(item);
                }
            }

            return collection;
        }
    }
}

 

HighlightLabel.cs

using System.Collections.Generic;
using System.Text.RegularExpressions;
using Xamarin.Forms;

namespace Test
{
    public class HighlightLabel
    {
        public FormattedString FormattedString(string highLightText, string rawText)
        {
            var htmlString = HighlightKeywords(highLightText, rawText);

            var formatted = new FormattedString();

            foreach (var item in ProcessString(htmlString))
                formatted.Spans.Add(CreateSpan(item));

            return formatted;
        }

        private Span CreateSpan(StringSection section)
        {
            var span = new Span()
            {
                Text = section.Text
            };

            if (!string.IsNullOrEmpty(section.HighlightText))
            {
                span.BackgroundColor = Color.Yellow;
            }

            return span;
        }

        public IList<StringSection> ProcessString(string rawText)
        {
            const string spanPattern = @"(<h.*?>.*?</h>)";

            MatchCollection collection = Regex.Matches(rawText, spanPattern, RegexOptions.Singleline | RegexOptions.IgnoreCase);

            var sections = new List<StringSection>();

            var lastIndex = 0;

            foreach (Match item in collection)
            {
                var foundText = item.Value;
                sections.Add(new StringSection() { Text = rawText.Substring(lastIndex, (item.Index - lastIndex)) });
                lastIndex = item.Index + item.Length;

                // Get HTML href 
                var html = new StringSection()
                {
                    HighlightText = Regex.Match(item.Value, "(?<=text=\\\")[\\S]+(?=\\\")").Value,
                    Text = Regex.Replace(item.Value, "<.*?>", string.Empty)
                };

                sections.Add(html);
            }

            sections.Add(new StringSection() { Text = rawText.Substring(lastIndex) });

            return sections;
        }

        private string HighlightKeywords(string keywords, string text)
        {
            // Swap out the ,<space> for pipes and add the braces
            Regex r = new Regex(@", ?");
            keywords = "(" + r.Replace(keywords, @"|") + ")";

            // Get ready to replace the keywords
            r = new Regex(keywords, RegexOptions.Singleline | RegexOptions.IgnoreCase);

            var sas = new MatchEvaluator(MatchEval);

            // Do the replace
            return r.Replace(text, new MatchEvaluator(MatchEval));
        }

        private string MatchEval(Match match)
        {
            if (match.Groups[1].Success)
            {
                return "<h text=\"" + match + "\">" + match + "</h>";
            }

            return ""; //no match
        }

        public class StringSection
        {
            public string Text { get; set; }
            public string HighlightText { get; set; }
        }
    }
}

 

Please take a look and let me know if you face any issue.

 

Thanks & Regards,

Nidhi Sood

 

0
Yana
Telerik team
answered on 05 Jul 2019, 10:13 AM
Hi Nidhi,

Thank you for sending the snippets.

I've managed to recreate the sample and to reproduce the described behavior.  The reason behind is that the Contacts class does not raise property changed notifications when the formatted string properties are updated.  In order to resolve it, you would just need to implement INotifyPropertyChanged interface:

public class Contacts : NotifyPropertyChangedBase
{
    private FormattedString _highlightedString;
    private FormattedString _highlightedStringNum;
    public string Name { get; set; }
    public string Num { get; set; }
    public string Description { get; set; }
    public string imgsource { get; set; }
    public FormattedString HighlightedString
    {
        get { return this._highlightedString; }
        set
        {
            if(this._highlightedString != value)
            {
                this._highlightedString = value;
                OnPropertyChanged();
            }
        }
    }
    public FormattedString HighlightedStringNum
    {
        get { return this._highlightedStringNum; }
        set
        {
            if (this._highlightedStringNum != value)
            {
                this._highlightedStringNum = value;
                OnPropertyChanged();
            }
        }
    }
}

I've used in the snippet Telerik.XamarinForms.Common.NotifyPropertyChangedBase class which is our implementation of the INotifyPropertyChanged interface.

I have attached the sample app as a reference.

I hope I was of help.

Regards,
Yana
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
0
Marginpoint
Top achievements
Rank 1
answered on 10 Jul 2019, 08:48 AM

Hi Yana,

Thanks, it fixed that issue. I totally missed that point to implement INotifyPropertyChanged.

I need help in another issue. I have one scenario where I need to set cursor position of Editor to the last index of the text, whenever it is focused. Is there any way to implement this for Editor?

I've tried doing this through custom renderer, but it sets the cursor position to last index only once, when focused initially. Then it sets back to its normal behaviour.

 

Any suggestions how can I achieve this or do we have such feature available through Telerik editor or something?

 

Thanks & Regards,

Nidhi Sood

0
Yana
Telerik team
answered on 11 Jul 2019, 08:32 AM
Hi Nidhi,

I am not sure what exactly you mean with Editor control - is it RadEntry? I would also like to ask you to open separate tickets for new and unrelated questions. This allows for us to keep different problems in separate threads and prevents really long conversations that have multiple problems in it. 

So, in the concrete case, please open a new ticket with more details on the scenario you have, as well as some snippets what you've tried so far.

Regards,
Yana
Progress Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Asked by
Nidhi
Top achievements
Rank 1
Answers by
Yana
Telerik team
Nidhi
Top achievements
Rank 1
Marginpoint
Top achievements
Rank 1
Share this question
or