mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
16 Commits
vault/pm-7
...
v2.17.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ececcb7bd7 | ||
|
|
81cccf22db | ||
|
|
4e36bc83ae | ||
|
|
9db70119b9 | ||
|
|
3d485f6fb5 | ||
|
|
372c875cb2 | ||
|
|
2e60787128 | ||
|
|
d283586d96 | ||
|
|
3a2a1b527f | ||
|
|
d908d5e64d | ||
|
|
8c59552d41 | ||
|
|
77c4c423e0 | ||
|
|
47241fd5a9 | ||
|
|
51cfd70398 | ||
|
|
de566be994 | ||
|
|
819d1b616a |
25
.github/workflows/build.yml
vendored
25
.github/workflows/build.yml
vendored
@@ -47,7 +47,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"
|
||||||
@@ -186,7 +186,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'
|
||||||
run: |
|
run: |
|
||||||
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/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"
|
||||||
@@ -241,6 +241,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
||||||
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
|
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
|
||||||
|
$corePath = $($env:GITHUB_WORKSPACE + "/src/Core/Core.csproj");
|
||||||
|
|
||||||
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
|
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
|
||||||
|
|
||||||
@@ -306,6 +307,18 @@ jobs:
|
|||||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
||||||
|
|
||||||
$xml.Save($appPath);
|
$xml.Save($appPath);
|
||||||
|
|
||||||
|
Write-Output "########################################"
|
||||||
|
Write-Output "##### Uninstall from Core.csproj"
|
||||||
|
Write-Output "########################################"
|
||||||
|
|
||||||
|
$xml=New-Object XML;
|
||||||
|
$xml.Load($corePath);
|
||||||
|
|
||||||
|
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
||||||
|
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
||||||
|
|
||||||
|
$xml.Save($corePath);
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Restore packages
|
- name: Restore packages
|
||||||
@@ -518,7 +531,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'
|
||||||
@@ -530,7 +543,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: appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
|
run: appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
|
||||||
@@ -542,7 +555,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 }}
|
||||||
@@ -605,7 +618,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 }}
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -24,9 +24,9 @@ jobs:
|
|||||||
- name: Branch check
|
- name: Branch check
|
||||||
if: github.event.inputs.release_type != 'dry-run'
|
if: github.event.inputs.release_type != 'dry-run'
|
||||||
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
|
||||||
|
|||||||
@@ -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.3" 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.17.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
|
|||||||
@@ -943,5 +943,10 @@ namespace Bit.Droid.Services
|
|||||||
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
||||||
return activity?.Resources?.Configuration?.FontScale ?? 1;
|
return activity?.Resources?.Configuration?.FontScale ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task OnAccountSwitchCompleteAsync()
|
||||||
|
{
|
||||||
|
// for any Android-specific cleanup required after switching accounts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,5 +46,6 @@ namespace Bit.App.Abstractions
|
|||||||
void CloseMainApp();
|
void CloseMainApp();
|
||||||
bool SupportsFido2();
|
bool SupportsFido2();
|
||||||
float GetSystemFontSizeScale();
|
float GetSystemFontSizeScale();
|
||||||
|
Task OnAccountSwitchCompleteAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,8 +73,9 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
else if (message.Command == "locked")
|
else if (message.Command == "locked")
|
||||||
{
|
{
|
||||||
var (userId, userInitiated) =
|
var extras = message.Data as Tuple<string, bool>;
|
||||||
message.Data as Tuple<string, bool> ?? new Tuple<string, bool>(null, false);
|
var userId = extras?.Item1;
|
||||||
|
var userInitiated = extras?.Item2 ?? false;
|
||||||
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
||||||
}
|
}
|
||||||
else if (message.Command == "lockVault")
|
else if (message.Command == "lockVault")
|
||||||
@@ -83,8 +84,10 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
else if (message.Command == "logout")
|
else if (message.Command == "logout")
|
||||||
{
|
{
|
||||||
var (userId, userInitiated, expired) =
|
var extras = message.Data as Tuple<string, bool, bool>;
|
||||||
message.Data as Tuple<string, bool, bool> ?? new Tuple<string, bool, bool>(null, true, false);
|
var userId = extras?.Item1;
|
||||||
|
var userInitiated = extras?.Item2 ?? true;
|
||||||
|
var expired = extras?.Item3 ?? false;
|
||||||
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
|
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
|
||||||
}
|
}
|
||||||
else if (message.Command == "loggedOut")
|
else if (message.Command == "loggedOut")
|
||||||
@@ -425,7 +428,7 @@ namespace Bit.App
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LockedAsync(string userId, bool autoPromptBiometric)
|
private async Task LockedAsync(string userId, bool userInitiated)
|
||||||
{
|
{
|
||||||
if (!await _stateService.IsActiveAccountAsync(userId))
|
if (!await _stateService.IsActiveAccountAsync(userId))
|
||||||
{
|
{
|
||||||
@@ -433,6 +436,7 @@ namespace Bit.App
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var autoPromptBiometric = !userInitiated;
|
||||||
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
|
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
|
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
|
||||||
|
|||||||
@@ -59,26 +59,26 @@
|
|||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Text="{Binding AccountView.Email}"
|
Text="{Binding AccountView.Email}"
|
||||||
IsVisible="{Binding IsActive}"
|
IsVisible="{Binding IsActive}"
|
||||||
StyleClass="list-title"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Text="{Binding AccountView.Email}"
|
Text="{Binding AccountView.Email}"
|
||||||
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="list-title"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
TextColor="{DynamicResource MutedColor}"
|
TextColor="{DynamicResource MutedColor}"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
IsVisible="{Binding ShowHostname}"
|
IsVisible="{Binding ShowHostname}"
|
||||||
Text="{Binding AccountView.Hostname}"
|
Text="{Binding AccountView.Hostname}"
|
||||||
StyleClass="list-sub"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountUnlocked}"
|
Text="{u:I18n AccountUnlocked}"
|
||||||
IsVisible="{Binding IsUnlockedAndNotActive}"
|
IsVisible="{Binding IsUnlockedAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLocked}"
|
Text="{u:I18n AccountLocked}"
|
||||||
IsVisible="{Binding IsLockedAndNotActive}"
|
IsVisible="{Binding IsLockedAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLoggedOut}"
|
Text="{u:I18n AccountLoggedOut}"
|
||||||
IsVisible="{Binding IsLoggedOutAndNotActive}"
|
IsVisible="{Binding IsLoggedOutAndNotActive}"
|
||||||
StyleClass="list-sub"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation" />
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="False" />
|
AutomationProperties.IsInAccessibleTree="False" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n AddAccount}"
|
Text="{u:I18n AddAccount}"
|
||||||
StyleClass="list-title"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
Grid.Column="1" />
|
Grid.Column="1" />
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ namespace Bit.App.Controls
|
|||||||
upperData = _data.ToUpper();
|
upperData = _data.ToUpper();
|
||||||
chars = GetFirstLetters(upperData, 2);
|
chars = GetFirstLetters(upperData, 2);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chars = upperData = _data.ToUpper();
|
||||||
|
}
|
||||||
|
|
||||||
var bgColor = StringToColor(upperData);
|
var bgColor = StringToColor(upperData);
|
||||||
var textColor = Color.White;
|
var textColor = Color.White;
|
||||||
@@ -122,7 +126,7 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
return data.Substring(0, 2);
|
return data.Substring(0, 2);
|
||||||
}
|
}
|
||||||
return null;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color StringToColor(string str)
|
private Color StringToColor(string str)
|
||||||
|
|||||||
@@ -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,33 +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"
|
|
||||||
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;
|
||||||
@@ -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,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,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
|
||||||
{
|
{
|
||||||
@@ -79,11 +80,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()
|
||||||
{
|
{
|
||||||
@@ -501,7 +502,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),
|
||||||
@@ -509,7 +512,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()
|
||||||
|
|||||||
@@ -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,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,30 @@
|
|||||||
</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,32 +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"
|
|
||||||
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 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,49 @@ 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
|
||||||
|
||
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@ namespace Bit.App.Services
|
|||||||
Constants.iOSAutoFillBiometricIntegrityKey,
|
Constants.iOSAutoFillBiometricIntegrityKey,
|
||||||
Constants.iOSExtensionClearCiphersCacheKey,
|
Constants.iOSExtensionClearCiphersCacheKey,
|
||||||
Constants.iOSExtensionBiometricIntegrityKey,
|
Constants.iOSExtensionBiometricIntegrityKey,
|
||||||
|
Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||||
|
Constants.iOSShareExtensionBiometricIntegrityKey,
|
||||||
Constants.RememberedEmailKey,
|
Constants.RememberedEmailKey,
|
||||||
Constants.RememberedOrgIdentifierKey,
|
Constants.RememberedOrgIdentifierKey,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
<Setter Property="TextColor"
|
<Setter Property="TextColor"
|
||||||
Value="{DynamicResource ButtonTextColor}" />
|
Value="{DynamicResource ButtonTextColor}" />
|
||||||
<Setter Property="FontSize"
|
<Setter Property="FontSize"
|
||||||
Value="18" />
|
Value="Medium" />
|
||||||
<Setter Property="CornerRadius"
|
<Setter Property="CornerRadius"
|
||||||
Value="5" />
|
Value="5" />
|
||||||
<Setter Property="Margin"
|
<Setter Property="Margin"
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
<Setter Property="TextColor"
|
<Setter Property="TextColor"
|
||||||
Value="{DynamicResource ButtonPrimaryTextColor}" />
|
Value="{DynamicResource ButtonPrimaryTextColor}" />
|
||||||
<Setter Property="FontSize"
|
<Setter Property="FontSize"
|
||||||
Value="18" />
|
Value="Medium" />
|
||||||
<Setter Property="FontAttributes"
|
<Setter Property="FontAttributes"
|
||||||
Value="Bold" />
|
Value="Bold" />
|
||||||
<Setter Property="CornerRadius"
|
<Setter Property="CornerRadius"
|
||||||
@@ -298,6 +298,18 @@
|
|||||||
<Setter Property="FontSize"
|
<Setter Property="FontSize"
|
||||||
Value="25" />
|
Value="25" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style TargetType="Label"
|
||||||
|
Class="accountlist-title-platform"
|
||||||
|
ApplyToDerivedTypes="True">
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="Body" />
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Label"
|
||||||
|
Class="accountlist-sub-platform"
|
||||||
|
ApplyToDerivedTypes="True">
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="Caption" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- Box -->
|
<!-- Box -->
|
||||||
|
|
||||||
|
|||||||
@@ -221,6 +221,14 @@
|
|||||||
<Setter Property="TextColor"
|
<Setter Property="TextColor"
|
||||||
Value="{DynamicResource MutedColor}" />
|
Value="{DynamicResource MutedColor}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style TargetType="Label"
|
||||||
|
Class="accountlist-title">
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Label"
|
||||||
|
Class="accountlist-sub">
|
||||||
|
<Setter Property="TextColor"
|
||||||
|
Value="{DynamicResource MutedColor}" />
|
||||||
|
</Style>
|
||||||
<Style TargetType="Label"
|
<Style TargetType="Label"
|
||||||
ApplyToDerivedTypes="True"
|
ApplyToDerivedTypes="True"
|
||||||
Class="list-title-icon">
|
Class="list-title-icon">
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
<Setter Property="TextColor"
|
<Setter Property="TextColor"
|
||||||
Value="{DynamicResource ButtonTextColor}" />
|
Value="{DynamicResource ButtonTextColor}" />
|
||||||
<Setter Property="FontSize"
|
<Setter Property="FontSize"
|
||||||
Value="18" />
|
Value="Medium" />
|
||||||
<Setter Property="Margin"
|
<Setter Property="Margin"
|
||||||
Value="0, 5, 0, 0" />
|
Value="0, 5, 0, 0" />
|
||||||
<Setter Property="VisualStateManager.VisualStateGroups">
|
<Setter Property="VisualStateManager.VisualStateGroups">
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
<Setter Property="TextColor"
|
<Setter Property="TextColor"
|
||||||
Value="{DynamicResource ButtonPrimaryTextColor}" />
|
Value="{DynamicResource ButtonPrimaryTextColor}" />
|
||||||
<Setter Property="FontSize"
|
<Setter Property="FontSize"
|
||||||
Value="18" />
|
Value="Medium" />
|
||||||
<Setter Property="FontAttributes"
|
<Setter Property="FontAttributes"
|
||||||
Value="Bold" />
|
Value="Bold" />
|
||||||
<Setter Property="Margin"
|
<Setter Property="Margin"
|
||||||
@@ -320,6 +320,16 @@
|
|||||||
<Setter Property="FontSize"
|
<Setter Property="FontSize"
|
||||||
Value="25" />
|
Value="25" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style TargetType="Label"
|
||||||
|
Class="accountlist-title-platform"
|
||||||
|
ApplyToDerivedTypes="True">
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Label"
|
||||||
|
Class="accountlist-sub-platform"
|
||||||
|
ApplyToDerivedTypes="True">
|
||||||
|
<Setter Property="FontSize"
|
||||||
|
Value="Small" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- Box -->
|
<!-- Box -->
|
||||||
|
|
||||||
|
|||||||
@@ -502,41 +502,25 @@ namespace Bit.App.Utilities
|
|||||||
|
|
||||||
public static async Task LogOutAsync(string userId, bool userInitiated = false)
|
public static async Task LogOutAsync(string userId, bool userInitiated = false)
|
||||||
{
|
{
|
||||||
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
|
|
||||||
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
|
||||||
var settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
|
|
||||||
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
|
||||||
var folderService = ServiceContainer.Resolve<IFolderService>("folderService");
|
|
||||||
var collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
|
|
||||||
var passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
|
|
||||||
"passwordGenerationService");
|
|
||||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
|
||||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
|
||||||
var searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
|
||||||
|
|
||||||
var isActiveAccount = await stateService.IsActiveAccountAsync(userId);
|
var isActiveAccount = await stateService.IsActiveAccountAsync(userId);
|
||||||
|
|
||||||
|
var isAccountRemoval = await vaultTimeoutService.IsLoggedOutByTimeoutAsync(userId) ||
|
||||||
|
await vaultTimeoutService.ShouldLogOutByTimeoutAsync(userId);
|
||||||
|
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
userId = await stateService.GetActiveUserIdAsync();
|
userId = await stateService.GetActiveUserIdAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(
|
await stateService.LogoutAccountAsync(userId, userInitiated);
|
||||||
cipherService.ClearAsync(userId),
|
|
||||||
folderService.ClearAsync(userId),
|
|
||||||
collectionService.ClearAsync(userId),
|
|
||||||
passwordGenerationService.ClearAsync(userId),
|
|
||||||
deviceActionService.ClearCacheAsync(),
|
|
||||||
tokenService.ClearTokenAsync(userId),
|
|
||||||
cryptoService.ClearKeysAsync(userId),
|
|
||||||
settingsService.ClearAsync(userId),
|
|
||||||
vaultTimeoutService.ClearAsync(userId),
|
|
||||||
policyService.ClearAsync(userId),
|
|
||||||
stateService.LogoutAccountAsync(userId, userInitiated));
|
|
||||||
|
|
||||||
searchService.ClearIndex();
|
if (isActiveAccount)
|
||||||
|
{
|
||||||
|
await ClearServiceCacheAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (!userInitiated)
|
if (!userInitiated)
|
||||||
{
|
{
|
||||||
@@ -558,8 +542,7 @@ namespace Bit.App.Utilities
|
|||||||
if (!isActiveAccount)
|
if (!isActiveAccount)
|
||||||
{
|
{
|
||||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
if (await vaultTimeoutService.IsLoggedOutByTimeoutAsync(userId) ||
|
if (isAccountRemoval)
|
||||||
await vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
|
||||||
{
|
{
|
||||||
platformUtilsService.ShowToast("info", null, AppResources.AccountRemovedSuccessfully);
|
platformUtilsService.ShowToast("info", null, AppResources.AccountRemovedSuccessfully);
|
||||||
return;
|
return;
|
||||||
@@ -571,6 +554,14 @@ namespace Bit.App.Utilities
|
|||||||
public static async Task OnAccountSwitchAsync()
|
public static async Task OnAccountSwitchAsync()
|
||||||
{
|
{
|
||||||
var environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
var environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||||
|
await environmentService.SetUrlsFromStorageAsync();
|
||||||
|
await ClearServiceCacheAsync();
|
||||||
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
await deviceActionService.OnAccountSwitchCompleteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task ClearServiceCacheAsync()
|
||||||
|
{
|
||||||
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
|
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
|
||||||
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||||
var settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
|
var settingsService = ServiceContainer.Resolve<ISettingsService>("settingsService");
|
||||||
@@ -584,8 +575,6 @@ namespace Bit.App.Utilities
|
|||||||
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
var searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
var searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
||||||
|
|
||||||
await environmentService.SetUrlsFromStorageAsync();
|
|
||||||
|
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
cipherService.ClearCacheAsync(),
|
cipherService.ClearCacheAsync(),
|
||||||
deviceActionService.ClearCacheAsync());
|
deviceActionService.ClearCacheAsync());
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetAutofillTileAddedAsync(bool? value);
|
Task SetAutofillTileAddedAsync(bool? value);
|
||||||
Task<string> GetEmailAsync(string userId = null);
|
Task<string> GetEmailAsync(string userId = null);
|
||||||
Task<string> GetNameAsync(string userId = null);
|
Task<string> GetNameAsync(string userId = null);
|
||||||
|
Task SetNameAsync(string value, string userId = null);
|
||||||
Task<string> GetOrgIdentifierAsync(string userId = null);
|
Task<string> GetOrgIdentifierAsync(string userId = null);
|
||||||
Task<long?> GetLastActiveTimeAsync(string userId = null);
|
Task<long?> GetLastActiveTimeAsync(string userId = null);
|
||||||
Task SetLastActiveTimeAsync(long? value, string userId = null);
|
Task SetLastActiveTimeAsync(long? value, string userId = null);
|
||||||
@@ -62,8 +63,8 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetVaultTimeoutAsync(int? value, string userId = null);
|
Task SetVaultTimeoutAsync(int? value, string userId = null);
|
||||||
Task<VaultTimeoutAction?> GetVaultTimeoutActionAsync(string userId = null);
|
Task<VaultTimeoutAction?> GetVaultTimeoutActionAsync(string userId = null);
|
||||||
Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null);
|
Task SetVaultTimeoutActionAsync(VaultTimeoutAction? value, string userId = null);
|
||||||
Task<DateTime?> GetLastFileCacheClearAsync(string userId = null);
|
Task<DateTime?> GetLastFileCacheClearAsync();
|
||||||
Task SetLastFileCacheClearAsync(DateTime? value, string userId = null);
|
Task SetLastFileCacheClearAsync(DateTime? value);
|
||||||
Task<PreviousPageInfo> GetPreviousPageInfoAsync(string userId = null);
|
Task<PreviousPageInfo> GetPreviousPageInfoAsync(string userId = null);
|
||||||
Task SetPreviousPageInfoAsync(PreviousPageInfo value, string userId = null);
|
Task SetPreviousPageInfoAsync(PreviousPageInfo value, string userId = null);
|
||||||
Task<int> GetInvalidUnlockAttemptsAsync(string userId = null);
|
Task<int> GetInvalidUnlockAttemptsAsync(string userId = null);
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
public static string iOSAutoFillBiometricIntegrityKey = "iOSAutoFillBiometricIntegrityState";
|
public static string iOSAutoFillBiometricIntegrityKey = "iOSAutoFillBiometricIntegrityState";
|
||||||
public static string iOSExtensionClearCiphersCacheKey = "iOSExtensionClearCiphersCache";
|
public static string iOSExtensionClearCiphersCacheKey = "iOSExtensionClearCiphersCache";
|
||||||
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
||||||
|
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
||||||
|
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
||||||
public static string EventCollectionKey = "eventCollection";
|
public static string EventCollectionKey = "eventCollection";
|
||||||
public static string RememberedEmailKey = "rememberedEmail";
|
public static string RememberedEmailKey = "rememberedEmail";
|
||||||
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
||||||
@@ -39,7 +41,8 @@
|
|||||||
{
|
{
|
||||||
ClearCiphersCacheKey,
|
ClearCiphersCacheKey,
|
||||||
iOSAutoFillClearCiphersCacheKey,
|
iOSAutoFillClearCiphersCacheKey,
|
||||||
iOSExtensionClearCiphersCacheKey
|
iOSExtensionClearCiphersCacheKey,
|
||||||
|
iOSShareExtensionClearCiphersCacheKey
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string CiphersKey(string userId) => $"ciphers_{userId}";
|
public static string CiphersKey(string userId) => $"ciphers_{userId}";
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@@ -9,7 +8,6 @@ using Bit.Core.Models.Data;
|
|||||||
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 Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@@ -48,7 +46,6 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await CheckStateAsync();
|
|
||||||
return userId == await GetActiveUserIdAsync();
|
return userId == await GetActiveUserIdAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,8 +157,9 @@ namespace Bit.Core.Services
|
|||||||
await CheckStateAsync();
|
await CheckStateAsync();
|
||||||
await RemoveAccountAsync(userId, userInitiated);
|
await RemoveAccountAsync(userId, userInitiated);
|
||||||
|
|
||||||
// If user initiated logout (not vault timeout) find the next user to make active, if any
|
// If user initiated logout (not vault timeout) and ActiveUserId is null after account removal, find the
|
||||||
if (userInitiated && _state?.Accounts != null)
|
// next user to make active, if any
|
||||||
|
if (userInitiated && _state?.ActiveUserId == null && _state?.Accounts != null)
|
||||||
{
|
{
|
||||||
foreach (var account in _state.Accounts)
|
foreach (var account in _state.Accounts)
|
||||||
{
|
{
|
||||||
@@ -470,6 +468,15 @@ namespace Bit.Core.Services
|
|||||||
))?.Profile?.Name;
|
))?.Profile?.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetNameAsync(string value, string userId = null)
|
||||||
|
{
|
||||||
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
var account = await GetAccountAsync(reconciledOptions);
|
||||||
|
account.Profile.Name = value;
|
||||||
|
await SaveAccountAsync(account, reconciledOptions);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> GetOrgIdentifierAsync(string userId = null)
|
public async Task<string> GetOrgIdentifierAsync(string userId = null)
|
||||||
{
|
{
|
||||||
return (await GetAccountAsync(
|
return (await GetAccountAsync(
|
||||||
@@ -525,20 +532,18 @@ namespace Bit.Core.Services
|
|||||||
await SaveAccountAsync(account, reconciledOptions);
|
await SaveAccountAsync(account, reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DateTime?> GetLastFileCacheClearAsync(string userId = null)
|
public async Task<DateTime?> GetLastFileCacheClearAsync()
|
||||||
{
|
{
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
var options = await GetDefaultStorageOptionsAsync();
|
||||||
await GetDefaultStorageOptionsAsync());
|
|
||||||
var key = Constants.LastFileCacheClearKey;
|
var key = Constants.LastFileCacheClearKey;
|
||||||
return await GetValueAsync<DateTime?>(key, reconciledOptions);
|
return await GetValueAsync<DateTime?>(key, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetLastFileCacheClearAsync(DateTime? value, string userId = null)
|
public async Task SetLastFileCacheClearAsync(DateTime? value)
|
||||||
{
|
{
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
var options = await GetDefaultStorageOptionsAsync();
|
||||||
await GetDefaultStorageOptionsAsync());
|
|
||||||
var key = Constants.LastFileCacheClearKey;
|
var key = Constants.LastFileCacheClearKey;
|
||||||
await SetValueAsync(key, value, reconciledOptions);
|
await SetValueAsync(key, value, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PreviousPageInfo> GetPreviousPageInfoAsync(string userId = null)
|
public async Task<PreviousPageInfo> GetPreviousPageInfoAsync(string userId = null)
|
||||||
@@ -1176,25 +1181,26 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
private async Task<T> GetValueAsync<T>(string key, StorageOptions options)
|
private async Task<T> GetValueAsync<T>(string key, StorageOptions options)
|
||||||
{
|
{
|
||||||
var value = await GetStorageService(options).GetAsync<T>(key);
|
return await GetStorageService(options).GetAsync<T>(key);
|
||||||
Log("GET", options, key, JsonConvert.SerializeObject(value));
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetValueAsync<T>(string key, T value, StorageOptions options)
|
private async Task SetValueAsync<T>(string key, T value, StorageOptions options)
|
||||||
{
|
{
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
Log("REMOVE", options, key, null);
|
|
||||||
await GetStorageService(options).RemoveAsync(key);
|
await GetStorageService(options).RemoveAsync(key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log("SET", options, key, JsonConvert.SerializeObject(value));
|
|
||||||
await GetStorageService(options).SaveAsync(key, value);
|
await GetStorageService(options).SaveAsync(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetValueGloballyAsync<T>(Func<string, string> keyPrefix, T value, StorageOptions options)
|
private async Task SetValueGloballyAsync<T>(Func<string, string> keyPrefix, T value, StorageOptions options)
|
||||||
{
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
// don't remove values globally
|
||||||
|
return;
|
||||||
|
}
|
||||||
await CheckStateAsync();
|
await CheckStateAsync();
|
||||||
if (_state?.Accounts == null)
|
if (_state?.Accounts == null)
|
||||||
{
|
{
|
||||||
@@ -1237,14 +1243,11 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
_state = await GetStateFromStorageAsync();
|
var state = await GetStateFromStorageAsync();
|
||||||
if (_state?.Accounts?.ContainsKey(options.UserId) ?? false)
|
if (state?.Accounts?.ContainsKey(options.UserId) ?? false)
|
||||||
{
|
{
|
||||||
if (_state.Accounts[options.UserId].VolatileData == null)
|
state.Accounts[options.UserId].VolatileData = new Account.AccountVolatileData();
|
||||||
{
|
return state.Accounts[options.UserId];
|
||||||
_state.Accounts[options.UserId].VolatileData = new Account.AccountVolatileData();
|
|
||||||
}
|
|
||||||
return _state.Accounts[options.UserId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -1312,8 +1315,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
_state.Accounts[userId].Tokens.AccessToken = null;
|
_state.Accounts[userId].Tokens.AccessToken = null;
|
||||||
_state.Accounts[userId].Tokens.RefreshToken = null;
|
_state.Accounts[userId].Tokens.RefreshToken = null;
|
||||||
_state.Accounts[userId].VolatileData.Key = null;
|
_state.Accounts[userId].VolatileData = null;
|
||||||
_state.Accounts[userId].VolatileData.BiometricLocked = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (userInitiated && _state?.ActiveUserId == userId)
|
if (userInitiated && _state?.ActiveUserId == userId)
|
||||||
@@ -1357,7 +1359,6 @@ namespace Bit.Core.Services
|
|||||||
await SetOrgKeysEncryptedAsync(null, userId);
|
await SetOrgKeysEncryptedAsync(null, userId);
|
||||||
await SetPrivateKeyEncryptedAsync(null, userId);
|
await SetPrivateKeyEncryptedAsync(null, userId);
|
||||||
await SetLastActiveTimeAsync(null, userId);
|
await SetLastActiveTimeAsync(null, userId);
|
||||||
await SetLastFileCacheClearAsync(null, userId);
|
|
||||||
await SetPreviousPageInfoAsync(null, userId);
|
await SetPreviousPageInfoAsync(null, userId);
|
||||||
await SetInvalidUnlockAttemptsAsync(null, userId);
|
await SetInvalidUnlockAttemptsAsync(null, userId);
|
||||||
await SetLocalDataAsync(null, userId);
|
await SetLocalDataAsync(null, userId);
|
||||||
@@ -1505,19 +1506,12 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
private async Task<State> GetStateFromStorageAsync()
|
private async Task<State> GetStateFromStorageAsync()
|
||||||
{
|
{
|
||||||
var state = await _storageService.GetAsync<State>(Constants.StateKey);
|
return await _storageService.GetAsync<State>(Constants.StateKey);
|
||||||
// TODO Remove logging once all bugs are squished
|
|
||||||
Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented),
|
|
||||||
">>> GetStateFromStorageAsync()");
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveStateToStorageAsync(State state)
|
private async Task SaveStateToStorageAsync(State state)
|
||||||
{
|
{
|
||||||
await _storageService.SaveAsync(Constants.StateKey, state);
|
await _storageService.SaveAsync(Constants.StateKey, state);
|
||||||
// TODO Remove logging once all bugs are squished
|
|
||||||
Debug.WriteLine(JsonConvert.SerializeObject(state, Formatting.Indented),
|
|
||||||
">>> SaveStateToStorageAsync()");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckStateAsync()
|
private async Task CheckStateAsync()
|
||||||
@@ -1560,17 +1554,5 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
throw new Exception("User does not exist in account list");
|
throw new Exception("User does not exist in account list");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Log(string tag, StorageOptions options, string key, string value)
|
|
||||||
{
|
|
||||||
// TODO Remove this once all bugs are squished
|
|
||||||
var text = options?.UseSecureStorage ?? false ? "SECURE / " : "";
|
|
||||||
text += "Key: " + key + " / ";
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
text += "Value: " + value;
|
|
||||||
}
|
|
||||||
Debug.WriteLine(text, ">>> " + tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -327,6 +327,7 @@ namespace Bit.Core.Services
|
|||||||
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));
|
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));
|
||||||
await _organizationService.ReplaceAsync(organizations);
|
await _organizationService.ReplaceAsync(organizations);
|
||||||
await _stateService.SetEmailVerifiedAsync(response.EmailVerified);
|
await _stateService.SetEmailVerifiedAsync(response.EmailVerified);
|
||||||
|
await _stateService.SetNameAsync(response.Name);
|
||||||
await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector);
|
await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ namespace Bit.Core.Services
|
|||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly Func<(string userId, bool userInitiated), Task> _lockedCallback;
|
private readonly Func<Tuple<string, bool>, Task> _lockedCallback;
|
||||||
private readonly Func<(string userId, bool userInitiated, bool expired), Task> _loggedOutCallback;
|
private readonly Func<Tuple<string, bool, bool>, Task> _loggedOutCallback;
|
||||||
|
|
||||||
public VaultTimeoutService(
|
public VaultTimeoutService(
|
||||||
ICryptoService cryptoService,
|
ICryptoService cryptoService,
|
||||||
@@ -34,8 +34,8 @@ namespace Bit.Core.Services
|
|||||||
ITokenService tokenService,
|
ITokenService tokenService,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
IKeyConnectorService keyConnectorService,
|
IKeyConnectorService keyConnectorService,
|
||||||
Func<(string userId, bool userInitiated), Task> lockedCallback,
|
Func<Tuple<string, bool>, Task> lockedCallback,
|
||||||
Func<(string userId, bool userInitiated, bool expired), Task> loggedOutCallback)
|
Func<Tuple<string, bool, bool>, Task> loggedOutCallback)
|
||||||
{
|
{
|
||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
@@ -183,7 +183,7 @@ namespace Bit.Core.Services
|
|||||||
await _stateService.SetBiometricLockedAsync(isBiometricLockSet, userId);
|
await _stateService.SetBiometricLockedAsync(isBiometricLockSet, userId);
|
||||||
if (isBiometricLockSet)
|
if (isBiometricLockSet)
|
||||||
{
|
{
|
||||||
_lockedCallback?.Invoke((userId, userInitiated));
|
_lockedCallback?.Invoke(new Tuple<string, bool>(userId, userInitiated));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,14 +200,14 @@ namespace Bit.Core.Services
|
|||||||
_collectionService.ClearCache();
|
_collectionService.ClearCache();
|
||||||
_searchService.ClearIndex();
|
_searchService.ClearIndex();
|
||||||
}
|
}
|
||||||
_lockedCallback?.Invoke((userId, userInitiated));
|
_lockedCallback?.Invoke(new Tuple<string, bool>(userId, userInitiated));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogOutAsync(bool userInitiated = true, string userId = null)
|
public async Task LogOutAsync(bool userInitiated = true, string userId = null)
|
||||||
{
|
{
|
||||||
if(_loggedOutCallback != null)
|
if(_loggedOutCallback != null)
|
||||||
{
|
{
|
||||||
await _loggedOutCallback.Invoke((userId, userInitiated, false));
|
await _loggedOutCallback.Invoke(new Tuple<string, bool, bool>(userId, userInitiated, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.3</string>
|
<string>2.17.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace Bit.iOS.Core.Renderers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
uiBarButtonItem.Image = uiBarButtonItem.Image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
|
uiBarButtonItem.Image = uiBarButtonItem.Image?.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -594,6 +594,11 @@ namespace Bit.iOS.Core.Services
|
|||||||
return scaledHeight / tempHeight;
|
return scaledHeight / tempHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task OnAccountSwitchCompleteAsync()
|
||||||
|
{
|
||||||
|
await ASHelpers.ReplaceAllIdentities();
|
||||||
|
}
|
||||||
|
|
||||||
public class PickerDelegate : UIDocumentPickerDelegate
|
public class PickerDelegate : UIDocumentPickerDelegate
|
||||||
{
|
{
|
||||||
private readonly DeviceActionService _deviceActionService;
|
private readonly DeviceActionService _deviceActionService;
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
var timeoutAction = await stateService.GetVaultTimeoutActionAsync();
|
var timeoutAction = await stateService.GetVaultTimeoutActionAsync();
|
||||||
if (timeoutAction == VaultTimeoutAction.Logout)
|
if (timeoutAction == VaultTimeoutAction.Logout)
|
||||||
{
|
{
|
||||||
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
if (await vaultTimeoutService.IsLockedAsync())
|
if (await vaultTimeoutService.IsLockedAsync())
|
||||||
{
|
{
|
||||||
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
await storageService.SaveAsync(Constants.AutofillNeedsIdentityReplacementKey, true);
|
await storageService.SaveAsync(Constants.AutofillNeedsIdentityReplacementKey, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -43,7 +45,9 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
{
|
{
|
||||||
await ASCredentialIdentityStore.SharedStore?.ReplaceCredentialIdentitiesAsync(identities.ToArray());
|
await ASCredentialIdentityStore.SharedStore?.ReplaceCredentialIdentitiesAsync(identities.ToArray());
|
||||||
await storageService.SaveAsync(Constants.AutofillNeedsIdentityReplacementKey, false);
|
await storageService.SaveAsync(Constants.AutofillNeedsIdentityReplacementKey, false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.3</string>
|
<string>2.17.0</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.3</string>
|
<string>2.17.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
private NFCNdefReaderSession _nfcSession = null;
|
private NFCNdefReaderSession _nfcSession = null;
|
||||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||||
|
|
||||||
readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateervice");
|
readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
|
||||||
readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>("vaultTimeoutService");
|
readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
readonly LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>("deviceActionService");
|
readonly LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>("deviceActionService");
|
||||||
readonly LazyResolve<IEventService> _eventService = new LazyResolve<IEventService>("eventService");
|
readonly LazyResolve<IEventService> _eventService = new LazyResolve<IEventService>("eventService");
|
||||||
@@ -215,7 +215,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
iOSCoreHelpers.RegisterLocalServices();
|
iOSCoreHelpers.RegisterLocalServices();
|
||||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
ServiceContainer.Init(_deviceActionService.Value.DeviceUserAgent,
|
ServiceContainer.Init(_deviceActionService.Value.DeviceUserAgent,
|
||||||
Bit.Core.Constants.iOSExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
Bit.Core.Constants.iOSShareExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
||||||
if (!_initedAppCenter)
|
if (!_initedAppCenter)
|
||||||
{
|
{
|
||||||
iOSCoreHelpers.RegisterAppCenter();
|
iOSCoreHelpers.RegisterAppCenter();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
public LockPasswordViewController(IntPtr handle)
|
public LockPasswordViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{
|
{
|
||||||
BiometricIntegrityKey = Bit.Core.Constants.iOSExtensionBiometricIntegrityKey;
|
BiometricIntegrityKey = Bit.Core.Constants.iOSShareExtensionBiometricIntegrityKey;
|
||||||
DismissModalAction = Cancel;
|
DismissModalAction = Cancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace Bit.iOS
|
|||||||
[Register("AppDelegate")]
|
[Register("AppDelegate")]
|
||||||
public partial class AppDelegate : FormsApplicationDelegate
|
public partial class AppDelegate : FormsApplicationDelegate
|
||||||
{
|
{
|
||||||
|
const int SPLASH_VIEW_TAG = 4321;
|
||||||
|
|
||||||
private NFCNdefReaderSession _nfcSession = null;
|
private NFCNdefReaderSession _nfcSession = null;
|
||||||
private iOSPushNotificationHandler _pushHandler = null;
|
private iOSPushNotificationHandler _pushHandler = null;
|
||||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||||
@@ -146,12 +148,7 @@ namespace Bit.iOS
|
|||||||
{
|
{
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||||
{
|
{
|
||||||
var extras = message.Data as Tuple<string, bool, bool>;
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
var userId = extras?.Item1;
|
|
||||||
var userInitiated = extras?.Item2;
|
|
||||||
var expired = extras?.Item3;
|
|
||||||
// TODO make specific to userId
|
|
||||||
// await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
||||||
@@ -164,9 +161,7 @@ namespace Bit.iOS
|
|||||||
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
||||||
if (timeoutAction == VaultTimeoutAction.Logout)
|
if (timeoutAction == VaultTimeoutAction.Logout)
|
||||||
{
|
{
|
||||||
var userId = await _stateService.GetActiveUserIdAsync();
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
// TODO make specific to userId
|
|
||||||
// await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -182,7 +177,7 @@ namespace Bit.iOS
|
|||||||
{
|
{
|
||||||
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||||
{
|
{
|
||||||
Tag = 4321
|
Tag = SPLASH_VIEW_TAG
|
||||||
};
|
};
|
||||||
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||||
{
|
{
|
||||||
@@ -212,11 +207,9 @@ namespace Bit.iOS
|
|||||||
{
|
{
|
||||||
base.OnActivated(uiApplication);
|
base.OnActivated(uiApplication);
|
||||||
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
|
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
|
||||||
var view = UIApplication.SharedApplication.KeyWindow.ViewWithTag(4321);
|
UIApplication.SharedApplication.KeyWindow?
|
||||||
if (view != null)
|
.ViewWithTag(SPLASH_VIEW_TAG)?
|
||||||
{
|
.RemoveFromSuperview();
|
||||||
view.RemoveFromSuperview();
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeManager.UpdateThemeOnPagesAsync();
|
ThemeManager.UpdateThemeOnPagesAsync();
|
||||||
}
|
}
|
||||||
@@ -235,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,
|
||||||
|
|||||||
@@ -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.3</string>
|
<string>2.17.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user