1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

...

16 Commits

Author SHA1 Message Date
github-actions[bot]
ececcb7bd7 Bump version to 2.17.0 (#1858)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit a3a508eb83)
2022-03-22 10:50:41 -06:00
Matt Portune
81cccf22db Remove verbose state & value storage debug logging (#1857) 2022-03-21 16:46:18 -04:00
Federico Maccaroni
4e36bc83ae Added null checks for iOS crash OnActivated on KeyWindow 2022-03-21 12:36:21 -03:00
github-actions[bot]
9db70119b9 Bumped version to 2.16.5 (#1854)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit fdcb2d76c9)
2022-03-18 13:22:15 -07:00
Federico Maccaroni
3d485f6fb5 Fixed flickering on iOS while loading collections for the collection crash hotfix 2022-03-18 15:49:48 -03:00
Matt Portune
372c875cb2 Misc fixes for account switching (#1849)
* Misc fixes for account switching

* use unique bio integrity key in ShareExtension
2022-03-17 17:57:31 -04:00
Federico Maccaroni
2e60787128 Fix iOS 15.4 crash from empty list to adding an item by awaiting after every header add; also added that on Settings just in case there is another crash scenario 2022-03-17 17:35:42 -03:00
github-actions[bot]
d283586d96 Bumped version to 2.16.4 (#1846)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit c47aad0412)
2022-03-15 09:29:41 -07:00
Federico Maccaroni
3a2a1b527f Removed grouping from Settings to fix a crash on iOS 15.4 2022-03-15 11:31:33 -03:00
Federico Maccaroni
d908d5e64d Fix #1745 crash on scroll of grouped collection view on iOS 15.4 beta 2022-03-14 13:54:37 -03:00
Matt Portune
8c59552d41 fixes for font cutoff on samsung devices (#1838) 2022-03-10 13:56:33 -05:00
Matt Portune
77c4c423e0 fixed issues with logging out inactive accounts (#1836) 2022-03-10 09:03:06 -05:00
Joseph Flinn
47241fd5a9 Update hotfix release branch name to hotfix-rc (#1834)
(cherry picked from commit bdd0ea007b)
2022-03-09 12:54:20 -08:00
Matt Portune
51cfd70398 Fix for short profile Name value crashing app (#1833) 2022-03-09 09:01:16 -05:00
Matt Portune
de566be994 fix for lock & logout message parsing issue (#1832) 2022-03-08 14:32:13 -05:00
Federico Maccaroni
819d1b616a Remove Microsoft.AppCenter.Crashes from Core.csproj on FDroid on the build.yml (#1831) 2022-03-08 14:31:57 -05:00
48 changed files with 542 additions and 272 deletions

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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"/>

View File

@@ -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
}
} }
} }

View File

@@ -46,5 +46,6 @@ namespace Bit.App.Abstractions
void CloseMainApp(); void CloseMainApp();
bool SupportsFido2(); bool SupportsFido2();
float GetSystemFontSizeScale(); float GetSystemFontSizeScale();
Task OnAccountSwitchCompleteAsync();
} }
} }

View File

@@ -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();

View File

@@ -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" />

View File

@@ -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)

View File

@@ -0,0 +1,6 @@
namespace Bit.App.Pages
{
public interface ISendGroupingsPageListItem
{
}
}

View File

@@ -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>

View File

@@ -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; }
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
{ {

View File

@@ -0,0 +1,6 @@
namespace Bit.App.Pages
{
public interface ISettingsPageListItem
{
}
}

View File

@@ -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>

View File

@@ -0,0 +1,12 @@
namespace Bit.App.Pages
{
public class SettingsPageHeaderListItem : ISettingsPageListItem
{
public SettingsPageHeaderListItem(string title)
{
Title = title;
}
public string Title { get; }
}
}

View File

@@ -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; }

View File

@@ -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;
} }

View File

@@ -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()

View File

@@ -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>

View File

@@ -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();
} }

View File

@@ -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>

View File

@@ -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; }
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
{ {

View File

@@ -0,0 +1,6 @@
namespace Bit.App.Pages
{
public interface IGroupingsPageListItem
{
}
}

View File

@@ -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,
}; };

View File

@@ -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 -->

View File

@@ -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">

View File

@@ -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 -->

View File

@@ -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());

View File

@@ -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);

View File

@@ -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}";

View File

@@ -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);
}
} }
} }

View File

@@ -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);
} }

View File

@@ -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));
} }
} }

View File

@@ -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>

View File

@@ -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)
{ {

View File

@@ -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;

View File

@@ -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();
} }
} }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();

View File

@@ -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;
} }

View File

@@ -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,

View File

@@ -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>