mirror of
https://github.com/bitwarden/mobile
synced 2025-12-10 21:33:36 +00:00
Compare commits
20 Commits
community/
...
v2.16.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea28fd462d | ||
|
|
14b6ccca6d | ||
|
|
4202a90674 | ||
|
|
ad55ba232f | ||
|
|
1f8620dc17 | ||
|
|
a4bc46f408 | ||
|
|
62f1522af3 | ||
|
|
3d0a405d7d | ||
|
|
bb18e40d00 | ||
|
|
6305b4d292 | ||
|
|
3562e2bac6 | ||
|
|
403f78ceca | ||
|
|
e4e52a41e0 | ||
|
|
2e1de95461 | ||
|
|
52f1143ad7 | ||
|
|
56de47960d | ||
|
|
9b17392e06 | ||
|
|
6bed326f28 | ||
|
|
6453836866 | ||
|
|
0068674bac |
BIN
.github/secrets/dist_autofill.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_autofill.mobileprovision.gpg
vendored
Binary file not shown.
BIN
.github/secrets/dist_bitwarden.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_bitwarden.mobileprovision.gpg
vendored
Binary file not shown.
BIN
.github/secrets/dist_extension.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_extension.mobileprovision.gpg
vendored
Binary file not shown.
Binary file not shown.
BIN
.github/secrets/iphone-distribution-cert.p12.gpg
vendored
BIN
.github/secrets/iphone-distribution-cert.p12.gpg
vendored
Binary file not shown.
BIN
.github/secrets/play_creds.json.gpg
vendored
BIN
.github/secrets/play_creds.json.gpg
vendored
Binary file not shown.
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
echo "::set-output name=rc_branch_exists::0"
|
echo "::set-output name=rc_branch_exists::0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $(git ls-remote --heads origin hotfix) ]]; then
|
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
|
||||||
echo "::set-output name=hotfix_branch_exists::1"
|
echo "::set-output name=hotfix_branch_exists::1"
|
||||||
else
|
else
|
||||||
echo "::set-output name=hotfix_branch_exists::0"
|
echo "::set-output name=hotfix_branch_exists::0"
|
||||||
@@ -182,9 +182,9 @@ jobs:
|
|||||||
&& needs.setup.outputs.rc_branch_exists == 0
|
&& needs.setup.outputs.rc_branch_exists == 0
|
||||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| github.ref == 'refs/heads/hotfix'
|
|| github.ref == 'refs/heads/hotfix-rc'
|
||||||
run: |
|
run: |
|
||||||
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll"
|
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
|
||||||
CREDS_PATH="$HOME/secrets/play_creds.json"
|
CREDS_PATH="$HOME/secrets/play_creds.json"
|
||||||
AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
|
AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
|
||||||
TRACK="internal"
|
TRACK="internal"
|
||||||
@@ -514,7 +514,7 @@ jobs:
|
|||||||
&& needs.setup.outputs.rc_branch_exists == 0
|
&& needs.setup.outputs.rc_branch_exists == 0
|
||||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| github.ref == 'refs/heads/hotfix'
|
|| github.ref == 'refs/heads/hotfix-rc'
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '14'
|
||||||
@@ -526,7 +526,7 @@ jobs:
|
|||||||
&& needs.setup.outputs.rc_branch_exists == 0
|
&& needs.setup.outputs.rc_branch_exists == 0
|
||||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| github.ref == 'refs/heads/hotfix'
|
|| github.ref == 'refs/heads/hotfix-rc'
|
||||||
env:
|
env:
|
||||||
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
|
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
|
||||||
run: |
|
run: |
|
||||||
@@ -539,7 +539,7 @@ jobs:
|
|||||||
&& needs.setup.outputs.rc_branch_exists == 0
|
&& needs.setup.outputs.rc_branch_exists == 0
|
||||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| github.ref == 'refs/heads/hotfix'
|
|| github.ref == 'refs/heads/hotfix-rc'
|
||||||
env:
|
env:
|
||||||
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
||||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
@@ -602,7 +602,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
(github.ref == 'refs/heads/master')
|
(github.ref == 'refs/heads/master')
|
||||||
|| (github.ref == 'refs/heads/rc')
|
|| (github.ref == 'refs/heads/rc')
|
||||||
|| (github.ref == 'refs/heads/hotfix')
|
|| (github.ref == 'refs/heads/hotfix-rc')
|
||||||
env:
|
env:
|
||||||
CLOC_STATUS: ${{ needs.cloc.result }}
|
CLOC_STATUS: ${{ needs.cloc.result }}
|
||||||
ANDROID_STATUS: ${{ needs.android.result }}
|
ANDROID_STATUS: ${{ needs.android.result }}
|
||||||
|
|||||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -22,9 +22,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Branch check
|
- name: Branch check
|
||||||
run: |
|
run: |
|
||||||
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then
|
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
|
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -68,13 +68,16 @@ jobs:
|
|||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
branch: ${{ steps.branch.outputs.branch-name }}
|
branch: ${{ steps.branch.outputs.branch-name }}
|
||||||
|
|
||||||
|
- name: Prep Bitwarden iOS release asset
|
||||||
|
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
|
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
|
||||||
with:
|
with:
|
||||||
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
|
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
|
||||||
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
|
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
|
||||||
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
||||||
./Bitwarden.ipa/Bitwarden.ipa"
|
./Bitwarden iOS.zip"
|
||||||
commit: ${{ github.sha }}
|
commit: ${{ github.sha }}
|
||||||
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
||||||
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.16.2" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.16.4" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public interface ISendGroupingsPageListItem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,7 +72,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}" />
|
||||||
|
|
||||||
@@ -113,33 +135,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"
|
|
||||||
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
|
|
||||||
<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;
|
||||||
@@ -50,7 +51,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;
|
||||||
@@ -105,7 +106,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; }
|
||||||
@@ -177,7 +178,49 @@ 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
|
||||||
|
||
|
||||||
|
GroupedSends.Any())
|
||||||
|
{
|
||||||
|
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: we need this on iOS, so that it doesn't crash when adding coming from an empty list
|
||||||
|
var first = true;
|
||||||
|
var items = new List<ISendGroupingsPageListItem>();
|
||||||
|
foreach (var itemGroup in groupedSends)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
items.Add(new SendGroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupedSends.Any())
|
||||||
|
{
|
||||||
|
GroupedSends.ReplaceRange(new List<ISendGroupingsPageListItem> { new SendGroupingsPageHeaderListItem(groupedSends[0].Name, groupedSends[0].ItemCount) });
|
||||||
|
GroupedSends.AddRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GroupedSends.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public interface ISettingsPageListItem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,8 +82,26 @@
|
|||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="headerTemplate"
|
||||||
|
x:DataType="pages:SettingsPageHeaderListItem">
|
||||||
|
<StackLayout
|
||||||
|
Padding="0" Spacing="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" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
<pages:SettingsPageListItemSelector
|
<pages:SettingsPageListItemSelector
|
||||||
x:Key="listItemDataTemplateSelector"
|
x:Key="listItemDataTemplateSelector"
|
||||||
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
RegularTemplate="{StaticResource regularTemplate}"
|
RegularTemplate="{StaticResource regularTemplate}"
|
||||||
TimePickerTemplate="{StaticResource timePickerTemplate}" />
|
TimePickerTemplate="{StaticResource timePickerTemplate}" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
@@ -93,28 +111,8 @@
|
|||||||
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:SettingsPageListGroup">
|
|
||||||
<StackLayout
|
|
||||||
Padding="0" Spacing="0" VerticalOptions="FillAndExpand"
|
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
|
||||||
<BoxView
|
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform"
|
|
||||||
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
|
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
|
||||||
<Label
|
|
||||||
Text="{Binding Name}"
|
|
||||||
StyleClass="list-header, list-header-platform" />
|
|
||||||
</StackLayout>
|
|
||||||
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.GroupHeaderTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
|
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class SettingsPageHeaderListItem : ISettingsPageListItem
|
||||||
|
{
|
||||||
|
public SettingsPageHeaderListItem(string title)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Title { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class SettingsPageListItem
|
public class SettingsPageListItem : ISettingsPageListItem
|
||||||
{
|
{
|
||||||
public string Icon { get; set; }
|
public string Icon { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|||||||
@@ -4,21 +4,19 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class SettingsPageListItemSelector : DataTemplateSelector
|
public class SettingsPageListItemSelector : DataTemplateSelector
|
||||||
{
|
{
|
||||||
|
public DataTemplate HeaderTemplate { get; set; }
|
||||||
public DataTemplate RegularTemplate { get; set; }
|
public DataTemplate RegularTemplate { get; set; }
|
||||||
public DataTemplate TimePickerTemplate { get; set; }
|
public DataTemplate TimePickerTemplate { get; set; }
|
||||||
|
|
||||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||||
{
|
{
|
||||||
|
if (item is SettingsPageHeaderListItem)
|
||||||
|
{
|
||||||
|
return HeaderTemplate;
|
||||||
|
}
|
||||||
if (item is SettingsPageListItem listItem)
|
if (item is SettingsPageListItem listItem)
|
||||||
{
|
{
|
||||||
if (listItem.ShowTimeInput)
|
return listItem.ShowTimeInput ? TimePickerTemplate : RegularTemplate;
|
||||||
{
|
|
||||||
return TimePickerTemplate;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return RegularTemplate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using ZXing.Client.Result;
|
using ZXing.Client.Result;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@@ -82,11 +83,11 @@ namespace Bit.App.Pages
|
|||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
|
|
||||||
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
|
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtendedObservableCollection<SettingsPageListGroup> GroupedItems { get; set; }
|
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
@@ -509,7 +510,9 @@ namespace Bit.App.Pages
|
|||||||
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
||||||
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
||||||
};
|
};
|
||||||
GroupedItems.ResetWithRange(new List<SettingsPageListGroup>
|
|
||||||
|
// TODO: improve this. Leaving this as is to reduce error possibility on the hotfix.
|
||||||
|
var settingsListGroupItems = new List<SettingsPageListGroup>()
|
||||||
{
|
{
|
||||||
new SettingsPageListGroup(autofillItems, AppResources.Autofill, doUpper, true),
|
new SettingsPageListGroup(autofillItems, AppResources.Autofill, doUpper, true),
|
||||||
new SettingsPageListGroup(manageItems, AppResources.Manage, doUpper),
|
new SettingsPageListGroup(manageItems, AppResources.Manage, doUpper),
|
||||||
@@ -517,7 +520,50 @@ namespace Bit.App.Pages
|
|||||||
new SettingsPageListGroup(accountItems, AppResources.Account, doUpper),
|
new SettingsPageListGroup(accountItems, AppResources.Account, doUpper),
|
||||||
new SettingsPageListGroup(toolsItems, AppResources.Tools, doUpper),
|
new SettingsPageListGroup(toolsItems, AppResources.Tools, doUpper),
|
||||||
new SettingsPageListGroup(otherItems, AppResources.Other, doUpper)
|
new SettingsPageListGroup(otherItems, AppResources.Other, doUpper)
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android
|
||||||
|
||
|
||||||
|
GroupedItems.Any())
|
||||||
|
{
|
||||||
|
var items = new List<ISettingsPageListItem>();
|
||||||
|
foreach (var itemGroup in settingsListGroupItems)
|
||||||
|
{
|
||||||
|
items.Add(new SettingsPageHeaderListItem(itemGroup.Name));
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupedItems.ReplaceRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list
|
||||||
|
var first = true;
|
||||||
|
var items = new List<ISettingsPageListItem>();
|
||||||
|
foreach (var itemGroup in settingsListGroupItems)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
items.Add(new SettingsPageHeaderListItem(itemGroup.Name));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingsListGroupItems.Any())
|
||||||
|
{
|
||||||
|
GroupedItems.ReplaceRange(new List<ISettingsPageListItem> { new SettingsPageHeaderListItem(settingsListGroupItems[0].Name) });
|
||||||
|
GroupedItems.AddRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GroupedItems.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IncludeLinksWithSubscriptionInfo()
|
private bool IncludeLinksWithSubscriptionInfo()
|
||||||
|
|||||||
@@ -29,8 +29,28 @@
|
|||||||
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}"
|
ButtonCommand="{Binding BindingContext.CipherOptionsCommand, Source={x:Reference _page}}"
|
||||||
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>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
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
|
||||||
@@ -38,14 +39,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
|
||||||
{
|
{
|
||||||
@@ -108,7 +109,49 @@ 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
|
||||||
|
||
|
||||||
|
GroupedItems.Any())
|
||||||
|
{
|
||||||
|
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: we need this on iOS, so that it doesn't crash when adding coming from an empty list
|
||||||
|
var first = true;
|
||||||
|
var items = new List<IGroupingsPageListItem>();
|
||||||
|
foreach (var itemGroup in groupedItems)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupedItems.Any())
|
||||||
|
{
|
||||||
|
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||||
|
GroupedItems.AddRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GroupedItems.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
ShowList = groupedItems.Any();
|
ShowList = groupedItems.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<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"
|
||||||
@@ -56,19 +56,42 @@
|
|||||||
</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}" />
|
||||||
|
|
||||||
@@ -95,32 +118,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"
|
|
||||||
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
|
|
||||||
<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 System;
|
|||||||
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
|
||||||
@@ -65,7 +66,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;
|
||||||
@@ -139,7 +140,7 @@ namespace Bit.App.Pages
|
|||||||
get => _websiteIconsEnabled;
|
get => _websiteIconsEnabled;
|
||||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
}
|
}
|
||||||
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; }
|
||||||
@@ -271,12 +272,54 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
new GroupingsPageListItem()
|
new GroupingsPageListItem()
|
||||||
{
|
{
|
||||||
IsTrash = true,
|
IsTrash = true,
|
||||||
ItemCount = _deletedCount.ToString("N0")
|
ItemCount = _deletedCount.ToString("N0")
|
||||||
}
|
}
|
||||||
}, AppResources.Trash, _deletedCount, uppercaseGroupNames, false));
|
}, AppResources.Trash, _deletedCount, uppercaseGroupNames, false));
|
||||||
}
|
}
|
||||||
GroupedItems.ResetWithRange(groupedItems);
|
|
||||||
|
// TODO: refactor this
|
||||||
|
if (Device.RuntimePlatform == Device.Android
|
||||||
|
||
|
||||||
|
GroupedItems.Any())
|
||||||
|
{
|
||||||
|
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: we need this on iOS, so that it doesn't crash when adding coming from an empty list
|
||||||
|
var first = true;
|
||||||
|
var items = new List<IGroupingsPageListItem>();
|
||||||
|
foreach (var itemGroup in groupedItems)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
items.AddRange(itemGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupedItems.Any())
|
||||||
|
{
|
||||||
|
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||||
|
GroupedItems.AddRange(items);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GroupedItems.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public interface IGroupingsPageListItem
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.16.2</string>
|
<string>2.16.4</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.16.2</string>
|
<string>2.16.4</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.16.2</string>
|
<string>2.16.4</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -234,11 +234,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,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden</string>
|
<string>com.8bit.bitwarden</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.16.2</string>
|
<string>2.16.4</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<RootNamespace>Bit.Publisher</RootNamespace>
|
<RootNamespace>Bit.Publisher</RootNamespace>
|
||||||
<Configurations>Debug;Release;FDroid</Configurations>
|
<Configurations>Debug;Release;FDroid</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user