diff --git a/src/App/Pages/Send/SendGroupingsPage/ISendGroupingsPageListItem.cs b/src/App/Pages/Send/SendGroupingsPage/ISendGroupingsPageListItem.cs new file mode 100644 index 000000000..6b8b84801 --- /dev/null +++ b/src/App/Pages/Send/SendGroupingsPage/ISendGroupingsPageListItem.cs @@ -0,0 +1,6 @@ +namespace Bit.App.Pages +{ + public interface ISendGroupingsPageListItem + { + } +} diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml index 114feede1..ec6b33043 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPage.xaml @@ -73,7 +73,29 @@ + + + + + + + + + @@ -114,32 +136,9 @@ ItemsSource="{Binding GroupedSends}" VerticalOptions="FillAndExpand" ItemTemplate="{StaticResource sendListItemDataTemplateSelector}" - IsGrouped="True" SelectionMode="Single" SelectionChanged="RowSelected" - StyleClass="list, list-platform"> - - - - - - - - - - - - + StyleClass="list, list-platform" /> diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageHeaderListItem.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageHeaderListItem.cs new file mode 100644 index 000000000..c8c7943be --- /dev/null +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageHeaderListItem.cs @@ -0,0 +1,14 @@ +namespace Bit.App.Pages +{ + public class SendGroupingsPageHeaderListItem : ISendGroupingsPageListItem + { + public SendGroupingsPageHeaderListItem(string title, string itemCount) + { + Title = title; + ItemCount = itemCount; + } + + public string Title { get; } + public string ItemCount { get; } + } +} diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageListItem.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageListItem.cs index 00d1bccbd..a89ebf18d 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageListItem.cs +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageListItem.cs @@ -5,7 +5,7 @@ using Bit.Core.Models.View; namespace Bit.App.Pages { - public class SendGroupingsPageListItem + public class SendGroupingsPageListItem : ISendGroupingsPageListItem { private string _icon; private string _name; diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageListItemSelector.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageListItemSelector.cs index 885bc0fb5..3eb8a6019 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageListItemSelector.cs +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageListItemSelector.cs @@ -4,11 +4,17 @@ namespace Bit.App.Pages { public class SendGroupingsPageListItemSelector : DataTemplateSelector { + public DataTemplate HeaderTemplate { get; set; } public DataTemplate SendTemplate { get; set; } public DataTemplate GroupTemplate { get; set; } protected override DataTemplate OnSelectTemplate(object item, BindableObject container) { + if (item is SendGroupingsPageHeaderListItem) + { + return HeaderTemplate; + } + if (item is SendGroupingsPageListItem listItem) { return listItem.Send != null ? SendTemplate : GroupTemplate; diff --git a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs index 971bb0b67..66f207211 100644 --- a/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs +++ b/src/App/Pages/Send/SendGroupingsPage/SendGroupingsPageViewModel.cs @@ -10,6 +10,7 @@ using Bit.Core.Abstractions; using Bit.Core.Enums; using Bit.Core.Models.View; using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Essentials; using Xamarin.Forms; using DeviceType = Bit.Core.Enums.DeviceType; @@ -48,7 +49,7 @@ namespace Bit.App.Pages Loading = true; PageTitle = AppResources.Send; - GroupedSends = new ExtendedObservableCollection(); + GroupedSends = new ObservableRangeCollection(); RefreshCommand = new Command(async () => { Refreshing = true; @@ -103,7 +104,7 @@ namespace Bit.App.Pages get => _showList; set => SetProperty(ref _showList, value); } - public ExtendedObservableCollection GroupedSends { get; set; } + public ObservableRangeCollection GroupedSends { get; set; } public Command RefreshCommand { get; set; } public Command SendOptionsCommand { get; set; } public bool LoadedOnce { get; set; } @@ -175,7 +176,33 @@ namespace Bit.App.Pages MainPage ? AppResources.AllSends : AppResources.Sends, sendsListItems.Count, uppercaseGroupNames, !MainPage)); } - GroupedSends.ResetWithRange(groupedSends); + + // TODO: refactor this + if (Device.RuntimePlatform == Device.Android) + { + var items = new List(); + foreach (var itemGroup in groupedSends) + { + items.Add(new SendGroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + items.AddRange(itemGroup); + } + + GroupedSends.ReplaceRange(items); + } + else + { + // HACK: This waitings are to avoid crash on iOS + GroupedSends.Clear(); + await Task.Delay(60); + + foreach (var itemGroup in groupedSends) + { + GroupedSends.Add(new SendGroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + await Task.Delay(60); + + GroupedSends.AddRange(itemGroup); + } + } } finally { diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index 85cd338b4..0af8a2ea6 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -513,13 +513,36 @@ namespace Bit.App.Pages new SettingsPageListGroup(toolsItems, AppResources.Tools, doUpper), new SettingsPageListGroup(otherItems, AppResources.Other, doUpper) }; - var settingsItems = new List(); - foreach (var itemGroup in settingsListGroupItems) + + // TODO: refactor this + if (Device.RuntimePlatform == Device.Android) { - settingsItems.Add(new SettingsPageHeaderListItem(itemGroup.Name)); - settingsItems.AddRange(itemGroup); + var items = new List(); + foreach (var itemGroup in settingsListGroupItems) + { + items.Add(new SettingsPageHeaderListItem(itemGroup.Name)); + items.AddRange(itemGroup); + } + + GroupedItems.ReplaceRange(items); + } + else + { + Device.InvokeOnMainThreadAsync(async () => + { + // HACK: This waitings are to avoid crash on iOS + GroupedItems.Clear(); + await Task.Delay(60); + + foreach (var itemGroup in settingsListGroupItems) + { + GroupedItems.Add(new SettingsPageHeaderListItem(itemGroup.Name)); + await Task.Delay(60); + + GroupedItems.AddRange(itemGroup); + } + }).FireAndForget(); } - GroupedItems.ReplaceRange(settingsItems); } private bool IncludeLinksWithSubscriptionInfo() diff --git a/src/App/Pages/Vault/AutofillCiphersPage.xaml b/src/App/Pages/Vault/AutofillCiphersPage.xaml index 8b9bdeff0..5bb8b4004 100644 --- a/src/App/Pages/Vault/AutofillCiphersPage.xaml +++ b/src/App/Pages/Vault/AutofillCiphersPage.xaml @@ -29,8 +29,28 @@ ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}" WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" /> + + + + + + + + @@ -52,30 +72,9 @@ ItemsSource="{Binding GroupedItems}" VerticalOptions="FillAndExpand" ItemTemplate="{StaticResource listItemDataTemplateSelector}" - IsGrouped="True" SelectionMode="Single" SelectionChanged="RowSelected" - StyleClass="list, list-platform"> - - - - - - - - - - - + StyleClass="list, list-platform" /> diff --git a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs index dc57e9915..5cad8f53e 100644 --- a/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs +++ b/src/App/Pages/Vault/AutofillCiphersPageViewModel.cs @@ -11,6 +11,7 @@ using Bit.Core.Utilities; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; namespace Bit.App.Pages @@ -36,14 +37,14 @@ namespace Bit.App.Pages _stateService = ServiceContainer.Resolve("stateService"); _passwordRepromptService = ServiceContainer.Resolve("passwordRepromptService"); - GroupedItems = new ExtendedObservableCollection(); + GroupedItems = new ObservableRangeCollection(); CipherOptionsCommand = new Command(CipherOptionsAsync); } public string Name { get; set; } public string Uri { get; set; } public Command CipherOptionsCommand { get; set; } - public ExtendedObservableCollection GroupedItems { get; set; } + public ObservableRangeCollection GroupedItems { get; set; } public bool ShowList { @@ -105,7 +106,33 @@ namespace Bit.App.Pages new GroupingsPageListGroup(fuzzy, AppResources.PossibleMatchingItems, fuzzy.Count, false, !hasMatching)); } - GroupedItems.ResetWithRange(groupedItems); + + // TODO: refactor this + if (Device.RuntimePlatform == Device.Android) + { + var items = new List(); + foreach (var itemGroup in groupedItems) + { + items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + items.AddRange(itemGroup); + } + + GroupedItems.ReplaceRange(items); + } + else + { + // HACK: This waitings are to avoid crash on iOS + GroupedItems.Clear(); + await Task.Delay(60); + + foreach (var itemGroup in groupedItems) + { + GroupedItems.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + await Task.Delay(60); + + GroupedItems.AddRange(itemGroup); + } + } ShowList = groupedItems.Any(); } diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml index 2d5a000f5..bb7c71535 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPage.xaml @@ -55,30 +55,53 @@ + StyleClass="list-row, list-row-platform"> + HorizontalOptions="Start" + VerticalOptions="Center" + StyleClass="list-icon, list-icon-platform" + ShouldUpdateFontSizeDynamicallyForAccesibility="True"> + + + + + + + + + @@ -105,31 +128,9 @@ ItemsSource="{Binding GroupedItems}" VerticalOptions="FillAndExpand" ItemTemplate="{StaticResource listItemDataTemplateSelector}" - IsGrouped="True" SelectionMode="Single" SelectionChanged="RowSelected" - StyleClass="list, list-platform"> - - - - - - - - - - - - + StyleClass="list, list-platform" /> diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageHeaderListItem.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageHeaderListItem.cs new file mode 100644 index 000000000..e7912ebc2 --- /dev/null +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageHeaderListItem.cs @@ -0,0 +1,14 @@ +namespace Bit.App.Pages +{ + public class GroupingsPageHeaderListItem : IGroupingsPageListItem + { + public GroupingsPageHeaderListItem(string title, string itemCount) + { + Title = title; + ItemCount = itemCount; + } + + public string Title { get; } + public string ItemCount { get; set; } + } +} diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageListItem.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageListItem.cs index 11e15677a..34674d961 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageListItem.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageListItem.cs @@ -5,7 +5,7 @@ using Bit.Core.Models.View; namespace Bit.App.Pages { - public class GroupingsPageListItem + public class GroupingsPageListItem : IGroupingsPageListItem { private string _icon; private string _name; diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageListItemSelector.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageListItemSelector.cs index 667aeee96..a2e2207b4 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageListItemSelector.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageListItemSelector.cs @@ -4,11 +4,17 @@ namespace Bit.App.Pages { public class GroupingsPageListItemSelector : DataTemplateSelector { + public DataTemplate HeaderTemplate { get; set; } public DataTemplate CipherTemplate { get; set; } public DataTemplate GroupTemplate { get; set; } protected override DataTemplate OnSelectTemplate(object item, BindableObject container) { + if (item is GroupingsPageHeaderListItem) + { + return HeaderTemplate; + } + if (item is GroupingsPageListItem listItem) { return listItem.Cipher != null ? CipherTemplate : GroupTemplate; diff --git a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs index 941660b37..7ffe1f982 100644 --- a/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs +++ b/src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs @@ -11,6 +11,7 @@ using Bit.Core.Enums; using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; +using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.Forms; namespace Bit.App.Pages @@ -63,7 +64,7 @@ namespace Bit.App.Pages Loading = true; PageTitle = AppResources.MyVault; - GroupedItems = new ExtendedObservableCollection(); + GroupedItems = new ObservableRangeCollection(); RefreshCommand = new Command(async () => { Refreshing = true; @@ -144,7 +145,7 @@ namespace Bit.App.Pages public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; } - public ExtendedObservableCollection GroupedItems { get; set; } + public ObservableRangeCollection GroupedItems { get; set; } public Command RefreshCommand { get; set; } public Command CipherOptionsCommand { get; set; } public bool LoadedOnce { get; set; } @@ -275,12 +276,38 @@ namespace Bit.App.Pages { new GroupingsPageListItem() { - IsTrash = true, + IsTrash = true, ItemCount = _deletedCount.ToString("N0") } }, AppResources.Trash, _deletedCount, uppercaseGroupNames, false)); } - GroupedItems.ResetWithRange(groupedItems); + + // TODO: refactor this + if (Device.RuntimePlatform == Device.Android) + { + var items = new List(); + foreach (var itemGroup in groupedItems) + { + items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + items.AddRange(itemGroup); + } + + GroupedItems.ReplaceRange(items); + } + else + { + // HACK: This waitings are to avoid crash on iOS + GroupedItems.Clear(); + await Task.Delay(60); + + foreach (var itemGroup in groupedItems) + { + GroupedItems.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount)); + await Task.Delay(60); + + GroupedItems.AddRange(itemGroup); + } + } } finally { diff --git a/src/App/Pages/Vault/GroupingsPage/IGroupingsPageListItem.cs b/src/App/Pages/Vault/GroupingsPage/IGroupingsPageListItem.cs new file mode 100644 index 000000000..56cb68d87 --- /dev/null +++ b/src/App/Pages/Vault/GroupingsPage/IGroupingsPageListItem.cs @@ -0,0 +1,6 @@ +namespace Bit.App.Pages +{ + public interface IGroupingsPageListItem + { + } +} diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs index 7de023dc7..0b213e832 100644 --- a/src/iOS/AppDelegate.cs +++ b/src/iOS/AppDelegate.cs @@ -228,11 +228,7 @@ namespace Bit.iOS public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) { - if (Xamarin.Essentials.Platform.OpenUrl(app, url, options)) - { - return true; - } - return base.OpenUrl(app, url, options); + return Xamarin.Essentials.Platform.OpenUrl(app, url, options); } public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,