mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-25 12:05:59 +01:00
Fix iOS 15.4 crash from empty list to adding an item by awaiting after every header add; also added that on Settings just in case there is another crash scenario. (#1850)
This commit is contained in:
parent
c1748acf39
commit
22b00bcb33
@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public interface ISendGroupingsPageListItem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -73,7 +73,29 @@
|
|||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="headerTemplate"
|
||||||
|
x:DataType="pages:SendGroupingsPageHeaderListItem">
|
||||||
|
<StackLayout
|
||||||
|
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
||||||
|
StyleClass="list-row-header-container, list-row-header-container-platform">
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
||||||
|
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
||||||
|
<Label
|
||||||
|
Text="{Binding Title}"
|
||||||
|
StyleClass="list-header, list-header-platform" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding ItemCount}"
|
||||||
|
StyleClass="list-header-sub" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<pages:SendGroupingsPageListItemSelector x:Key="sendListItemDataTemplateSelector"
|
<pages:SendGroupingsPageListItemSelector x:Key="sendListItemDataTemplateSelector"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
SendTemplate="{StaticResource sendTemplate}"
|
SendTemplate="{StaticResource sendTemplate}"
|
||||||
GroupTemplate="{StaticResource sendGroupTemplate}" />
|
GroupTemplate="{StaticResource sendGroupTemplate}" />
|
||||||
|
|
||||||
@ -114,32 +136,9 @@
|
|||||||
ItemsSource="{Binding GroupedSends}"
|
ItemsSource="{Binding GroupedSends}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
||||||
IsGrouped="True"
|
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform" />
|
||||||
|
|
||||||
<CollectionView.GroupHeaderTemplate>
|
|
||||||
<DataTemplate x:DataType="pages:SendGroupingsPageListGroup">
|
|
||||||
<StackLayout
|
|
||||||
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
|
||||||
<Label
|
|
||||||
Text="{Binding Name}"
|
|
||||||
StyleClass="list-header, list-header-platform" />
|
|
||||||
<Label
|
|
||||||
Text="{Binding ItemCount}"
|
|
||||||
StyleClass="list-header-sub" />
|
|
||||||
</StackLayout>
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.GroupHeaderTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
</RefreshView>
|
</RefreshView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ using Bit.Core.Models.View;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class SendGroupingsPageListItem
|
public class SendGroupingsPageListItem : ISendGroupingsPageListItem
|
||||||
{
|
{
|
||||||
private string _icon;
|
private string _icon;
|
||||||
private string _name;
|
private string _name;
|
||||||
|
@ -4,11 +4,17 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class SendGroupingsPageListItemSelector : DataTemplateSelector
|
public class SendGroupingsPageListItemSelector : DataTemplateSelector
|
||||||
{
|
{
|
||||||
|
public DataTemplate HeaderTemplate { get; set; }
|
||||||
public DataTemplate SendTemplate { get; set; }
|
public DataTemplate SendTemplate { get; set; }
|
||||||
public DataTemplate GroupTemplate { get; set; }
|
public DataTemplate GroupTemplate { get; set; }
|
||||||
|
|
||||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||||
{
|
{
|
||||||
|
if (item is SendGroupingsPageHeaderListItem)
|
||||||
|
{
|
||||||
|
return HeaderTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
if (item is SendGroupingsPageListItem listItem)
|
if (item is SendGroupingsPageListItem listItem)
|
||||||
{
|
{
|
||||||
return listItem.Send != null ? SendTemplate : GroupTemplate;
|
return listItem.Send != null ? SendTemplate : GroupTemplate;
|
||||||
|
@ -10,6 +10,7 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using DeviceType = Bit.Core.Enums.DeviceType;
|
using DeviceType = Bit.Core.Enums.DeviceType;
|
||||||
@ -48,7 +49,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
Loading = true;
|
Loading = true;
|
||||||
PageTitle = AppResources.Send;
|
PageTitle = AppResources.Send;
|
||||||
GroupedSends = new ExtendedObservableCollection<SendGroupingsPageListGroup>();
|
GroupedSends = new ObservableRangeCollection<ISendGroupingsPageListItem>();
|
||||||
RefreshCommand = new Command(async () =>
|
RefreshCommand = new Command(async () =>
|
||||||
{
|
{
|
||||||
Refreshing = true;
|
Refreshing = true;
|
||||||
@ -103,7 +104,7 @@ namespace Bit.App.Pages
|
|||||||
get => _showList;
|
get => _showList;
|
||||||
set => SetProperty(ref _showList, value);
|
set => SetProperty(ref _showList, value);
|
||||||
}
|
}
|
||||||
public ExtendedObservableCollection<SendGroupingsPageListGroup> GroupedSends { get; set; }
|
public ObservableRangeCollection<ISendGroupingsPageListItem> GroupedSends { get; set; }
|
||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<SendView> SendOptionsCommand { get; set; }
|
public Command<SendView> SendOptionsCommand { get; set; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
@ -175,7 +176,33 @@ namespace Bit.App.Pages
|
|||||||
MainPage ? AppResources.AllSends : AppResources.Sends, sendsListItems.Count,
|
MainPage ? AppResources.AllSends : AppResources.Sends, sendsListItems.Count,
|
||||||
uppercaseGroupNames, !MainPage));
|
uppercaseGroupNames, !MainPage));
|
||||||
}
|
}
|
||||||
GroupedSends.ResetWithRange(groupedSends);
|
|
||||||
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
var items = new List<ISendGroupingsPageListItem>();
|
||||||
|
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
|
finally
|
||||||
{
|
{
|
||||||
|
@ -513,13 +513,36 @@ namespace Bit.App.Pages
|
|||||||
new SettingsPageListGroup(toolsItems, AppResources.Tools, doUpper),
|
new SettingsPageListGroup(toolsItems, AppResources.Tools, doUpper),
|
||||||
new SettingsPageListGroup(otherItems, AppResources.Other, doUpper)
|
new SettingsPageListGroup(otherItems, AppResources.Other, doUpper)
|
||||||
};
|
};
|
||||||
var settingsItems = new List<ISettingsPageListItem>();
|
|
||||||
foreach (var itemGroup in settingsListGroupItems)
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
settingsItems.Add(new SettingsPageHeaderListItem(itemGroup.Name));
|
var items = new List<ISettingsPageListItem>();
|
||||||
settingsItems.AddRange(itemGroup);
|
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()
|
private bool IncludeLinksWithSubscriptionInfo()
|
||||||
|
@ -30,7 +30,27 @@
|
|||||||
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
|
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="headerTemplate"
|
||||||
|
x:DataType="pages:GroupingsPageHeaderListItem">
|
||||||
|
<StackLayout
|
||||||
|
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
||||||
|
StyleClass="list-row-header-container, list-row-header-container-platform">
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
||||||
|
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
||||||
|
<Label
|
||||||
|
Text="{Binding Title}"
|
||||||
|
StyleClass="list-header, list-header-platform" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding ItemCount}"
|
||||||
|
StyleClass="list-header-sub" />
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
CipherTemplate="{StaticResource cipherTemplate}" />
|
CipherTemplate="{StaticResource cipherTemplate}" />
|
||||||
|
|
||||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||||
@ -52,30 +72,9 @@
|
|||||||
ItemsSource="{Binding GroupedItems}"
|
ItemsSource="{Binding GroupedItems}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
IsGrouped="True"
|
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform" />
|
||||||
|
|
||||||
<CollectionView.GroupHeaderTemplate>
|
|
||||||
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
|
|
||||||
<StackLayout
|
|
||||||
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
|
||||||
<Label
|
|
||||||
Text="{Binding Name}"
|
|
||||||
StyleClass="list-header, list-header-platform" />
|
|
||||||
<Label
|
|
||||||
Text="{Binding ItemCount}"
|
|
||||||
StyleClass="list-header-sub" />
|
|
||||||
</StackLayout>
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.GroupHeaderTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
@ -11,6 +11,7 @@ using Bit.Core.Utilities;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@ -36,14 +37,14 @@ namespace Bit.App.Pages
|
|||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
|
|
||||||
GroupedItems = new ExtendedObservableCollection<GroupingsPageListGroup>();
|
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Uri { get; set; }
|
public string Uri { get; set; }
|
||||||
public Command CipherOptionsCommand { get; set; }
|
public Command CipherOptionsCommand { get; set; }
|
||||||
public ExtendedObservableCollection<GroupingsPageListGroup> GroupedItems { get; set; }
|
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||||
|
|
||||||
public bool ShowList
|
public bool ShowList
|
||||||
{
|
{
|
||||||
@ -105,7 +106,33 @@ namespace Bit.App.Pages
|
|||||||
new GroupingsPageListGroup(fuzzy, AppResources.PossibleMatchingItems, fuzzy.Count, false,
|
new GroupingsPageListGroup(fuzzy, AppResources.PossibleMatchingItems, fuzzy.Count, false,
|
||||||
!hasMatching));
|
!hasMatching));
|
||||||
}
|
}
|
||||||
GroupedItems.ResetWithRange(groupedItems);
|
|
||||||
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
var items = new List<IGroupingsPageListItem>();
|
||||||
|
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();
|
ShowList = groupedItems.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,30 +55,53 @@
|
|||||||
<DataTemplate x:Key="groupTemplate"
|
<DataTemplate x:Key="groupTemplate"
|
||||||
x:DataType="pages:GroupingsPageListItem">
|
x:DataType="pages:GroupingsPageListItem">
|
||||||
<controls:ExtendedStackLayout Orientation="Horizontal"
|
<controls:ExtendedStackLayout Orientation="Horizontal"
|
||||||
StyleClass="list-row, list-row-platform">
|
StyleClass="list-row, list-row-platform">
|
||||||
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
StyleClass="list-icon, list-icon-platform"
|
StyleClass="list-icon, list-icon-platform"
|
||||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True">
|
ShouldUpdateFontSizeDynamicallyForAccesibility="True">
|
||||||
<controls:IconLabel.Effects>
|
<controls:IconLabel.Effects>
|
||||||
<effects:FixedSizeEffect />
|
<effects:FixedSizeEffect />
|
||||||
</controls:IconLabel.Effects>
|
</controls:IconLabel.Effects>
|
||||||
</controls:IconLabel>
|
</controls:IconLabel>
|
||||||
<Label Text="{Binding Name, Mode=OneWay}"
|
<Label Text="{Binding Name, Mode=OneWay}"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
StyleClass="list-title"/>
|
StyleClass="list-title"/>
|
||||||
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
StyleClass="list-sub"/>
|
StyleClass="list-sub"/>
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="headerTemplate"
|
||||||
|
x:DataType="pages:GroupingsPageHeaderListItem">
|
||||||
|
<StackLayout
|
||||||
|
Spacing="0"
|
||||||
|
Padding="0"
|
||||||
|
VerticalOptions="FillAndExpand"
|
||||||
|
StyleClass="list-row-header-container, list-row-header-container-platform">
|
||||||
|
<BoxView
|
||||||
|
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
||||||
|
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
||||||
|
<Label
|
||||||
|
Text="{Binding Title}"
|
||||||
|
StyleClass="list-header, list-header-platform" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding ItemCount}"
|
||||||
|
StyleClass="list-header-sub" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
CipherTemplate="{StaticResource cipherTemplate}"
|
CipherTemplate="{StaticResource cipherTemplate}"
|
||||||
GroupTemplate="{StaticResource groupTemplate}" />
|
GroupTemplate="{StaticResource groupTemplate}" />
|
||||||
|
|
||||||
@ -105,31 +128,9 @@
|
|||||||
ItemsSource="{Binding GroupedItems}"
|
ItemsSource="{Binding GroupedItems}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
IsGrouped="True"
|
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform" />
|
||||||
|
|
||||||
<CollectionView.GroupHeaderTemplate>
|
|
||||||
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
|
|
||||||
<StackLayout
|
|
||||||
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
|
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
|
||||||
<Label
|
|
||||||
Text="{Binding Name}"
|
|
||||||
StyleClass="list-header, list-header-platform" />
|
|
||||||
<Label
|
|
||||||
Text="{Binding ItemCount}"
|
|
||||||
StyleClass="list-header-sub" />
|
|
||||||
</StackLayout>
|
|
||||||
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.GroupHeaderTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
</RefreshView>
|
</RefreshView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ using Bit.Core.Models.View;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GroupingsPageListItem
|
public class GroupingsPageListItem : IGroupingsPageListItem
|
||||||
{
|
{
|
||||||
private string _icon;
|
private string _icon;
|
||||||
private string _name;
|
private string _name;
|
||||||
|
@ -4,11 +4,17 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class GroupingsPageListItemSelector : DataTemplateSelector
|
public class GroupingsPageListItemSelector : DataTemplateSelector
|
||||||
{
|
{
|
||||||
|
public DataTemplate HeaderTemplate { get; set; }
|
||||||
public DataTemplate CipherTemplate { get; set; }
|
public DataTemplate CipherTemplate { get; set; }
|
||||||
public DataTemplate GroupTemplate { get; set; }
|
public DataTemplate GroupTemplate { get; set; }
|
||||||
|
|
||||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||||
{
|
{
|
||||||
|
if (item is GroupingsPageHeaderListItem)
|
||||||
|
{
|
||||||
|
return HeaderTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
if (item is GroupingsPageListItem listItem)
|
if (item is GroupingsPageListItem listItem)
|
||||||
{
|
{
|
||||||
return listItem.Cipher != null ? CipherTemplate : GroupTemplate;
|
return listItem.Cipher != null ? CipherTemplate : GroupTemplate;
|
||||||
|
@ -11,6 +11,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@ -63,7 +64,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
Loading = true;
|
Loading = true;
|
||||||
PageTitle = AppResources.MyVault;
|
PageTitle = AppResources.MyVault;
|
||||||
GroupedItems = new ExtendedObservableCollection<GroupingsPageListGroup>();
|
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
||||||
RefreshCommand = new Command(async () =>
|
RefreshCommand = new Command(async () =>
|
||||||
{
|
{
|
||||||
Refreshing = true;
|
Refreshing = true;
|
||||||
@ -144,7 +145,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
|
|
||||||
public ExtendedObservableCollection<GroupingsPageListGroup> GroupedItems { get; set; }
|
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
@ -280,7 +281,33 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}, AppResources.Trash, _deletedCount, uppercaseGroupNames, false));
|
}, AppResources.Trash, _deletedCount, uppercaseGroupNames, false));
|
||||||
}
|
}
|
||||||
GroupedItems.ResetWithRange(groupedItems);
|
|
||||||
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
var items = new List<IGroupingsPageListItem>();
|
||||||
|
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
|
finally
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public interface IGroupingsPageListItem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -228,11 +228,7 @@ namespace Bit.iOS
|
|||||||
|
|
||||||
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
|
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
|
||||||
{
|
{
|
||||||
if (Xamarin.Essentials.Platform.OpenUrl(app, url, options))
|
return Xamarin.Essentials.Platform.OpenUrl(app, url, options);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return base.OpenUrl(app, url, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
|
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
|
||||||
|
Loading…
Reference in New Issue
Block a user