mirror of
https://github.com/bitwarden/mobile
synced 2026-01-24 21:33:23 +00:00
Compare commits
29 Commits
uitests
...
beeep-totp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a484ca633c | ||
|
|
655b51b6a5 | ||
|
|
8168089591 | ||
|
|
6b55fc3032 | ||
|
|
87ab42b155 | ||
|
|
98130e89de | ||
|
|
121f0e3628 | ||
|
|
8a3d88b3ce | ||
|
|
b8b41fe847 | ||
|
|
5bbef3ee16 | ||
|
|
9a2b6c8ec9 | ||
|
|
5272c99643 | ||
|
|
43e9515a03 | ||
|
|
7e9b7398c8 | ||
|
|
58d7b001a5 | ||
|
|
a259560d29 | ||
|
|
22c746543a | ||
|
|
bcbc2738ca | ||
|
|
604e3b6892 | ||
|
|
b081a8c634 | ||
|
|
c251b950d1 | ||
|
|
0b626cedc7 | ||
|
|
3ac2580742 | ||
|
|
008ed8eb56 | ||
|
|
26e0e43bb4 | ||
|
|
0ad992faec | ||
|
|
98dd8298ea | ||
|
|
bb37bac620 | ||
|
|
b02c58e362 |
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -21,11 +21,6 @@
|
||||
|
||||
|
||||
|
||||
## Testing requirements
|
||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
||||
|
||||
|
||||
|
||||
## Before you submit
|
||||
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
||||
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||
|
||||
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -304,18 +304,6 @@ jobs:
|
||||
|
||||
$xml.Save($androidPath);
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Uninstall from App.csproj"
|
||||
Write-Output "########################################"
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($appPath);
|
||||
|
||||
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
||||
|
||||
$xml.Save($appPath);
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Uninstall from Core.csproj"
|
||||
Write-Output "########################################"
|
||||
|
||||
@@ -46,8 +46,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.ShareExtension", "src\i
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UiTests", "src\UiTests\UiTests.csproj", "{23FB637B-1705-485F-9464-078FCAF361A8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||
@@ -448,36 +446,6 @@ Global
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhone.Build.0 = Release|iPhone
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|Any CPU.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|iPhone.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|iPhone.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.FDroid|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -496,7 +464,6 @@ Global
|
||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0} = {8904C536-C67D-420F-9971-51B26574C3AA}
|
||||
{F8C3F648-EA5A-4719-8005-85D1690B1655} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||
{23FB637B-1705-485F-9464-078FCAF361A8} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7D436EA3-8B7E-45D2-8D14-0730BD2E0410}
|
||||
|
||||
@@ -145,12 +145,12 @@
|
||||
<Compile Include="Tiles\GeneratorTileService.cs" />
|
||||
<Compile Include="Tiles\MyVaultTileService.cs" />
|
||||
<Compile Include="Utilities\AndroidHelpers.cs" />
|
||||
<Compile Include="Utilities\AppCenterHelper.cs" />
|
||||
<Compile Include="Utilities\ThemeHelpers.cs" />
|
||||
<Compile Include="WebAuthCallbackActivity.cs" />
|
||||
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
|
||||
<Compile Include="Services\ClipboardService.cs" />
|
||||
<Compile Include="Utilities\IntentExtensions.cs" />
|
||||
<Compile Include="Renderers\CustomPageRenderer.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
||||
|
||||
@@ -64,15 +64,12 @@ namespace Bit.Droid
|
||||
Intent?.Validate();
|
||||
|
||||
base.OnCreate(savedInstanceState);
|
||||
//if (!CoreHelpers.InDebugMode())
|
||||
//{
|
||||
// Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||
//}
|
||||
if (!CoreHelpers.InDebugMode())
|
||||
{
|
||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||
}
|
||||
|
||||
#if !DEBUG && !FDROID
|
||||
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
|
||||
var appCenterTask = appCenterHelper.InitAsync();
|
||||
#endif
|
||||
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||
|
||||
var toplayout = Window?.DecorView?.RootView;
|
||||
if (toplayout != null)
|
||||
@@ -85,6 +82,7 @@ namespace Bit.Droid
|
||||
_appOptions = GetOptions();
|
||||
LoadApplication(new App.App(_appOptions));
|
||||
|
||||
|
||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
||||
{
|
||||
if (message.Command == "startEventTimer")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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.18.1" 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="2022.05.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||
|
||||
|
||||
31
src/Android/Renderers/CustomPageRenderer.cs
Normal file
31
src/Android/Renderers/CustomPageRenderer.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using AndroidX.AppCompat.Widget;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CustomPageRenderer : PageRenderer
|
||||
{
|
||||
public CustomPageRenderer(Context context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
Activity context = (Activity)this.Context;
|
||||
var toolbar = context.FindViewById<Toolbar>(Resource.Id.toolbar);
|
||||
if(toolbar != null)
|
||||
{
|
||||
toolbar.NavigationContentDescription = AppResources.TapToGoBack;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,10 @@ using System.Threading.Tasks;
|
||||
using Android.OS;
|
||||
using Android.Security.Keystore;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Java.Security;
|
||||
using Javax.Crypto;
|
||||
#if !FDROID
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
#endif
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
@@ -74,9 +73,7 @@ namespace Bit.Droid.Services
|
||||
catch (InvalidKeyException e)
|
||||
{
|
||||
// Fallback for old bitwarden users without a key
|
||||
#if !FDROID
|
||||
Crashes.TrackError(e);
|
||||
#endif
|
||||
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||
CreateKey();
|
||||
}
|
||||
|
||||
@@ -101,9 +98,7 @@ namespace Bit.Droid.Services
|
||||
{
|
||||
// Catch silently to allow biometrics to function on devices that are in a state where key generation
|
||||
// is not functioning
|
||||
#if !FDROID
|
||||
Crashes.TrackError(e);
|
||||
#endif
|
||||
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
#if !FDROID
|
||||
using Bit.Core.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AppCenter;
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Droid.Utilities
|
||||
{
|
||||
public class AppCenterHelper
|
||||
{
|
||||
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
|
||||
|
||||
private readonly IAppIdService _appIdService;
|
||||
private readonly IStateService _stateService;
|
||||
|
||||
private string _userId;
|
||||
private string _appId;
|
||||
|
||||
public AppCenterHelper(
|
||||
IAppIdService appIdService,
|
||||
IStateService stateService)
|
||||
{
|
||||
_appIdService = appIdService;
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
_userId = await _stateService.GetActiveUserIdAsync();
|
||||
_appId = await _appIdService.GetAppIdAsync();
|
||||
|
||||
AppCenter.Start(AppSecret, typeof(Crashes));
|
||||
AppCenter.SetUserId(_userId);
|
||||
|
||||
Crashes.GetErrorAttachments = (ErrorReport report) =>
|
||||
{
|
||||
return new ErrorAttachmentLog[]
|
||||
{
|
||||
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return JsonConvert.SerializeObject(new
|
||||
{
|
||||
AppId = _appId,
|
||||
UserId = _userId
|
||||
}, Formatting.Indented);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -13,7 +13,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
|
||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
|
||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.1" />
|
||||
@@ -128,6 +127,7 @@
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Behaviors\" />
|
||||
<Folder Include="Pages\Authenticator\" />
|
||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Controls.AuthenticatorViewCell"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
StyleClass="list-row, list-row-platform"
|
||||
RowSpacing="0"
|
||||
ColumnSpacing="0"
|
||||
x:DataType="pages:AuthenticatorPageListItem">
|
||||
|
||||
<Grid.Resources>
|
||||
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
||||
<u:IconImageConverter x:Key="iconImageConverter"/>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="60" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:IconLabel
|
||||
Grid.Column="0"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-icon, list-icon-platform"
|
||||
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<ff:CachedImage
|
||||
Grid.Column="0"
|
||||
BitmapOptimizations="True"
|
||||
ErrorPlaceholder="login.png"
|
||||
LoadingPlaceholder="login.png"
|
||||
HorizontalOptions="Center"
|
||||
VerticalOptions="Center"
|
||||
WidthRequest="22"
|
||||
HeightRequest="22"
|
||||
IsVisible="{Binding ShowIconImage}"
|
||||
Source="{Binding IconImageSource, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="False" />
|
||||
|
||||
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:MonoLabel
|
||||
LineBreakMode="TailTruncation"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="3"
|
||||
StyleClass="list-title, list-title-platform"
|
||||
Text="{Binding TotpCodeFormatted, Mode=OneWay}" />
|
||||
<Label
|
||||
LineBreakMode="TailTruncation"
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
StyleClass="list-subtitle, list-subtitle-platform"
|
||||
Text="{Binding Cipher.Name}" />
|
||||
<controls:IconLabel
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
HorizontalOptions="Start"
|
||||
VerticalOptions="Center"
|
||||
StyleClass="list-title-icon"
|
||||
Margin="5, 0, 0, 0"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
|
||||
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Shared}" />
|
||||
</Grid>
|
||||
|
||||
<Label
|
||||
Text="{Binding TotpSec, Mode=OneWay}"
|
||||
Style="{DynamicResource textTotp}"
|
||||
Margin="0, 0, 10, 0"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
HorizontalOptions="End"
|
||||
HorizontalTextAlignment="End"
|
||||
VerticalOptions="CenterAndExpand" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
Command="{Binding CopyCommand}"
|
||||
CommandParameter="LoginTotp"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Grid.RowSpan="2"
|
||||
Padding="0,0,1,0"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n CopyTotp}" />
|
||||
</controls:ExtendedGrid>
|
||||
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public partial class AuthenticatorViewCell : ExtendedGrid
|
||||
{
|
||||
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
|
||||
nameof(Cipher), typeof(CipherView), typeof(AuthenticatorViewCell), default(CipherView), BindingMode.TwoWay);
|
||||
|
||||
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
|
||||
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(AuthenticatorViewCell));
|
||||
|
||||
public static readonly BindableProperty TotpSecProperty = BindableProperty.Create(
|
||||
nameof(TotpSec), typeof(long), typeof(AuthenticatorViewCell));
|
||||
|
||||
//public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
|
||||
// nameof(ButtonCommand), typeof(Command<CipherView>), typeof(AuthenticatorViewCell));
|
||||
|
||||
public AuthenticatorViewCell()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public Command CopyCommand { get; set; }
|
||||
|
||||
public CipherView Cipher
|
||||
{
|
||||
get => GetValue(CipherProperty) as CipherView;
|
||||
set => SetValue(CipherProperty, value);
|
||||
}
|
||||
|
||||
public bool? WebsiteIconsEnabled
|
||||
{
|
||||
get => (bool)GetValue(WebsiteIconsEnabledProperty);
|
||||
set => SetValue(WebsiteIconsEnabledProperty, value);
|
||||
}
|
||||
|
||||
public long TotpSec
|
||||
{
|
||||
get => (long)GetValue(TotpSecProperty);
|
||||
set => SetValue(TotpSecProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowIconImage
|
||||
{
|
||||
get => WebsiteIconsEnabled ?? false
|
||||
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
|
||||
&& IconImageSource != null;
|
||||
}
|
||||
|
||||
private string _iconImageSource = string.Empty;
|
||||
public string IconImageSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||
{
|
||||
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
|
||||
}
|
||||
return _iconImageSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private string _totpCodeFormatted = "938 928";
|
||||
public string TotpCodeFormatted
|
||||
{
|
||||
get => _totpCodeFormatted;
|
||||
set => _totpCodeFormatted = value;
|
||||
}
|
||||
|
||||
|
||||
//public Command<CipherView> ButtonCommand
|
||||
//{
|
||||
// get => GetValue(ButtonCommandProperty) as Command<CipherView>;
|
||||
// set => SetValue(ButtonCommandProperty, value);
|
||||
//}
|
||||
|
||||
//protected override void OnPropertyChanged(string propertyName = null)
|
||||
//{
|
||||
// base.OnPropertyChanged(propertyName);
|
||||
// if (propertyName == CipherProperty.PropertyName)
|
||||
// {
|
||||
// if (Cipher == null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// _cipherLabel.Text = Cipher.Name;
|
||||
// }
|
||||
// else if (propertyName == WebsiteIconsEnabledProperty.PropertyName)
|
||||
// {
|
||||
// if (Cipher == null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// ((AuthenticatorViewCellViewModel)BindingContext).WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
|
||||
// }
|
||||
// else if (propertyName == TotpSecProperty.PropertyName)
|
||||
// {
|
||||
// if (Cipher == null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// ((AuthenticatorViewCellViewModel)BindingContext).UpdateTotpSec(TotpSec);
|
||||
// }
|
||||
//}
|
||||
|
||||
private void MoreButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var cipher = ((sender as MiButton)?.BindingContext as AuthenticatorViewCellViewModel)?.Cipher;
|
||||
if (cipher != null)
|
||||
{
|
||||
//ButtonCommand?.Execute(cipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
public class AuthenticatorViewCellViewModel : ExtendedViewModel
|
||||
{
|
||||
private CipherView _cipher;
|
||||
private string _totpCodeFormatted = "938 928";
|
||||
private string _totpSec;
|
||||
private bool _websiteIconsEnabled;
|
||||
private string _iconImageSource = string.Empty;
|
||||
|
||||
public AuthenticatorViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
|
||||
{
|
||||
Cipher = cipherView;
|
||||
WebsiteIconsEnabled = websiteIconsEnabled;
|
||||
}
|
||||
|
||||
public Command CopyCommand { get; set; }
|
||||
|
||||
public CipherView Cipher
|
||||
{
|
||||
get => _cipher;
|
||||
set => SetProperty(ref _cipher, value);
|
||||
}
|
||||
|
||||
public string TotpCodeFormatted
|
||||
{
|
||||
get => _totpCodeFormatted;
|
||||
set => SetProperty(ref _totpCodeFormatted, value);
|
||||
}
|
||||
|
||||
public string TotpSec
|
||||
{
|
||||
get => _totpSec;
|
||||
set => SetProperty(ref _totpSec, value);
|
||||
}
|
||||
|
||||
public bool WebsiteIconsEnabled
|
||||
{
|
||||
get => _websiteIconsEnabled;
|
||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||
}
|
||||
|
||||
public bool ShowIconImage
|
||||
{
|
||||
get => WebsiteIconsEnabled
|
||||
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
|
||||
&& IconImageSource != null;
|
||||
}
|
||||
|
||||
public string IconImageSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||
{
|
||||
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
|
||||
}
|
||||
return _iconImageSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void UpdateTotpSec(long totpSec)
|
||||
{
|
||||
_totpSec = totpSec.ToString();
|
||||
}
|
||||
|
||||
//private async Task TotpUpdateCodeAsync()
|
||||
//{
|
||||
// if (Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null)
|
||||
// {
|
||||
// _totpInterval = null;
|
||||
// return;
|
||||
// }
|
||||
// _totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
|
||||
// if (_totpCode != null)
|
||||
// {
|
||||
// if (_totpCode.Length > 4)
|
||||
// {
|
||||
// var half = (int)Math.Floor(_totpCode.Length / 2M);
|
||||
// TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
|
||||
// _totpCode.Substring(half));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// TotpCodeFormatted = _totpCode;
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// TotpCodeFormatted = null;
|
||||
// _totpInterval = null;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<controls:MiButton
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Text=""
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||
Clicked="MoreButton_Clicked"
|
||||
VerticalOptions="CenterAndExpand"
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
<controls:MiButton
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Text=""
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||
IsVisible="{Binding ShowOptions, Mode=OneWay}"
|
||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||
Clicked="MoreButton_Clicked"
|
||||
|
||||
@@ -46,7 +46,11 @@ namespace Bit.App.Pages
|
||||
{
|
||||
get => _showPassword;
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
|
||||
additionalPropertyNames: new[]
|
||||
{
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText)
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsPolicyInEffect
|
||||
@@ -68,6 +72,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string MasterPassword { get; set; }
|
||||
public string ConfirmMasterPassword { get; set; }
|
||||
public string Hint { get; set; }
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Submit_Clicked" AutomationId="save_button"/>
|
||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Submit_Clicked" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ScrollView>
|
||||
@@ -34,8 +34,7 @@
|
||||
Placeholder="ex. https://bitwarden.company.com"
|
||||
StyleClass="box-value"
|
||||
ReturnType="Go"
|
||||
ReturnCommand="{Binding SubmitCommand}"
|
||||
AutomationId="server_input"/>
|
||||
ReturnCommand="{Binding SubmitCommand}" />
|
||||
</StackLayout>
|
||||
<Label
|
||||
Text="{u:I18n SelfHostedEnvironmentFooter}"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Priority="-1"
|
||||
UseOriginalImage="True"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Account}"/>
|
||||
AutomationProperties.Name="{u:I18n Account}" />
|
||||
<ToolbarItem
|
||||
Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
@@ -37,18 +37,15 @@
|
||||
<Image
|
||||
x:Name="_logo"
|
||||
Source="logo.png"
|
||||
VerticalOptions="Center"
|
||||
AutomationId="logo_image"
|
||||
/>
|
||||
VerticalOptions="Center" />
|
||||
<Label Text="{u:I18n LoginOrCreateNewAccount}"
|
||||
StyleClass="text-lg"
|
||||
HorizontalTextAlignment="Center"/>
|
||||
|
||||
HorizontalTextAlignment="Center">
|
||||
</Label>
|
||||
<StackLayout Spacing="5">
|
||||
<Button Text="{u:I18n LogIn}"
|
||||
StyleClass="btn-primary"
|
||||
Clicked="LogIn_Clicked"
|
||||
AutomationId="homepage_login_button"/>
|
||||
Clicked="LogIn_Clicked" />
|
||||
<Button Text="{u:I18n CreateAccount}"
|
||||
Clicked="Register_Clicked" />
|
||||
<Button Text="{u:I18n LogInSso}"
|
||||
|
||||
@@ -80,7 +80,8 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="_passwordGrid"
|
||||
@@ -119,7 +120,8 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||
</Grid>
|
||||
<StackLayout
|
||||
StyleClass="box-row"
|
||||
|
||||
@@ -72,7 +72,8 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowPasswordIcon)
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -128,6 +129,7 @@ namespace Bit.App.Pages
|
||||
public Command SubmitCommand { get; }
|
||||
public Command TogglePasswordCommand { get; }
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string MasterPassword { get; set; }
|
||||
public string Pin { get; set; }
|
||||
public Action UnlockedAction { get; set; }
|
||||
|
||||
@@ -56,8 +56,7 @@
|
||||
x:Name="_email"
|
||||
Text="{Binding Email}"
|
||||
Keyboard="Email"
|
||||
StyleClass="box-value"
|
||||
AutomationId="email_input">
|
||||
StyleClass="box-value">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Disabled">
|
||||
@@ -93,8 +92,7 @@
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
ReturnType="Go"
|
||||
ReturnCommand="{Binding LogInCommand}"
|
||||
AutomationId="password_input"/>
|
||||
ReturnCommand="{Binding LogInCommand}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowPasswordIcon}"
|
||||
@@ -103,18 +101,17 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
<StackLayout Padding="10, 0">
|
||||
<Button Text="{u:I18n LogIn}"
|
||||
StyleClass="btn-primary"
|
||||
Clicked="LogIn_Clicked"
|
||||
AutomationId="loginpage_login_button"/>
|
||||
Clicked="LogIn_Clicked" />
|
||||
<Button Text="{u:I18n Cancel}"
|
||||
IsVisible="{Binding ShowCancelButton}"
|
||||
Clicked="Cancel_Clicked"
|
||||
AutomationId="cancel_button"/>
|
||||
Clicked="Cancel_Clicked" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
||||
@@ -58,7 +58,8 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowPasswordIcon)
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,6 +86,7 @@ namespace Bit.App.Pages
|
||||
public Command LogInCommand { get; }
|
||||
public Command TogglePasswordCommand { get; }
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public Action StartTwoFactorAction { get; set; }
|
||||
public Action LogInSuccessAction { get; set; }
|
||||
public Action UpdateTempPasswordAction { get; set; }
|
||||
|
||||
@@ -68,7 +68,8 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||
</Grid>
|
||||
<Label
|
||||
Text="{u:I18n MasterPasswordDescription}"
|
||||
@@ -106,7 +107,8 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||
</Grid>
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
|
||||
@@ -51,7 +51,8 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowPasswordIcon)
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,6 +74,7 @@ namespace Bit.App.Pages
|
||||
public Command TogglePasswordCommand { get; }
|
||||
public Command ToggleConfirmPasswordCommand { get; }
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string MasterPassword { get; set; }
|
||||
|
||||
@@ -107,7 +107,8 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||
</Grid>
|
||||
<Label
|
||||
Text="{u:I18n MasterPasswordDescription}"
|
||||
@@ -145,7 +146,8 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||
</Grid>
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
|
||||
@@ -55,7 +55,11 @@ namespace Bit.App.Pages
|
||||
{
|
||||
get => _showPassword;
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
|
||||
additionalPropertyNames: new[]
|
||||
{
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText)
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsPolicyInEffect
|
||||
@@ -86,6 +90,7 @@ namespace Bit.App.Pages
|
||||
public Command TogglePasswordCommand { get; }
|
||||
public Command ToggleConfirmPasswordCommand { get; }
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string MasterPassword { get; set; }
|
||||
public string ConfirmMasterPassword { get; set; }
|
||||
public string Hint { get; set; }
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Bit.App.Pages
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly IAppIdService _appIdService;
|
||||
|
||||
private TwoFactorProviderType? _selectedProviderType;
|
||||
private string _totpInstruction;
|
||||
@@ -49,6 +50,7 @@ namespace Bit.App.Pages
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||
|
||||
PageTitle = AppResources.TwoStepLogin;
|
||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||
@@ -380,7 +382,8 @@ namespace Bit.App.Pages
|
||||
var request = new TwoFactorEmailRequest
|
||||
{
|
||||
Email = _authService.Email,
|
||||
MasterPasswordHash = _authService.MasterPasswordHash
|
||||
MasterPasswordHash = _authService.MasterPasswordHash,
|
||||
DeviceIdentifier = await _appIdService.GetAppIdAsync()
|
||||
};
|
||||
await _apiService.PostTwoFactorEmailAsync(request);
|
||||
if (showLoading)
|
||||
|
||||
@@ -105,7 +105,8 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box">
|
||||
@@ -140,7 +141,8 @@
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||
</Grid>
|
||||
<StackLayout StyleClass="box-row">
|
||||
<Label
|
||||
|
||||
97
src/App/Pages/Authenticator/AuthenticatorPage.xaml
Normal file
97
src/App/Pages/Authenticator/AuthenticatorPage.xaml
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Pages.AuthenticatorPage"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
x:DataType="pages:AuthenticatorPageViewModel"
|
||||
Title="{Binding PageTitle}"
|
||||
x:Name="_page">
|
||||
|
||||
<ContentPage.BindingContext>
|
||||
<pages:AuthenticatorPageViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Search}" />
|
||||
</ContentPage.ToolbarItems>
|
||||
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
|
||||
<ToolbarItem x:Name="_aboutIconItem" x:Key="aboutIconItem" Icon="info.png"
|
||||
Clicked="About_Clicked" Order="Primary" Priority="-1"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n AboutSend}" />
|
||||
<ToolbarItem x:Name="_syncItem" x:Key="syncItem" Text="{u:I18n Sync}"
|
||||
Clicked="Sync_Clicked" Order="Secondary" />
|
||||
<ToolbarItem x:Name="_lockItem" x:Key="lockItem" Text="{u:I18n Lock}"
|
||||
Clicked="Lock_Clicked" Order="Secondary" />
|
||||
<ToolbarItem x:Name="_aboutTextItem" x:Key="aboutTextItem" Text="{u:I18n AboutSend}"
|
||||
Clicked="About_Clicked" Order="Secondary" />
|
||||
<ToolbarItem x:Name="_addItem" x:Key="addItem" Icon="plus.png"
|
||||
Clicked="AddButton_Clicked" Order="Primary"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n AddItem}" />
|
||||
|
||||
<DataTemplate x:Key="authenticatorTemplate"
|
||||
x:DataType="pages:AuthenticatorPageListItem">
|
||||
<controls:AuthenticatorViewCell
|
||||
Cipher="{Binding Cipher}"
|
||||
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
|
||||
TotpSec="{Binding TotpSec}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||
<RefreshView>
|
||||
<controls:ExtendedCollectionView
|
||||
ItemsSource="{Binding Items}"
|
||||
VerticalOptions="FillAndExpand"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="RowSelected"
|
||||
StyleClass="list, list-platform">
|
||||
|
||||
<controls:ExtendedCollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="pages:AuthenticatorPageListItem">
|
||||
<controls:AuthenticatorViewCell />
|
||||
</DataTemplate>
|
||||
</controls:ExtendedCollectionView.ItemTemplate>
|
||||
|
||||
</controls:ExtendedCollectionView>
|
||||
</RefreshView>
|
||||
|
||||
</StackLayout>
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
|
||||
|
||||
<AbsoluteLayout
|
||||
x:Name="_absLayout"
|
||||
VerticalOptions="FillAndExpand"
|
||||
HorizontalOptions="FillAndExpand">
|
||||
<ContentView
|
||||
x:Name="_mainContent"
|
||||
AbsoluteLayout.LayoutFlags="All"
|
||||
AbsoluteLayout.LayoutBounds="0, 0, 1, 1" />
|
||||
<Button
|
||||
x:Name="_fab"
|
||||
Image="plus.png"
|
||||
Clicked="AddButton_Clicked"
|
||||
Style="{StaticResource btn-fab}"
|
||||
AbsoluteLayout.LayoutFlags="PositionProportional"
|
||||
AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n AddItem}">
|
||||
<Button.Effects>
|
||||
<effects:FabShadowEffect />
|
||||
</Button.Effects>
|
||||
</Button>
|
||||
</AbsoluteLayout>
|
||||
|
||||
</pages:BaseContentPage>
|
||||
176
src/App/Pages/Authenticator/AuthenticatorPage.xaml.cs
Normal file
176
src/App/Pages/Authenticator/AuthenticatorPage.xaml.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using Bit.App.Resources;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.PlatformConfiguration;
|
||||
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class AuthenticatorPage : BaseContentPage
|
||||
{
|
||||
#region Members
|
||||
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly ICipherService _cipherService;
|
||||
private AuthenticatorPageViewModel _vm;
|
||||
private readonly bool _fromTabPage;
|
||||
private readonly Action<string> _selectAction;
|
||||
private readonly TabsPage _tabsPage;
|
||||
|
||||
#endregion
|
||||
|
||||
public AuthenticatorPage(bool fromTabPage, Action<string> selectAction = null, TabsPage tabsPage = null)
|
||||
{
|
||||
//_tabsPage = tabsPage;
|
||||
InitializeComponent();
|
||||
//_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
_vm = BindingContext as AuthenticatorPageViewModel;
|
||||
//_vm.Page = this;
|
||||
//_fromTabPage = fromTabPage;
|
||||
//_selectAction = selectAction;
|
||||
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
_absLayout.Children.Remove(_fab);
|
||||
ToolbarItems.Add(_aboutIconItem);
|
||||
ToolbarItems.Add(_addItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToolbarItems.Add(_syncItem);
|
||||
ToolbarItems.Add(_lockItem);
|
||||
ToolbarItems.Add(_aboutTextItem);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
|
||||
protected async override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
//if (!_fromTabPage)
|
||||
//{
|
||||
// await InitAsync();
|
||||
//}
|
||||
//_broadcasterService.Subscribe(nameof(GeneratorPage), async (message) =>
|
||||
//{
|
||||
// if (message.Command == "updatedTheme")
|
||||
// {
|
||||
// Device.BeginInvokeOnMainThread(() =>
|
||||
// {
|
||||
// //_vm.RedrawPassword();
|
||||
// });
|
||||
// }
|
||||
//});
|
||||
|
||||
await LoadOnAppearedAsync(_mainLayout, false, async () =>
|
||||
{
|
||||
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
catch (Exception e) when (e.Message.Contains("No key."))
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
if (!_vm.Loaded)
|
||||
{
|
||||
await _vm.LoadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
AdjustToolbar();
|
||||
//await CheckAddRequest();
|
||||
}, _mainContent);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async void Search_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
// var page = new SendsPage(_vm.Filter, _vm.Type != null);
|
||||
// await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
}
|
||||
|
||||
private async void Sync_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
// await _vm.SyncAsync();
|
||||
}
|
||||
|
||||
private async void Lock_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
// await _vaultTimeoutService.LockAsync(true, true);
|
||||
}
|
||||
|
||||
private void About_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
// _vm.ShowAbout();
|
||||
}
|
||||
|
||||
private async void AddButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
{
|
||||
// var page = new SendAddEditPage(null, null, _vm.Type);
|
||||
// await Navigation.PushModalAsync(new NavigationPage(page));
|
||||
}
|
||||
}
|
||||
|
||||
private async void RowSelected(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private async void Copy_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
//await _vm.CopyAsync();
|
||||
}
|
||||
|
||||
private async void More_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
//if (!DoOnce())
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
//var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
|
||||
// null, AppResources.PasswordHistory);
|
||||
//if (selection == AppResources.PasswordHistory)
|
||||
//{
|
||||
// var page = new GeneratorHistoryPage();
|
||||
// await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
|
||||
//}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
//_broadcasterService.Unsubscribe(nameof(GeneratorPage));
|
||||
}
|
||||
|
||||
private void AdjustToolbar()
|
||||
{
|
||||
//_addItem.IsEnabled = !_vm.Deleted;
|
||||
//_addItem.IconImageSource = _vm.Deleted ? null : "plus.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
129
src/App/Pages/Authenticator/AuthenticatorPageListItem.cs
Normal file
129
src/App/Pages/Authenticator/AuthenticatorPageListItem.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class AuthenticatorPageListItem : ExtendedViewModel
|
||||
{
|
||||
//private string _totpCode;
|
||||
private readonly ITotpService _totpService;
|
||||
|
||||
//public CipherView Cipher { get; set; }
|
||||
//public CipherType? Type { get; set; }
|
||||
//public int interval { get; set; }
|
||||
//public long TotpSec { get; set; }
|
||||
//private DateTime? _totpInterval = null;
|
||||
|
||||
private CipherView _cipher;
|
||||
|
||||
private bool _websiteIconsEnabled;
|
||||
private string _iconImageSource = string.Empty;
|
||||
|
||||
public int interval { get; set; }
|
||||
private string _totpSec;
|
||||
|
||||
private string _totpCode;
|
||||
private string _totpCodeFormatted = "938 928";
|
||||
|
||||
|
||||
public AuthenticatorPageListItem(CipherView cipherView, bool websiteIconsEnabled)
|
||||
{
|
||||
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
|
||||
Cipher = cipherView;
|
||||
WebsiteIconsEnabled = websiteIconsEnabled;
|
||||
interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
|
||||
}
|
||||
|
||||
|
||||
public Command CopyCommand { get; set; }
|
||||
|
||||
public CipherView Cipher
|
||||
{
|
||||
get => _cipher;
|
||||
set => SetProperty(ref _cipher, value);
|
||||
}
|
||||
|
||||
public string TotpCodeFormatted
|
||||
{
|
||||
get => _totpCodeFormatted;
|
||||
set => SetProperty(ref _totpCodeFormatted, value);
|
||||
}
|
||||
|
||||
public string TotpSec
|
||||
{
|
||||
get => _totpSec;
|
||||
set => SetProperty(ref _totpSec, value);
|
||||
}
|
||||
|
||||
public bool WebsiteIconsEnabled
|
||||
{
|
||||
get => _websiteIconsEnabled;
|
||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||
}
|
||||
|
||||
public bool ShowIconImage
|
||||
{
|
||||
get => WebsiteIconsEnabled
|
||||
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
|
||||
&& IconImageSource != null;
|
||||
}
|
||||
|
||||
public string IconImageSource
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||
{
|
||||
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
|
||||
}
|
||||
return _iconImageSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task TotpTickAsync()
|
||||
{
|
||||
var epoc = CoreHelpers.EpocUtcNow() / 1000;
|
||||
var mod = epoc % interval;
|
||||
var totpSec = interval - mod;
|
||||
TotpSec = totpSec.ToString();
|
||||
//TotpLow = totpSec < 7;
|
||||
if (mod == 0)
|
||||
{
|
||||
await TotpUpdateCodeAsync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task TotpUpdateCodeAsync()
|
||||
{
|
||||
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
|
||||
if (_totpCode != null)
|
||||
{
|
||||
if (_totpCode.Length > 4)
|
||||
{
|
||||
var half = (int)Math.Floor(_totpCode.Length / 2M);
|
||||
TotpCodeFormatted = string.Format("{0} {1}", _totpCode.Substring(0, half),
|
||||
_totpCode.Substring(half));
|
||||
}
|
||||
else
|
||||
{
|
||||
TotpCodeFormatted = _totpCode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TotpCodeFormatted = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
153
src/App/Pages/Authenticator/AuthenticatorPageViewModel.cs
Normal file
153
src/App/Pages/Authenticator/AuthenticatorPageViewModel.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class AuthenticatorPageViewModel : BaseViewModel
|
||||
{
|
||||
#region Members
|
||||
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly ITotpService _totpService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||
private readonly ICipherService _cipherService;
|
||||
|
||||
private bool _showList = true;
|
||||
private bool _refreshing;
|
||||
private bool _loaded;
|
||||
private bool _websiteIconsEnabled = true;
|
||||
//private long _totpSec;
|
||||
#endregion
|
||||
|
||||
#region Ctor
|
||||
|
||||
public AuthenticatorPageViewModel()
|
||||
{
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
|
||||
PageTitle = AppResources.Authenticator;
|
||||
Items = new ExtendedObservableCollection<AuthenticatorPageListItem>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public async Task CopyAsync()
|
||||
{
|
||||
//await _clipboardService.CopyTextAsync(Password);
|
||||
//_platformUtilsService.ShowToast("success", null,
|
||||
// string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
var authed = await _userService.IsAuthenticatedAsync();
|
||||
if (!authed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await LoadDataAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ShowList = true;
|
||||
Refreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
var _allCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||
_allCiphers = _allCiphers.Where(c => c.Type == Core.Enums.CipherType.Login && c.Login.Totp != null).ToList();
|
||||
var filteredCiphers = _allCiphers.Select(c => new AuthenticatorPageListItem(c, WebsiteIconsEnabled)).ToList();
|
||||
Items.ResetWithRange(filteredCiphers);
|
||||
|
||||
foreach (AuthenticatorPageListItem item in Items)
|
||||
{
|
||||
item.TotpUpdateCodeAsync();
|
||||
}
|
||||
|
||||
//await TotpUpdateCodeAsync();
|
||||
// var interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
|
||||
// await TotpTickAsync(interval);
|
||||
// _totpInterval = DateTime.UtcNow;
|
||||
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
|
||||
{
|
||||
foreach(AuthenticatorPageListItem item in Items)
|
||||
{
|
||||
item.TotpTickAsync();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
//}
|
||||
|
||||
//private async Task TotpTickAsync(int intervalSeconds)
|
||||
//{
|
||||
// var epoc = CoreHelpers.EpocUtcNow() / 1000;
|
||||
// var mod = epoc % intervalSeconds;
|
||||
// var totpSec = intervalSeconds - mod;
|
||||
// TotpSec = totpSec.ToString();
|
||||
// TotpLow = totpSec < 7;
|
||||
// if (mod == 0)
|
||||
// {
|
||||
// await TotpUpdateCodeAsync();
|
||||
// }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public ExtendedObservableCollection<AuthenticatorPageListItem> Items { get; set; }
|
||||
public Command RefreshCommand { get; set; }
|
||||
|
||||
public bool ShowList
|
||||
{
|
||||
get => _showList;
|
||||
set => SetProperty(ref _showList, value);
|
||||
}
|
||||
|
||||
public bool Refreshing
|
||||
{
|
||||
get => _refreshing;
|
||||
set => SetProperty(ref _refreshing, value);
|
||||
}
|
||||
|
||||
public bool WebsiteIconsEnabled
|
||||
{
|
||||
get => _websiteIconsEnabled;
|
||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||
}
|
||||
|
||||
public bool Loaded
|
||||
{
|
||||
get => _loaded;
|
||||
set => SetProperty(ref _loaded, value);
|
||||
}
|
||||
//public long TotpSec
|
||||
//{
|
||||
// get => _totpSec;
|
||||
// set => SetProperty(ref _totpSec, value);
|
||||
//}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,6 @@ namespace Bit.App.Pages
|
||||
VerticalOptions = LayoutOptions.CenterAndExpand,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Color = ThemeManager.GetResourceColor("PrimaryColor"),
|
||||
AutomationId = "activity_indicator"
|
||||
};
|
||||
if (targetView != null)
|
||||
{
|
||||
|
||||
@@ -37,11 +37,14 @@ namespace Bit.App.Pages
|
||||
bool cancelled = false;
|
||||
try
|
||||
{
|
||||
// PrefersEphemeralWebBrowserSession should be false to allow access to the hCaptcha accessibility
|
||||
// cookie set in the default browser
|
||||
// https://www.hcaptcha.com/accessibility
|
||||
var options = new WebAuthenticatorOptions
|
||||
{
|
||||
Url = new Uri(url),
|
||||
CallbackUrl = new Uri(callbackUri),
|
||||
PrefersEphemeralWebBrowserSession = true,
|
||||
PrefersEphemeralWebBrowserSession = false,
|
||||
};
|
||||
authResult = await WebAuthenticator.AuthenticateAsync(options);
|
||||
}
|
||||
|
||||
@@ -183,52 +183,68 @@
|
||||
<Label
|
||||
Text="A-Z"
|
||||
StyleClass="box-label-regular"
|
||||
HorizontalOptions="StartAndExpand" />
|
||||
HorizontalOptions="StartAndExpand"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
|
||||
<Switch
|
||||
IsToggled="{Binding Uppercase}"
|
||||
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
|
||||
Converter={StaticResource inverseBool}}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
HorizontalOptions="End"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
|
||||
</StackLayout>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
<StackLayout StyleClass="box-row, box-row-switch">
|
||||
<Label
|
||||
Text="a-z"
|
||||
StyleClass="box-label-regular"
|
||||
HorizontalOptions="StartAndExpand" />
|
||||
HorizontalOptions="StartAndExpand"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
|
||||
<Switch
|
||||
IsToggled="{Binding Lowercase}"
|
||||
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
|
||||
Converter={StaticResource inverseBool}}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
HorizontalOptions="End"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n LowercaseAtoZ}" />
|
||||
</StackLayout>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
<StackLayout StyleClass="box-row, box-row-switch">
|
||||
<Label
|
||||
Text="0-9"
|
||||
StyleClass="box-label-regular"
|
||||
HorizontalOptions="StartAndExpand" />
|
||||
HorizontalOptions="StartAndExpand"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
|
||||
<Switch
|
||||
IsToggled="{Binding Number}"
|
||||
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
|
||||
Converter={StaticResource inverseBool}}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
HorizontalOptions="End"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
|
||||
</StackLayout>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
<StackLayout StyleClass="box-row, box-row-switch">
|
||||
<Label
|
||||
Text="!@#$%^&*"
|
||||
StyleClass="box-label-regular"
|
||||
HorizontalOptions="StartAndExpand" />
|
||||
HorizontalOptions="StartAndExpand"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
|
||||
<Switch
|
||||
IsToggled="{Binding Special}"
|
||||
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
|
||||
Converter={StaticResource inverseBool}}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
HorizontalOptions="End"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
|
||||
</StackLayout>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
<StackLayout StyleClass="box-row, box-row-stepper">
|
||||
@@ -277,7 +293,7 @@
|
||||
StyleClass="box-label-regular"
|
||||
HorizontalOptions="StartAndExpand" />
|
||||
<Switch
|
||||
IsToggled="{Binding AvoidAmbiguous}"
|
||||
IsToggled="{Binding AvoidAmbiguousChars}"
|
||||
StyleClass="box-value"
|
||||
HorizontalOptions="End" />
|
||||
</StackLayout>
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Bit.App.Pages
|
||||
private bool _lowercase;
|
||||
private bool _number;
|
||||
private bool _special;
|
||||
private bool _avoidAmbiguous;
|
||||
private bool _allowAmbiguousChars;
|
||||
private int _minNumber;
|
||||
private int _minSpecial;
|
||||
private int _length = 5;
|
||||
@@ -130,19 +130,29 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
public bool AvoidAmbiguous
|
||||
public bool AllowAmbiguousChars
|
||||
{
|
||||
get => _avoidAmbiguous;
|
||||
get => _allowAmbiguousChars;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _avoidAmbiguous, value))
|
||||
if (SetProperty(ref _allowAmbiguousChars, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(AvoidAmbiguousChars)
|
||||
}))
|
||||
{
|
||||
_options.Ambiguous = !value;
|
||||
_options.AllowAmbiguousChar = value;
|
||||
var task = SaveOptionsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AvoidAmbiguousChars
|
||||
{
|
||||
get => !AllowAmbiguousChars;
|
||||
set => AllowAmbiguousChars = !value;
|
||||
}
|
||||
|
||||
public int MinNumber
|
||||
{
|
||||
get => _minNumber;
|
||||
@@ -315,7 +325,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private void LoadFromOptions()
|
||||
{
|
||||
AvoidAmbiguous = !_options.Ambiguous.GetValueOrDefault();
|
||||
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault();
|
||||
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
|
||||
IsPassword = TypeSelectedIndex == 0;
|
||||
MinNumber = _options.MinNumber.GetValueOrDefault();
|
||||
@@ -333,7 +343,7 @@ namespace Bit.App.Pages
|
||||
|
||||
private void SetOptions()
|
||||
{
|
||||
_options.Ambiguous = !AvoidAmbiguous;
|
||||
_options.AllowAmbiguousChar = AllowAmbiguousChars;
|
||||
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
|
||||
_options.MinNumber = MinNumber;
|
||||
_options.MinSpecial = MinSpecial;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<pages:BaseContentPage
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
||||
x:Class="Bit.App.Pages.SendAddEditPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
@@ -121,6 +122,7 @@
|
||||
Clicked="FileType_Clicked"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n File}"
|
||||
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
|
||||
Grid.Column="0">
|
||||
</Button>
|
||||
<Button
|
||||
@@ -132,6 +134,7 @@
|
||||
Clicked="TextType_Clicked"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Text}"
|
||||
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
|
||||
Grid.Column="1">
|
||||
</Button>
|
||||
</Grid>
|
||||
@@ -250,28 +253,31 @@
|
||||
</StackLayout>
|
||||
<StackLayout
|
||||
Orientation="Horizontal"
|
||||
Spacing="0">
|
||||
Spacing="0"
|
||||
xct:TouchEffect.Command="{Binding ToggleOptionsCommand}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
|
||||
<Button
|
||||
Text="{u:I18n Options}"
|
||||
x:Name="_btnOptions"
|
||||
StyleClass="box-row-button"
|
||||
TextColor="{DynamicResource PrimaryColor}"
|
||||
Margin="0"
|
||||
Clicked="ToggleOptions_Clicked"/>
|
||||
AutomationProperties.IsInAccessibleTree="False"/>
|
||||
<controls:IconButton
|
||||
x:Name="_btnOptionsUp"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
||||
StyleClass="box-row-button"
|
||||
TextColor="{DynamicResource PrimaryColor}"
|
||||
Clicked="ToggleOptions_Clicked"
|
||||
IsVisible="{Binding ShowOptions}" />
|
||||
IsVisible="{Binding ShowOptions}"
|
||||
AutomationProperties.IsInAccessibleTree="False"/>
|
||||
<controls:IconButton
|
||||
x:Name="_btnOptionsDown"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
||||
StyleClass="box-row-button"
|
||||
TextColor="{DynamicResource PrimaryColor}"
|
||||
Clicked="ToggleOptions_Clicked"
|
||||
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" />
|
||||
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
|
||||
AutomationProperties.IsInAccessibleTree="False"/>
|
||||
</StackLayout>
|
||||
<StackLayout IsVisible="{Binding ShowOptions}">
|
||||
<StackLayout
|
||||
@@ -438,7 +444,8 @@
|
||||
Command="{Binding TogglePasswordCommand}"
|
||||
Margin="10,0,0,0"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||
</StackLayout>
|
||||
<Label
|
||||
Text="{u:I18n PasswordInfo}"
|
||||
|
||||
@@ -209,11 +209,6 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleOptions_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
_vm.ToggleOptions();
|
||||
}
|
||||
|
||||
private void ClearExpirationDate_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace Bit.App.Pages
|
||||
{
|
||||
nameof(IsText),
|
||||
nameof(IsFile),
|
||||
nameof(FileTypeAccessibilityLabel),
|
||||
nameof(TextTypeAccessibilityLabel)
|
||||
};
|
||||
private bool _disableHideEmail;
|
||||
private bool _sendOptionsPolicyInEffect;
|
||||
@@ -59,6 +61,7 @@ namespace Bit.App.Pages
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
ToggleOptionsCommand = new Command(ToggleOptions);
|
||||
|
||||
TypeOptions = new List<KeyValuePair<string, SendType>>
|
||||
{
|
||||
@@ -89,6 +92,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
public Command TogglePasswordCommand { get; set; }
|
||||
public Command ToggleOptionsCommand { get; set; }
|
||||
public string SendId { get; set; }
|
||||
public int SegmentedButtonHeight { get; set; }
|
||||
public int SegmentedButtonFontSize { get; set; }
|
||||
@@ -102,6 +106,7 @@ namespace Bit.App.Pages
|
||||
public bool DisableHideEmailControl { get; set; }
|
||||
public bool IsAddFromShare { get; set; }
|
||||
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
|
||||
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
|
||||
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
||||
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
||||
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
|
||||
@@ -134,7 +139,11 @@ namespace Bit.App.Pages
|
||||
public bool ShowOptions
|
||||
{
|
||||
get => _showOptions;
|
||||
set => SetProperty(ref _showOptions, value);
|
||||
set => SetProperty(ref _showOptions, value,
|
||||
additionalPropertyNames: new[]
|
||||
{
|
||||
nameof(OptionsAccessilibityText)
|
||||
});
|
||||
}
|
||||
public int ExpirationDateTypeSelectedIndex
|
||||
{
|
||||
@@ -211,7 +220,8 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new[]
|
||||
{
|
||||
nameof(ShowPasswordIcon)
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText)
|
||||
});
|
||||
}
|
||||
public bool DisableHideEmail
|
||||
@@ -231,6 +241,9 @@ namespace Bit.App.Pages
|
||||
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
||||
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
||||
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
Grid.Column="1"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
||||
<Label
|
||||
Text="{u:I18n ConfirmYourIdentity}"
|
||||
|
||||
@@ -109,7 +109,11 @@ namespace Bit.App.Pages
|
||||
{
|
||||
get => _showPassword;
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) });
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText),
|
||||
});
|
||||
}
|
||||
|
||||
public bool UseOTPVerification
|
||||
@@ -139,6 +143,7 @@ namespace Bit.App.Pages
|
||||
public Command TogglePasswordCommand { get; }
|
||||
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
|
||||
public void TogglePassword()
|
||||
{
|
||||
|
||||
@@ -167,6 +167,10 @@ namespace Bit.App.Pages
|
||||
{
|
||||
await _vm.UpdatePinAsync();
|
||||
}
|
||||
else if (item.Name == AppResources.ReportCrashLogs)
|
||||
{
|
||||
await _vm.LoggerReportingAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
var biometricName = AppResources.Biometrics;
|
||||
|
||||
@@ -7,10 +7,10 @@ using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
using ZXing.Client.Result;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
@@ -29,7 +29,7 @@ namespace Bit.App.Pages
|
||||
private readonly ILocalizeService _localizeService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
|
||||
private readonly ILogger _loggerService;
|
||||
private const int CustomVaultTimeoutValue = -100;
|
||||
|
||||
private bool _supportsBiometric;
|
||||
@@ -39,6 +39,7 @@ namespace Bit.App.Pages
|
||||
private string _vaultTimeoutDisplayValue;
|
||||
private string _vaultTimeoutActionDisplayValue;
|
||||
private bool _showChangeMasterPassword;
|
||||
private bool _reportLoggingEnabled;
|
||||
|
||||
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
||||
new List<KeyValuePair<string, int?>>
|
||||
@@ -79,6 +80,7 @@ namespace Bit.App.Pages
|
||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||
PageTitle = AppResources.Settings;
|
||||
@@ -123,7 +125,7 @@ namespace Bit.App.Pages
|
||||
|
||||
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
||||
!await _keyConnectorService.GetUsesKeyConnector();
|
||||
|
||||
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||
BuildList();
|
||||
}
|
||||
|
||||
@@ -286,6 +288,26 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoggerReportingAsync()
|
||||
{
|
||||
var options = new[]
|
||||
{
|
||||
CreateSelectableOption(AppResources.Yes, _reportLoggingEnabled),
|
||||
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
|
||||
};
|
||||
|
||||
var selection = await Page.DisplayActionSheet(AppResources.ReportCrashLogsDescription, AppResources.Cancel, null, options);
|
||||
|
||||
if (selection == null || selection == AppResources.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _loggerService.SetEnabled(CompareSelection(selection, AppResources.Yes));
|
||||
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||
BuildList();
|
||||
}
|
||||
|
||||
public async Task VaultTimeoutActionAsync()
|
||||
{
|
||||
var options = _vaultTimeoutActions.Select(o =>
|
||||
@@ -494,11 +516,19 @@ namespace Bit.App.Pages
|
||||
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
|
||||
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
|
||||
}
|
||||
|
||||
var otherItems = new List<SettingsPageListItem>
|
||||
{
|
||||
new SettingsPageListItem { Name = AppResources.Options },
|
||||
new SettingsPageListItem { Name = AppResources.About },
|
||||
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
|
||||
#if !FDROID
|
||||
new SettingsPageListItem
|
||||
{
|
||||
Name = AppResources.ReportCrashLogs,
|
||||
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
||||
},
|
||||
#endif
|
||||
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
||||
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
||||
};
|
||||
@@ -576,5 +606,9 @@ namespace Bit.App.Pages
|
||||
{
|
||||
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
|
||||
}
|
||||
|
||||
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
||||
|
||||
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Bit.App.Effects;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Effects;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
@@ -10,15 +12,19 @@ namespace Bit.App.Pages
|
||||
{
|
||||
public class TabsPage : TabbedPage
|
||||
{
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
|
||||
private NavigationPage _groupingsPage;
|
||||
private NavigationPage _authenticatorPage;
|
||||
private NavigationPage _sendGroupingsPage;
|
||||
private NavigationPage _generatorPage;
|
||||
|
||||
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
||||
{
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
|
||||
@@ -27,8 +33,16 @@ namespace Bit.App.Pages
|
||||
Title = AppResources.MyVault,
|
||||
IconImageSource = "lock.png"
|
||||
};
|
||||
|
||||
Children.Add(_groupingsPage);
|
||||
|
||||
_authenticatorPage = new NavigationPage(new AuthenticatorPage(true, null, this))
|
||||
{
|
||||
Title = AppResources.Authenticator,
|
||||
IconImageSource = "info.png"
|
||||
};
|
||||
Children.Add(_authenticatorPage);
|
||||
|
||||
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
|
||||
{
|
||||
Title = AppResources.Send,
|
||||
@@ -78,12 +92,26 @@ namespace Bit.App.Pages
|
||||
protected override async void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
|
||||
{
|
||||
if (message.Command == "syncCompleted")
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
|
||||
}
|
||||
});
|
||||
await UpdateVaultButtonTitleAsync();
|
||||
if (await _keyConnectorService.UserNeedsMigration())
|
||||
{
|
||||
_messagingService.Send("convertAccountToKeyConnector");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
_broadcasterService.Unsubscribe(nameof(TabsPage));
|
||||
}
|
||||
|
||||
public void ResetToVaultPage()
|
||||
{
|
||||
CurrentPage = _groupingsPage;
|
||||
@@ -131,5 +159,19 @@ namespace Bit.App.Pages
|
||||
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateVaultButtonTitleAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
var isShowingVaultFilter = await policyService.ShouldShowVaultFilterAsync();
|
||||
_groupingsPage.Title = isShowingVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
|
||||
@@ -249,7 +249,8 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowPasswordIcon)
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText)
|
||||
});
|
||||
}
|
||||
public bool ShowCardNumber
|
||||
@@ -298,6 +299,7 @@ namespace Bit.App.Pages
|
||||
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
||||
public bool AllowPersonal { get; set; }
|
||||
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
Text=""
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Clicked="BackButton_Clicked"
|
||||
x:Name="_backButton" />
|
||||
x:Name="_backButton"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n TapToGoBack}"/>
|
||||
<controls:ExtendedSearchBar
|
||||
x:Name="_searchBar"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="pages:GroupingsPageViewModel"
|
||||
Title="{Binding PageTitle}"
|
||||
x:Name="_page">
|
||||
@@ -106,6 +107,30 @@
|
||||
GroupTemplate="{StaticResource groupTemplate}" />
|
||||
|
||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||
<StackLayout
|
||||
IsVisible="{Binding ShowVaultFilter}"
|
||||
Orientation="Horizontal"
|
||||
HorizontalOptions="FillAndExpand"
|
||||
Margin="0,5,0,0">
|
||||
<Label
|
||||
Text="{Binding VaultFilterDescription}"
|
||||
LineBreakMode="TailTruncation"
|
||||
Margin="10,0"
|
||||
StyleClass="text-md, text-muted"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="StartAndExpand"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Filter}" />
|
||||
<controls:MiButton
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||
Command="{Binding VaultFilterCommand}"
|
||||
VerticalOptions="Center"
|
||||
HorizontalOptions="End"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n Filter}" />
|
||||
</StackLayout>
|
||||
|
||||
<StackLayout
|
||||
VerticalOptions="CenterAndExpand"
|
||||
Padding="20, 0"
|
||||
|
||||
@@ -27,8 +27,8 @@ namespace Bit.App.Pages
|
||||
private PreviousPageInfo _previousPage;
|
||||
|
||||
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
|
||||
string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null,
|
||||
bool deleted = false)
|
||||
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
|
||||
PreviousPageInfo previousPage = null, bool deleted = false)
|
||||
{
|
||||
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
||||
InitializeComponent();
|
||||
@@ -52,6 +52,10 @@ namespace Bit.App.Pages
|
||||
{
|
||||
_vm.PageTitle = pageTitle;
|
||||
}
|
||||
if (vaultFilterSelection != null)
|
||||
{
|
||||
_vm.VaultFilterDescription = vaultFilterSelection;
|
||||
}
|
||||
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Resources;
|
||||
@@ -29,7 +30,10 @@ namespace Bit.App.Pages
|
||||
private bool _showList;
|
||||
private bool _websiteIconsEnabled;
|
||||
private bool _syncRefreshing;
|
||||
private bool _showVaultFilter;
|
||||
private string _vaultFilterSelection;
|
||||
private string _noDataText;
|
||||
private List<Organization> _organizations;
|
||||
private List<CipherView> _allCiphers;
|
||||
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
||||
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
||||
@@ -46,6 +50,8 @@ namespace Bit.App.Pages
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public GroupingsPageViewModel()
|
||||
@@ -60,10 +66,11 @@ namespace Bit.App.Pages
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
Loading = true;
|
||||
PageTitle = AppResources.MyVault;
|
||||
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
||||
RefreshCommand = new Command(async () =>
|
||||
{
|
||||
@@ -71,6 +78,9 @@ namespace Bit.App.Pages
|
||||
await LoadAsync();
|
||||
});
|
||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||
onException: ex => _logger.Exception(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||
{
|
||||
@@ -87,8 +97,9 @@ namespace Bit.App.Pages
|
||||
public bool HasCiphers { get; set; }
|
||||
public bool HasFolders { get; set; }
|
||||
public bool HasCollections { get; set; }
|
||||
public bool ShowNoFolderCiphers => (NoFolderCiphers?.Count ?? int.MaxValue) < NoFolderListSize &&
|
||||
(!Collections?.Any() ?? true);
|
||||
public bool ShowNoFolderCipherGroup => NoFolderCiphers != null
|
||||
&& NoFolderCiphers.Count < NoFolderListSize
|
||||
&& (Collections is null || !Collections.Any());
|
||||
public List<CipherView> Ciphers { get; set; }
|
||||
public List<CipherView> FavoriteCiphers { get; set; }
|
||||
public List<CipherView> NoFolderCiphers { get; set; }
|
||||
@@ -142,12 +153,30 @@ namespace Bit.App.Pages
|
||||
get => _websiteIconsEnabled;
|
||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||
}
|
||||
public bool ShowVaultFilter
|
||||
{
|
||||
get => _showVaultFilter;
|
||||
set => SetProperty(ref _showVaultFilter, value);
|
||||
}
|
||||
public string VaultFilterDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||
{
|
||||
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||
}
|
||||
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||
}
|
||||
set => SetProperty(ref _vaultFilterSelection, value);
|
||||
}
|
||||
|
||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||
|
||||
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||
public Command RefreshCommand { get; set; }
|
||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||
public ICommand VaultFilterCommand { get; }
|
||||
public bool LoadedOnce { get; set; }
|
||||
|
||||
public async Task LoadAsync()
|
||||
@@ -172,6 +201,17 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
_organizations = await _organizationService.GetAllAsync();
|
||||
if (MainPage)
|
||||
{
|
||||
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
||||
if (ShowVaultFilter && _vaultFilterSelection == null)
|
||||
{
|
||||
_vaultFilterSelection = AppResources.AllVaults;
|
||||
}
|
||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||
}
|
||||
|
||||
_doingLoad = true;
|
||||
LoadedOnce = true;
|
||||
ShowNoData = false;
|
||||
@@ -185,9 +225,9 @@ namespace Bit.App.Pages
|
||||
try
|
||||
{
|
||||
await LoadDataAsync();
|
||||
if (ShowNoFolderCiphers && (NestedFolders?.Any() ?? false))
|
||||
if (ShowNoFolderCipherGroup && (NestedFolders?.Any() ?? false))
|
||||
{
|
||||
// Remove "No Folder" from folder listing
|
||||
// Remove "No Folder" folder from folders group
|
||||
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
||||
}
|
||||
|
||||
@@ -262,7 +302,7 @@ namespace Bit.App.Pages
|
||||
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
|
||||
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
|
||||
}
|
||||
if (ShowNoFolderCiphers)
|
||||
if (ShowNoFolderCipherGroup)
|
||||
{
|
||||
var noFolderCiphersListItems = NoFolderCiphers.Select(
|
||||
c => new GroupingsPageListItem { Cipher = c }).ToList();
|
||||
@@ -294,6 +334,12 @@ namespace Bit.App.Pages
|
||||
items.AddRange(itemGroup);
|
||||
}
|
||||
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||
// because of update to XF v5.0.0.2401
|
||||
GroupedItems.Clear();
|
||||
}
|
||||
GroupedItems.ReplaceRange(items);
|
||||
}
|
||||
else
|
||||
@@ -316,6 +362,12 @@ namespace Bit.App.Pages
|
||||
|
||||
if (groupedItems.Any())
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||
// because of update to XF v5.0.0.2401
|
||||
GroupedItems.Clear();
|
||||
}
|
||||
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||
GroupedItems.AddRange(items);
|
||||
}
|
||||
@@ -342,6 +394,25 @@ namespace Bit.App.Pages
|
||||
SyncRefreshing = false;
|
||||
}
|
||||
|
||||
public async Task VaultFilterOptionsAsync()
|
||||
{
|
||||
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||
if (_organizations.Any())
|
||||
{
|
||||
options.AddRange(_organizations.Select(o => o.Name));
|
||||
}
|
||||
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||
options.ToArray());
|
||||
if (selection == AppResources.Cancel ||
|
||||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||
{
|
||||
return;
|
||||
}
|
||||
VaultFilterDescription = selection;
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
public async Task SelectCipherAsync(CipherView cipher)
|
||||
{
|
||||
var page = new ViewPage(cipher.Id);
|
||||
@@ -368,25 +439,26 @@ namespace Bit.App.Pages
|
||||
default:
|
||||
break;
|
||||
}
|
||||
var page = new GroupingsPage(false, type, null, null, title);
|
||||
var page = new GroupingsPage(false, type, null, null, title, _vaultFilterSelection);
|
||||
await Page.Navigation.PushAsync(page);
|
||||
}
|
||||
|
||||
public async Task SelectFolderAsync(FolderView folder)
|
||||
{
|
||||
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name);
|
||||
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name, _vaultFilterSelection);
|
||||
await Page.Navigation.PushAsync(page);
|
||||
}
|
||||
|
||||
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
|
||||
{
|
||||
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name);
|
||||
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name, _vaultFilterSelection);
|
||||
await Page.Navigation.PushAsync(page);
|
||||
}
|
||||
|
||||
public async Task SelectTrashAsync()
|
||||
{
|
||||
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, null, true);
|
||||
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, _vaultFilterSelection, null,
|
||||
true);
|
||||
await Page.Navigation.PushAsync(page);
|
||||
}
|
||||
|
||||
@@ -424,8 +496,8 @@ namespace Bit.App.Pages
|
||||
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
|
||||
NoDataText = AppResources.NoItems;
|
||||
_allCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||
HasCiphers = _allCiphers.Any();
|
||||
FavoriteCiphers?.Clear();
|
||||
NoFolderCiphers?.Clear();
|
||||
@@ -439,12 +511,11 @@ namespace Bit.App.Pages
|
||||
|
||||
if (MainPage)
|
||||
{
|
||||
Folders = await _folderService.GetAllDecryptedAsync();
|
||||
NestedFolders = await _folderService.GetAllNestedAsync();
|
||||
await FillFoldersAndCollectionsAsync(orgId);
|
||||
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
||||
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
||||
Collections = await _collectionService.GetAllDecryptedAsync();
|
||||
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
|
||||
HasCollections = NestedCollections.Any();
|
||||
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
||||
HasCollections = NestedCollections?.Any() ?? false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -564,6 +635,63 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
|
||||
{
|
||||
string orgId = null;
|
||||
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||
if (IsVaultFilterMyVault)
|
||||
{
|
||||
_allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||
}
|
||||
else if (IsVaultFilterOrgVault)
|
||||
{
|
||||
orgId = GetVaultFilterOrgId();
|
||||
_allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_allCiphers = decCiphers;
|
||||
}
|
||||
return orgId;
|
||||
}
|
||||
|
||||
private async Task FillFoldersAndCollectionsAsync(string orgId)
|
||||
{
|
||||
var decFolders = await _folderService.GetAllDecryptedAsync();
|
||||
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
||||
if (IsVaultFilterMyVault)
|
||||
{
|
||||
Folders = BuildFolders(decFolders);
|
||||
Collections = null;
|
||||
}
|
||||
else if (IsVaultFilterOrgVault && !string.IsNullOrWhiteSpace(orgId))
|
||||
{
|
||||
Folders = BuildFolders(decFolders);
|
||||
Collections = decCollections?.Where(c => c.OrganizationId == orgId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
Folders = decFolders;
|
||||
Collections = decCollections;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||
|
||||
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||
_vaultFilterSelection != AppResources.MyVault;
|
||||
|
||||
private string GetVaultFilterOrgId()
|
||||
{
|
||||
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||
}
|
||||
|
||||
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
||||
{
|
||||
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
||||
return folders.Any() ? folders : null;
|
||||
}
|
||||
|
||||
private async void CipherOptionsAsync(CipherView cipher)
|
||||
{
|
||||
if ((Page as BaseContentPage).DoOnce())
|
||||
|
||||
@@ -41,8 +41,11 @@ namespace Bit.App.Pages
|
||||
{
|
||||
var cipher = await _cipherService.GetAsync(CipherId);
|
||||
var decCipher = await cipher.DecryptAsync();
|
||||
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
|
||||
ShowNoData = History.Count == 0;
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
|
||||
ShowNoData = History.Count == 0;
|
||||
});
|
||||
}
|
||||
|
||||
private async void CopyAsync(PasswordHistoryView ph)
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
|
||||
@@ -120,7 +120,8 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _showPassword, value,
|
||||
additionalPropertyNames: new string[]
|
||||
{
|
||||
nameof(ShowPasswordIcon)
|
||||
nameof(ShowPasswordIcon),
|
||||
nameof(PasswordVisibilityAccessibilityText)
|
||||
});
|
||||
}
|
||||
public bool ShowCardNumber
|
||||
@@ -213,6 +214,7 @@ namespace Bit.App.Pages
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string TotpCodeFormatted
|
||||
{
|
||||
get => _totpCodeFormatted;
|
||||
@@ -754,12 +756,12 @@ namespace Bit.App.Pages
|
||||
{
|
||||
if (IsBooleanType)
|
||||
{
|
||||
return _field.Value == "true" ? "" : "";
|
||||
return _field.Value == "true" ? BitwardenIcons.Square : BitwardenIcons.CheckSquare;
|
||||
}
|
||||
else if (IsLinkedType)
|
||||
{
|
||||
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
||||
return " " + _i18nService.T(i18nKey);
|
||||
return BitwardenIcons.Link + _i18nService.T(i18nKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
132
src/App/Resources/AppResources.Designer.cs
generated
132
src/App/Resources/AppResources.Designer.cs
generated
@@ -353,6 +353,12 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string Authenticator {
|
||||
get {
|
||||
return ResourceManager.GetString("Authenticator", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Name", resourceCulture);
|
||||
@@ -3299,6 +3305,12 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string Text {
|
||||
get {
|
||||
return ResourceManager.GetString("Text", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string TypeText {
|
||||
get {
|
||||
return ResourceManager.GetString("TypeText", resourceCulture);
|
||||
@@ -3329,6 +3341,30 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
public static string FileTypeIsSelected {
|
||||
get {
|
||||
return ResourceManager.GetString("FileTypeIsSelected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string FileTypeIsNotSelected {
|
||||
get {
|
||||
return ResourceManager.GetString("FileTypeIsNotSelected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string TextTypeIsSelected {
|
||||
get {
|
||||
return ResourceManager.GetString("TextTypeIsSelected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string TextTypeIsNotSelected {
|
||||
get {
|
||||
return ResourceManager.GetString("TextTypeIsNotSelected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DeletionDate {
|
||||
get {
|
||||
return ResourceManager.GetString("DeletionDate", resourceCulture);
|
||||
@@ -3892,5 +3928,101 @@ namespace Bit.App.Resources {
|
||||
return ResourceManager.GetString("EnterTheVerificationCodeThatWasSentToYourEmail", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReportCrashLogs {
|
||||
get {
|
||||
return ResourceManager.GetString("ReportCrashLogs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReportCrashLogsDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("ReportCrashLogsDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string OptionsExpanded {
|
||||
get {
|
||||
return ResourceManager.GetString("OptionsExpanded", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string OptionsCollapsed {
|
||||
get {
|
||||
return ResourceManager.GetString("OptionsCollapsed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string UppercaseAtoZ {
|
||||
get {
|
||||
return ResourceManager.GetString("UppercaseAtoZ", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string LowercaseAtoZ {
|
||||
get {
|
||||
return ResourceManager.GetString("LowercaseAtoZ", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string NumbersZeroToNine {
|
||||
get {
|
||||
return ResourceManager.GetString("NumbersZeroToNine", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string SpecialCharacters {
|
||||
get {
|
||||
return ResourceManager.GetString("SpecialCharacters", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string TapToGoBack {
|
||||
get {
|
||||
return ResourceManager.GetString("TapToGoBack", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string PasswordIsVisibleTapToHide {
|
||||
get {
|
||||
return ResourceManager.GetString("PasswordIsVisibleTapToHide", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string PasswordIsNotVisibleTapToShow {
|
||||
get {
|
||||
return ResourceManager.GetString("PasswordIsNotVisibleTapToShow", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string FilterByVault {
|
||||
get {
|
||||
return ResourceManager.GetString("FilterByVault", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string AllVaults {
|
||||
get {
|
||||
return ResourceManager.GetString("AllVaults", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string Vaults {
|
||||
get {
|
||||
return ResourceManager.GetString("Vaults", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string VaultFilterDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("VaultFilterDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public static string All {
|
||||
get {
|
||||
return ResourceManager.GetString("All", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +299,10 @@
|
||||
<value>My Vault</value>
|
||||
<comment>The title for the vault page.</comment>
|
||||
</data>
|
||||
<data name="Authenticator" xml:space="preserve">
|
||||
<value>Authenticator</value>
|
||||
<comment>Authenticator TOTP feature</comment>
|
||||
</data>
|
||||
<data name="Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
<comment>Label for an entity name.</comment>
|
||||
@@ -1861,6 +1865,9 @@
|
||||
<value>A friendly name to describe this Send.</value>
|
||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="Text" xml:space="preserve">
|
||||
<value>Text</value>
|
||||
</data>
|
||||
<data name="TypeText" xml:space="preserve">
|
||||
<value>Text</value>
|
||||
</data>
|
||||
@@ -1877,6 +1884,18 @@
|
||||
<data name="TypeFileInfo" xml:space="preserve">
|
||||
<value>The file you want to send.</value>
|
||||
</data>
|
||||
<data name="FileTypeIsSelected" xml:space="preserve">
|
||||
<value>File type is selected.</value>
|
||||
</data>
|
||||
<data name="FileTypeIsNotSelected" xml:space="preserve">
|
||||
<value>File type is not selected, tap to select.</value>
|
||||
</data>
|
||||
<data name="TextTypeIsSelected" xml:space="preserve">
|
||||
<value>Text type is selected.</value>
|
||||
</data>
|
||||
<data name="TextTypeIsNotSelected" xml:space="preserve">
|
||||
<value>Text type is not selected, tap to select.</value>
|
||||
</data>
|
||||
<data name="DeletionDate" xml:space="preserve">
|
||||
<value>Deletion Date</value>
|
||||
</data>
|
||||
@@ -2181,4 +2200,52 @@
|
||||
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
|
||||
<value>Enter the verification code that was sent to your email</value>
|
||||
</data>
|
||||
<data name="ReportCrashLogs" xml:space="preserve">
|
||||
<value>Report crash logs</value>
|
||||
</data>
|
||||
<data name="ReportCrashLogsDescription" xml:space="preserve">
|
||||
<value>Help Bitwarden improve app stability by allowing crash reports.</value>
|
||||
</data>
|
||||
<data name="OptionsExpanded" xml:space="preserve">
|
||||
<value>Options are expanded, tap to collapse.</value>
|
||||
</data>
|
||||
<data name="OptionsCollapsed" xml:space="preserve">
|
||||
<value>Options are collapsed, tap to expand.</value>
|
||||
</data>
|
||||
<data name="UppercaseAtoZ" xml:space="preserve">
|
||||
<value>Uppercase (A to Z)</value>
|
||||
</data>
|
||||
<data name="LowercaseAtoZ" xml:space="preserve">
|
||||
<value>Lowercase (A to Z)</value>
|
||||
</data>
|
||||
<data name="NumbersZeroToNine" xml:space="preserve">
|
||||
<value>Numbers (0 to 9)</value>
|
||||
</data>
|
||||
<data name="SpecialCharacters" xml:space="preserve">
|
||||
<value>Special Characters (!@#$%^&*)</value>
|
||||
</data>
|
||||
<data name="TapToGoBack" xml:space="preserve">
|
||||
<value>Tap to go back</value>
|
||||
</data>
|
||||
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
|
||||
<value>Password is visible, tap to hide.</value>
|
||||
</data>
|
||||
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
|
||||
<value>Password is not visible, tap to show.</value>
|
||||
</data>
|
||||
<data name="FilterByVault" xml:space="preserve">
|
||||
<value>Filter items by vault</value>
|
||||
</data>
|
||||
<data name="AllVaults" xml:space="preserve">
|
||||
<value>All Vaults</value>
|
||||
</data>
|
||||
<data name="Vaults" xml:space="preserve">
|
||||
<value>Vaults</value>
|
||||
</data>
|
||||
<data name="VaultFilterDescription" xml:space="preserve">
|
||||
<value>Vault: {0}</value>
|
||||
</data>
|
||||
<data name="All" xml:space="preserve">
|
||||
<value>All</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null);
|
||||
Task<List<Folder>> GetAllAsync();
|
||||
Task<List<FolderView>> GetAllDecryptedAsync();
|
||||
Task<List<TreeNode<FolderView>>> GetAllNestedAsync();
|
||||
Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null);
|
||||
Task<Folder> GetAsync(string id);
|
||||
Task<TreeNode<FolderView>> GetNestedAsync(string id);
|
||||
Task ReplaceAsync(Dictionary<string, FolderData> folders);
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
/// <summary>
|
||||
/// Place necessary code to initiaze logger
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task InitAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the current logger is enable or disable.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<bool> IsEnabled();
|
||||
|
||||
/// <summary>
|
||||
/// Changes the state of the current logger. Setting state enabled to false will block logging.
|
||||
/// </summary>
|
||||
Task SetEnabled(bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Logs something that is not in itself an exception, e.g. a wrong flow or value that needs to be reported
|
||||
/// and looked into.
|
||||
|
||||
@@ -20,5 +20,6 @@ namespace Bit.Core.Abstractions
|
||||
string orgId);
|
||||
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
||||
int? GetPolicyInt(Policy policy, string key);
|
||||
Task<bool> ShouldShowVaultFilterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,5 +111,6 @@
|
||||
public const string EyeSlash = "\xe96d";
|
||||
public const string File = "\xe96e";
|
||||
public const string Paste = "\xe96f";
|
||||
public const string ViewCellMenu = "\xe5d3";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
if (defaultOptions)
|
||||
{
|
||||
Length = 14;
|
||||
Ambiguous = false;
|
||||
AllowAmbiguousChar = true;
|
||||
Number = true;
|
||||
MinNumber = 1;
|
||||
Uppercase = true;
|
||||
@@ -27,7 +27,7 @@
|
||||
}
|
||||
|
||||
public int? Length { get; set; }
|
||||
public bool? Ambiguous { get; set; }
|
||||
public bool? AllowAmbiguousChar { get; set; }
|
||||
public bool? Number { get; set; }
|
||||
public int? MinNumber { get; set; }
|
||||
public bool? Uppercase { get; set; }
|
||||
@@ -45,7 +45,7 @@
|
||||
public void Merge(PasswordGenerationOptions defaults)
|
||||
{
|
||||
Length = Length ?? defaults.Length;
|
||||
Ambiguous = Ambiguous ?? defaults.Ambiguous;
|
||||
AllowAmbiguousChar = AllowAmbiguousChar ?? defaults.AllowAmbiguousChar;
|
||||
Number = Number ?? defaults.Number;
|
||||
MinNumber = MinNumber ?? defaults.MinNumber;
|
||||
Uppercase = Uppercase ?? defaults.Uppercase;
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
{
|
||||
public string Email { get; set; }
|
||||
public string MasterPasswordHash { get; set; }
|
||||
public string DeviceIdentifier { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,6 +346,7 @@ namespace Bit.Core.Services
|
||||
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
|
||||
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
|
||||
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
|
||||
await _tokenService.ClearTwoFactorTokenAsync(email);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,9 +107,12 @@ namespace Bit.Core.Services
|
||||
return _decryptedFolderCache;
|
||||
}
|
||||
|
||||
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync()
|
||||
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null)
|
||||
{
|
||||
var folders = await GetAllDecryptedAsync();
|
||||
if (folders == null)
|
||||
{
|
||||
folders = await GetAllDecryptedAsync();
|
||||
}
|
||||
var nodes = new List<TreeNode<FolderView>>();
|
||||
foreach (var f in folders)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
@@ -45,6 +46,12 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
public void Exception(Exception ex) => Debug.WriteLine(ex);
|
||||
|
||||
public Task InitAsync() => Task.CompletedTask;
|
||||
|
||||
public Task<bool> IsEnabled() => Task.FromResult(true);
|
||||
|
||||
public Task SetEnabled(bool value) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5,13 +5,24 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AppCenter;
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class Logger : ILogger
|
||||
{
|
||||
private const string iOSAppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3";
|
||||
private const string DroidAppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
|
||||
|
||||
private string _userId;
|
||||
private string _appId;
|
||||
private bool _isInitialised = false;
|
||||
|
||||
static ILogger _instance;
|
||||
public static ILogger Instance
|
||||
{
|
||||
@@ -29,6 +40,60 @@ namespace Bit.Core.Services
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return JsonConvert.SerializeObject(new
|
||||
{
|
||||
AppId = _appId,
|
||||
UserId = _userId
|
||||
}, Formatting.Indented);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
if (_isInitialised)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var device = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService").GetDevice();
|
||||
_userId = await ServiceContainer.Resolve<IStateService>("stateService").GetActiveUserIdAsync();
|
||||
_appId = await ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync();
|
||||
|
||||
switch (device)
|
||||
{
|
||||
case Enums.DeviceType.Android:
|
||||
AppCenter.Start(DroidAppSecret, typeof(Crashes));
|
||||
break;
|
||||
case Enums.DeviceType.iOS:
|
||||
AppCenter.Start(iOSAppSecret, typeof(Crashes));
|
||||
break;
|
||||
default:
|
||||
throw new AppCenterException("Cannot start AppCenter. Device type is not configured.");
|
||||
|
||||
}
|
||||
|
||||
AppCenter.SetUserId(_userId);
|
||||
|
||||
Crashes.GetErrorAttachments = (ErrorReport report) =>
|
||||
{
|
||||
return new ErrorAttachmentLog[]
|
||||
{
|
||||
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
|
||||
};
|
||||
};
|
||||
|
||||
_isInitialised = true;
|
||||
}
|
||||
|
||||
public async Task<bool> IsEnabled() => await AppCenter.IsEnabledAsync();
|
||||
|
||||
public async Task SetEnabled(bool value) => await AppCenter.SetEnabledAsync(value);
|
||||
|
||||
public void Error(string message,
|
||||
IDictionary<string, string> extraData = null,
|
||||
[CallerMemberName] string memberName = "",
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
#if !FDROID
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
#endif
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@@ -25,8 +22,9 @@ namespace Bit.Core.Services
|
||||
#if !FDROID
|
||||
// just in case the caller throws the exception in a moment where the logger can't be resolved
|
||||
// we need to track the error as well
|
||||
Crashes.TrackError(ex);
|
||||
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
@@ -17,5 +18,11 @@ namespace Bit.Core.Services
|
||||
public void Exception(Exception ex)
|
||||
{
|
||||
}
|
||||
|
||||
public Task InitAsync() => Task.CompletedTask;
|
||||
|
||||
public Task<bool> IsEnabled() => Task.FromResult(false);
|
||||
|
||||
public Task SetEnabled(bool value) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Bit.Core.Services
|
||||
// Build out other character sets
|
||||
var allCharSet = string.Empty;
|
||||
var lowercaseCharSet = LowercaseCharSet;
|
||||
if (options.Ambiguous.GetValueOrDefault())
|
||||
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||
{
|
||||
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
|
||||
}
|
||||
@@ -103,7 +103,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
var uppercaseCharSet = UppercaseCharSet;
|
||||
if (options.Ambiguous.GetValueOrDefault())
|
||||
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||
{
|
||||
uppercaseCharSet = string.Concat(uppercaseCharSet, "IO");
|
||||
}
|
||||
@@ -113,7 +113,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
var numberCharSet = NumberCharSet;
|
||||
if (options.Ambiguous.GetValueOrDefault())
|
||||
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||
{
|
||||
numberCharSet = string.Concat(numberCharSet, "01");
|
||||
}
|
||||
|
||||
@@ -193,7 +193,8 @@ namespace Bit.Core.Services
|
||||
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
||||
}
|
||||
|
||||
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter, string userId = null)
|
||||
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null,
|
||||
string userId = null)
|
||||
{
|
||||
var policies = await GetAll(policyType, userId);
|
||||
if (policies == null)
|
||||
@@ -246,6 +247,13 @@ namespace Bit.Core.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||
{
|
||||
var organizations = await _organizationService.GetAllAsync();
|
||||
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
return (organizations?.Any() ?? false) && !personalOwnershipPolicyApplies;
|
||||
}
|
||||
|
||||
private bool? GetPolicyBool(Policy policy, string key)
|
||||
{
|
||||
if (policy.Data.ContainsKey(key))
|
||||
|
||||
@@ -74,28 +74,34 @@ namespace Bit.Core.Services
|
||||
CancellationToken ct = default, bool deleted = false)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var matchedCiphers = new List<CipherView>();
|
||||
var lowPriorityMatchedCiphers = new List<CipherView>();
|
||||
query = query.Trim().ToLower();
|
||||
return ciphers.Where(c =>
|
||||
|
||||
foreach (var c in ciphers)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (c.Name?.ToLower().Contains(query) ?? false)
|
||||
{
|
||||
return true;
|
||||
matchedCiphers.Add(c);
|
||||
}
|
||||
if (query.Length >= 8 && c.Id.StartsWith(query))
|
||||
else if (query.Length >= 8 && c.Id.StartsWith(query))
|
||||
{
|
||||
return true;
|
||||
lowPriorityMatchedCiphers.Add(c);
|
||||
}
|
||||
if (c.SubTitle?.ToLower().Contains(query) ?? false)
|
||||
else if (c.SubTitle?.ToLower().Contains(query) ?? false)
|
||||
{
|
||||
return true;
|
||||
lowPriorityMatchedCiphers.Add(c);
|
||||
}
|
||||
if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
|
||||
else if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
|
||||
{
|
||||
return true;
|
||||
lowPriorityMatchedCiphers.Add(c);
|
||||
}
|
||||
return false;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
matchedCiphers.AddRange(lowPriorityMatchedCiphers);
|
||||
return matchedCiphers;
|
||||
}
|
||||
|
||||
public async Task<List<SendView>> SearchSendsAsync(string query, Func<SendView, bool> filter = null,
|
||||
@@ -133,25 +139,31 @@ namespace Bit.Core.Services
|
||||
public List<SendView> SearchSendsBasic(List<SendView> sends, string query, CancellationToken ct = default,
|
||||
bool deleted = false)
|
||||
{
|
||||
var matchedSends = new List<SendView>();
|
||||
var lowPriorityMatchSends = new List<SendView>();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
query = query.Trim().ToLower();
|
||||
return sends.Where(s =>
|
||||
|
||||
foreach (var s in sends)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (s.Name?.ToLower().Contains(query) ?? false)
|
||||
{
|
||||
return true;
|
||||
matchedSends.Add(s);
|
||||
}
|
||||
if (s.Text?.Text?.ToLower().Contains(query) ?? false)
|
||||
else if (s.Text?.Text?.ToLower().Contains(query) ?? false)
|
||||
{
|
||||
return true;
|
||||
lowPriorityMatchSends.Add(s);
|
||||
}
|
||||
if (s.File?.FileName?.ToLower()?.Contains(query) ?? false)
|
||||
else if (s.File?.FileName?.ToLower()?.Contains(query) ?? false)
|
||||
{
|
||||
return true;
|
||||
lowPriorityMatchSends.Add(s);
|
||||
}
|
||||
return false;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
matchedSends.AddRange(lowPriorityMatchSends);
|
||||
return matchedSends;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1372,10 +1372,6 @@ namespace Bit.Core.Services
|
||||
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
||||
await SetEncryptedSendsAsync(null, userId);
|
||||
await SetSettingsAsync(null, userId);
|
||||
if (!string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
await SetTwoFactorTokenAsync(null, email);
|
||||
}
|
||||
|
||||
if (userInitiated)
|
||||
{
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Bit.UITests.Categories
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
public class SmokeTestAttribute : CategoryAttribute
|
||||
#pragma warning restore SA1649 // File name should match first type name
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.UITest;
|
||||
using Xamarin.UITest.Queries;
|
||||
|
||||
namespace Bit.UITests.Extensions
|
||||
{
|
||||
public static class IAppExtension
|
||||
{
|
||||
public static void Wait(this IApp app, float seconds)
|
||||
{
|
||||
var waitTime = DateTime.Now + TimeSpan.FromSeconds(seconds);
|
||||
|
||||
app.WaitFor(() => DateTime.Now > waitTime);
|
||||
}
|
||||
|
||||
public static void WaitAndTapElement(this IApp app, Func<AppQuery, AppQuery> elementQuery)
|
||||
{
|
||||
app.WaitForElement(elementQuery);
|
||||
app.Tap(elementQuery);
|
||||
}
|
||||
|
||||
public static void WaitAndTapElement(this IApp app, Func<AppQuery, AppWebQuery> elementQuery)
|
||||
{
|
||||
app.WaitForElement(elementQuery);
|
||||
app.Tap(elementQuery);
|
||||
}
|
||||
|
||||
public static void WaitAndScreenshot(this IApp app, string screenshotTitle)
|
||||
{
|
||||
app.Wait(1); //screenshots tend to be too fast and not capture the previous actions
|
||||
app.Screenshot(screenshotTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Xamarin.UITest;
|
||||
|
||||
namespace Bit.UITests.Helpers
|
||||
{
|
||||
public static class AppState
|
||||
{
|
||||
|
||||
public static void EnableScreenshots(this IApp app)
|
||||
{
|
||||
//TODO placeholder, mobile app needs the service / setting to enable Android screenshots first
|
||||
app.Invoke("Zamboni");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.UITest.Utils;
|
||||
|
||||
namespace Bit.UITests.Helpers
|
||||
{
|
||||
public class CustomWaitTimes : IWaitTimes
|
||||
{
|
||||
private readonly TimeSpan _timeout;
|
||||
public static readonly TimeSpan DefaultCustomTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
public CustomWaitTimes()
|
||||
{
|
||||
_timeout = DefaultCustomTimeout;
|
||||
}
|
||||
|
||||
public CustomWaitTimes(TimeSpan timeoutTimeSpan)
|
||||
{
|
||||
_timeout = timeoutTimeSpan;
|
||||
}
|
||||
|
||||
public TimeSpan GestureCompletionTimeout => _timeout;
|
||||
|
||||
public TimeSpan GestureWaitTimeout => _timeout;
|
||||
|
||||
public TimeSpan WaitForTimeout => _timeout;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using Bit.UITests.Extensions;
|
||||
using Bit.UITests.Setup;
|
||||
using Query = System.Func<Xamarin.UITest.Queries.AppQuery, Xamarin.UITest.Queries.AppQuery>;
|
||||
|
||||
namespace Bit.UITests.Pages.Accounts
|
||||
{
|
||||
public class EnvironmentPage : BasePage
|
||||
{
|
||||
private readonly Query _saveButton;
|
||||
private readonly Query _serverUrlInput;
|
||||
|
||||
public EnvironmentPage()
|
||||
: base()
|
||||
{
|
||||
if (OnAndroid)
|
||||
{
|
||||
_saveButton = x => x.Marked("save_button");
|
||||
_serverUrlInput = x => x.Marked("server_input");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (OniOS)
|
||||
{
|
||||
_saveButton = x => x.Marked("save_button");
|
||||
_serverUrlInput = x => x.Marked("server_input");
|
||||
}
|
||||
}
|
||||
|
||||
protected override PlatformQuery Trait => new PlatformQuery
|
||||
{
|
||||
Android = x => x.Marked("server_input"),
|
||||
iOS = x => x.Marked("server_input"),
|
||||
};
|
||||
|
||||
public EnvironmentPage TapSaveAndNavigate()
|
||||
{
|
||||
App.Tap(_saveButton);
|
||||
WaitForPageToLeave();
|
||||
return this;
|
||||
}
|
||||
|
||||
public EnvironmentPage InputServerUrl(string serverUrl)
|
||||
{
|
||||
App.ClearText(_serverUrlInput);
|
||||
App.EnterText(_serverUrlInput, serverUrl);
|
||||
App.DismissKeyboard();
|
||||
App.WaitAndScreenshot("After inserting the server url, I can see the field filled");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using Bit.UITests.Setup;
|
||||
using Query = System.Func<Xamarin.UITest.Queries.AppQuery, Xamarin.UITest.Queries.AppQuery>;
|
||||
|
||||
namespace Bit.UITests.Pages.Accounts
|
||||
{
|
||||
public class HomePage : BasePage
|
||||
{
|
||||
private readonly Query _loginButton;
|
||||
private readonly Query _environmentButton;
|
||||
|
||||
public HomePage()
|
||||
: base()
|
||||
{
|
||||
if (OnAndroid)
|
||||
{
|
||||
_loginButton = x => x.Marked("homepage_login_button");
|
||||
|
||||
//TODO a11y uses the same fields as the UI tests and we're prioritising that
|
||||
// improve this by getting the app runtime locale and use the i18n service here instead
|
||||
_environmentButton = x => x.Marked("Options");
|
||||
//_environmentButton = x => x.Marked("environment_button");
|
||||
return;
|
||||
}
|
||||
|
||||
if (OniOS)
|
||||
{
|
||||
_loginButton = x => x.Marked("homepage_login_button");
|
||||
_environmentButton = x => x.Marked("Options");
|
||||
}
|
||||
}
|
||||
|
||||
protected override PlatformQuery Trait => new PlatformQuery
|
||||
{
|
||||
Android = x => x.Marked("logo_image"),
|
||||
iOS = x => x.Marked("logo_image"),
|
||||
};
|
||||
|
||||
public HomePage TapLoginAndNavigate()
|
||||
{
|
||||
App.Tap(_loginButton);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HomePage TapEnvironmentAndNavigate()
|
||||
{
|
||||
App.Tap(_environmentButton);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using System;
|
||||
using Bit.UITests.Extensions;
|
||||
using Bit.UITests.Setup;
|
||||
using Query = System.Func<Xamarin.UITest.Queries.AppQuery, Xamarin.UITest.Queries.AppQuery>;
|
||||
|
||||
namespace Bit.UITests.Pages.Accounts
|
||||
{
|
||||
public class LoginPage : BasePage
|
||||
{
|
||||
private readonly Query _loginButton;
|
||||
private readonly Query _cancelButton;
|
||||
private readonly Query _passwordVisibilityToggle;
|
||||
|
||||
private readonly Query _emailInput;
|
||||
private readonly Query _passwordInput;
|
||||
|
||||
|
||||
public LoginPage()
|
||||
: base()
|
||||
{
|
||||
if (OnAndroid)
|
||||
{
|
||||
_loginButton = x => x.Marked("loginpage_login_button");
|
||||
_cancelButton = x => x.Marked("cancel_button");
|
||||
|
||||
//TODO a11y uses the same fields as the UI tests and we're prioritising that
|
||||
// improve this by getting the app runtime locale and use the i18n service here instead
|
||||
_passwordVisibilityToggle = x => x.Marked("Toggle Visibility");
|
||||
|
||||
_emailInput = x => x.Marked("email_input");
|
||||
_passwordInput = x => x.Marked("password_input");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (OniOS)
|
||||
{
|
||||
_loginButton = x => x.Marked("loginpage_login_button");
|
||||
_cancelButton = x => x.Marked("cancel_button");
|
||||
_passwordVisibilityToggle = x => x.Marked("Toggle Visibility");
|
||||
|
||||
_emailInput = x => x.Marked("email_input");
|
||||
_passwordInput = x => x.Marked("password_input");
|
||||
}
|
||||
}
|
||||
|
||||
protected override PlatformQuery Trait => new PlatformQuery
|
||||
{
|
||||
Android = x => x.Marked("email_input"),
|
||||
iOS = x => x.Marked("email_input"),
|
||||
};
|
||||
|
||||
public LoginPage TapLoginAndNavigate()
|
||||
{
|
||||
App.Tap(_loginButton);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoginPage TapCancelAndNavigate()
|
||||
{
|
||||
App.Tap(_cancelButton);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoginPage TapPasswordVisibilityToggle()
|
||||
{
|
||||
App.Tap(_passwordVisibilityToggle);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoginPage InputEmail(string email)
|
||||
{
|
||||
App.ClearText(_emailInput);
|
||||
App.EnterText(_emailInput, email);
|
||||
App.DismissKeyboard();
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoginPage InputPassword(string password)
|
||||
{
|
||||
App.Tap(_passwordInput);
|
||||
App.EnterText(password);
|
||||
App.DismissKeyboard();
|
||||
App.WaitAndScreenshot("After I input the email and password fields, I can see both fields filled");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using Bit.UITests.Extensions;
|
||||
using Bit.UITests.Setup;
|
||||
using Query = System.Func<Xamarin.UITest.Queries.AppQuery, Xamarin.UITest.Queries.AppQuery>;
|
||||
|
||||
namespace Bit.UITests.Pages
|
||||
{
|
||||
public class ExamplePage : BasePage
|
||||
{
|
||||
private readonly Query _loginButton;
|
||||
private readonly Query _passwordInput;
|
||||
|
||||
public ExamplePage()
|
||||
: base()
|
||||
{
|
||||
if (OnAndroid)
|
||||
{
|
||||
_loginButton = x => x.Marked("loginpage_login_button");
|
||||
_passwordInput = x => x.Marked("password_input");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (OniOS)
|
||||
{
|
||||
_loginButton = x => x.Marked("loginpage_login_button");
|
||||
_passwordInput = x => x.Marked("password_input");
|
||||
}
|
||||
}
|
||||
|
||||
protected override PlatformQuery Trait => new PlatformQuery
|
||||
{
|
||||
Android = x => x.Marked("password_input"),
|
||||
iOS = x => x.Marked("password_input"),
|
||||
};
|
||||
|
||||
public ExamplePage TapLogin()
|
||||
{
|
||||
App.Tap(_loginButton);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExamplePage InputPassword(string password)
|
||||
{
|
||||
App.Tap(_passwordInput);
|
||||
App.EnterText(password);
|
||||
App.DismissKeyboard();
|
||||
|
||||
App.WaitAndScreenshot("After I input the email and password fields, I can see both fields filled");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using Bit.UITests.Extensions;
|
||||
using Bit.UITests.Helpers;
|
||||
using Bit.UITests.Setup;
|
||||
using Query = System.Func<Xamarin.UITest.Queries.AppQuery, Xamarin.UITest.Queries.AppQuery>;
|
||||
|
||||
namespace Bit.UITests.Pages
|
||||
{
|
||||
public class TabsPage : BasePage
|
||||
{
|
||||
private readonly Query _vaultTab;
|
||||
private readonly Query _sendTab;
|
||||
private readonly Query _accountSwitchingAvatar;
|
||||
private readonly Query _accountSwitchingAddAccount;
|
||||
|
||||
|
||||
public TabsPage()
|
||||
: base()
|
||||
{
|
||||
_vaultTab = x => x.Marked("My Vault");
|
||||
_sendTab = x => x.Marked("Send");
|
||||
_accountSwitchingAvatar = x => x.Marked("Account");
|
||||
_accountSwitchingAddAccount = x => x.Marked("Add Account");
|
||||
|
||||
WaitForNoLoader();
|
||||
}
|
||||
|
||||
protected override PlatformQuery Trait => new PlatformQuery
|
||||
{
|
||||
Android = x => x.Marked("Send"),
|
||||
iOS = x => x.Marked("Send"),
|
||||
};
|
||||
|
||||
public TabsPage WaitForNoLoader()
|
||||
{
|
||||
App.WaitForNoElement(LoadingIndicator, timeout: CustomWaitTimes.DefaultCustomTimeout);
|
||||
App.WaitAndScreenshot("Page finished loading");
|
||||
return this;
|
||||
}
|
||||
|
||||
public TabsPage TapAccountSwitchingAvatar()
|
||||
{
|
||||
App.WaitForElement(_accountSwitchingAvatar);
|
||||
App.Tap(_accountSwitchingAvatar);
|
||||
App.WaitAndScreenshot("Tapping the avatar, I can see the account switching panel");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TabsPage TapAccountSwitchingAddAccount()
|
||||
{
|
||||
App.Tap(_accountSwitchingAddAccount);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TabsPage TapVaultTab()
|
||||
{
|
||||
App.Tap(_vaultTab);
|
||||
App.WaitAndScreenshot("Tapping the Vault tab, I can see the Vault view");
|
||||
return this;
|
||||
}
|
||||
|
||||
public TabsPage TapSTab()
|
||||
{
|
||||
App.Tap(_sendTab);
|
||||
App.WaitAndScreenshot("Tapping the Send tab, I can see the Send view");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Bit.UITests.Helpers;
|
||||
using Bit.UITests.Setup.SimulatorManager;
|
||||
using Xamarin.UITest;
|
||||
|
||||
namespace Bit.UITests.Setup
|
||||
{
|
||||
internal static class AppManager
|
||||
{
|
||||
private static readonly string _slnPath = GetSlnPath();
|
||||
|
||||
private const string AndroidPackageName = "com.x8bit.bitwarden";
|
||||
private const string IosBundleId = "com.x8bit.bitwarden";
|
||||
private static readonly string _apkPath = Path.Combine(_slnPath, "Android", "bin", "release", $"{AndroidPackageName}-Signed.apk");
|
||||
private static readonly string _iosPath = Path.Combine("..", "..", "..", $"{IosBundleId}.app");
|
||||
|
||||
private static IApp _app;
|
||||
|
||||
private static Platform? _platform;
|
||||
|
||||
public static IApp App
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_app == null)
|
||||
{
|
||||
throw new NullReferenceException("'AppManager.App' not set. Call 'AppManager.StartApp()' before trying to access it.");
|
||||
}
|
||||
|
||||
return _app;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Platform Platform
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_platform == null)
|
||||
{
|
||||
throw new NullReferenceException("'AppManager.Platform' not set.");
|
||||
}
|
||||
|
||||
return _platform.Value;
|
||||
}
|
||||
|
||||
set => _platform = value;
|
||||
}
|
||||
|
||||
public static void StartApp()
|
||||
{
|
||||
Console.WriteLine($"TestEnvironment.IsTestCloud: {TestEnvironment.IsTestCloud}");
|
||||
Console.WriteLine($"TestEnvironment.Platform: {TestEnvironment.Platform}");
|
||||
Console.WriteLine($"Platform: {Platform}");
|
||||
|
||||
switch (Platform, TestEnvironment.IsTestCloud)
|
||||
{
|
||||
case (Platform.Android, false):
|
||||
_app = ConfigureApp
|
||||
.Android
|
||||
.InstalledApp(AndroidPackageName)
|
||||
//.ApkFile(_apkPath)
|
||||
.StartApp();
|
||||
break;
|
||||
case (Platform.iOS, false):
|
||||
_app = ConfigureApp
|
||||
.iOS
|
||||
.SetDeviceByName("iPhone X") //NOTE Get Devices name in terminal: xcrun instruments -s devices
|
||||
.AppBundle(_iosPath)
|
||||
// .InstalledApp(IosBundleId)
|
||||
.StartApp();
|
||||
break;
|
||||
case (Platform.Android, true):
|
||||
_app = ConfigureApp.Android.WaitTimes(new CustomWaitTimes()).StartApp();
|
||||
break;
|
||||
case (Platform.iOS, true):
|
||||
_app = ConfigureApp.iOS.WaitTimes(new CustomWaitTimes()).StartApp();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static string GetSlnPath()
|
||||
{
|
||||
string currentFile = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath;
|
||||
var fi = new FileInfo(currentFile);
|
||||
string path = fi.Directory!.Parent!.Parent!.Parent!.FullName;
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using Bit.UITests.Extensions;
|
||||
using Bit.UITests.Helpers;
|
||||
using NUnit.Framework;
|
||||
using Xamarin.UITest;
|
||||
using Query = System.Func<Xamarin.UITest.Queries.AppQuery, Xamarin.UITest.Queries.AppQuery>;
|
||||
|
||||
namespace Bit.UITests.Setup
|
||||
{
|
||||
public abstract class BasePage
|
||||
{
|
||||
|
||||
protected BasePage()
|
||||
{
|
||||
AssertOnPage(CustomWaitTimes.DefaultCustomTimeout);
|
||||
App.Screenshot("On " + GetType().Name);
|
||||
}
|
||||
|
||||
protected readonly Query LoadingIndicator = x => x.Marked("activity_indicator");
|
||||
|
||||
protected IApp App => AppManager.App;
|
||||
|
||||
protected bool OnAndroid => AppManager.Platform == Platform.Android;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
protected bool OniOS => AppManager.Platform == Platform.iOS;
|
||||
|
||||
protected abstract PlatformQuery Trait { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the trait is still present. Defaults to no wait.
|
||||
/// </summary>
|
||||
/// <param name="timeout">Time to wait before the assertion fails</param>
|
||||
public void AssertOnPage(TimeSpan? timeout = default(TimeSpan?))
|
||||
{
|
||||
var message = "Unable to verify on page: " + GetType().Name;
|
||||
|
||||
if (timeout == null)
|
||||
{
|
||||
Assert.IsNotEmpty(App.Query(Trait.Current), message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.DoesNotThrow(() => App.WaitForElement(Trait.Current, timeout: timeout), message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the trait is no longer present. Defaults to a 5 second wait.
|
||||
/// </summary>
|
||||
/// <param name="timeout">Time to wait before the assertion fails</param>
|
||||
public void WaitForPageToLeave(TimeSpan? timeout = default(TimeSpan?))
|
||||
{
|
||||
timeout ??= TimeSpan.FromSeconds(5);
|
||||
var message = "Unable to verify *not* on page: " + GetType().Name;
|
||||
|
||||
Assert.DoesNotThrow(() => App.WaitForNoElement(Trait.Current, timeout: timeout), message);
|
||||
}
|
||||
|
||||
|
||||
public BasePage Wait(int seconds)
|
||||
{
|
||||
App.Wait(seconds);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BasePage Back()
|
||||
{
|
||||
App.Back();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using Bit.UITests.Helpers;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using Xamarin.UITest;
|
||||
|
||||
namespace Bit.UITests.Setup
|
||||
{
|
||||
|
||||
[TestFixture(Platform.Android)]
|
||||
[TestFixture(Platform.iOS)]
|
||||
public abstract class BaseTestFixture
|
||||
{
|
||||
protected IApp App => AppManager.App;
|
||||
|
||||
protected bool OnAndroid => AppManager.Platform == Platform.Android;
|
||||
|
||||
protected bool OniOS => AppManager.Platform == Platform.iOS;
|
||||
|
||||
protected BaseTestFixture(Platform platform)
|
||||
{
|
||||
AppManager.Platform = platform;
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public virtual void BeforeEachTest()
|
||||
{
|
||||
AppManager.StartApp();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (TestContext.CurrentContext.Result.Outcome.Status != TestStatus.Failed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (App == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TestEnvironment.Platform != TestPlatform.Local)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE uncomment to help debug failing tests
|
||||
//App.Repl();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// NOTE C# 9.0 support
|
||||
// https://stackoverflow.com/a/64749403/859738
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
public class IsExternalInit { }
|
||||
#pragma warning restore SA1649 // File name should match first type name
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using Xamarin.UITest;
|
||||
using Xamarin.UITest.Queries;
|
||||
|
||||
namespace Bit.UITests.Setup
|
||||
{
|
||||
public class PlatformQuery
|
||||
{
|
||||
Func<AppQuery, AppQuery> _current;
|
||||
public Func<AppQuery, AppQuery> Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_current == null)
|
||||
throw new NullReferenceException("Trait not set for current platform");
|
||||
|
||||
return _current;
|
||||
}
|
||||
}
|
||||
|
||||
public Func<AppQuery, AppQuery> Android
|
||||
{
|
||||
set
|
||||
{
|
||||
if (AppManager.Platform == Platform.Android)
|
||||
_current = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Func<AppQuery, AppQuery> iOS
|
||||
{
|
||||
set
|
||||
{
|
||||
if (AppManager.Platform == Platform.iOS)
|
||||
_current = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Bit.UITests.Setup.SimulatorManager
|
||||
{
|
||||
class InstrumentsRunner
|
||||
{
|
||||
static string[] GetInstrumentsOutput()
|
||||
{
|
||||
const string cmd = "/usr/bin/xcrun";
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = cmd,
|
||||
Arguments = "instruments -s devices",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
var proc = new Process();
|
||||
proc.StartInfo = startInfo;
|
||||
proc.Start();
|
||||
var result = proc.StandardOutput.ReadToEnd();
|
||||
proc.WaitForExit();
|
||||
|
||||
var lines = result.Split('\n');
|
||||
return lines;
|
||||
}
|
||||
|
||||
public Simulator[] GetListOfSimulators()
|
||||
{
|
||||
var simulators = new List<Simulator>();
|
||||
var lines = GetInstrumentsOutput();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var sim = new Simulator(line);
|
||||
if (sim.IsValid())
|
||||
{
|
||||
simulators.Add(sim);
|
||||
}
|
||||
}
|
||||
|
||||
return simulators.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xamarin.UITest;
|
||||
using Xamarin.UITest.Configuration;
|
||||
|
||||
namespace Bit.UITests.Setup.SimulatorManager
|
||||
{
|
||||
internal static class IosSimulatorsManager
|
||||
{
|
||||
|
||||
public static iOSAppConfigurator SetDeviceByName(this iOSAppConfigurator configurator, string simulatorName)
|
||||
{
|
||||
var deviceId = GetDeviceId(simulatorName);
|
||||
return configurator.DeviceIdentifier(deviceId);
|
||||
}
|
||||
|
||||
public static string GetDeviceId(string simulatorName)
|
||||
{
|
||||
if (!TestEnvironment.Platform.Equals(TestPlatform.Local))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// See below for the InstrumentsRunner class.
|
||||
IEnumerable<Simulator> simulators = new InstrumentsRunner().GetListOfSimulators();
|
||||
|
||||
var simulator = simulators.FirstOrDefault(x => x.Name.Contains(simulatorName));
|
||||
|
||||
if (simulator == null)
|
||||
{
|
||||
throw new ArgumentException("Could not find a device identifier for '" + simulatorName + "'.", "simulatorName");
|
||||
}
|
||||
|
||||
return simulator.GUID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
namespace Bit.UITests.Setup.SimulatorManager
|
||||
{
|
||||
internal class Simulator
|
||||
{
|
||||
public Simulator(string line)
|
||||
{
|
||||
ParseLine(line);
|
||||
}
|
||||
|
||||
public string Line { get; private set; }
|
||||
|
||||
public string GUID { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(GUID) && !(string.IsNullOrWhiteSpace(Name));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Line;
|
||||
}
|
||||
|
||||
void ParseLine(string line)
|
||||
{
|
||||
GUID = string.Empty;
|
||||
Name = string.Empty;
|
||||
Line = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Line = line.Trim();
|
||||
var idx1 = line.IndexOf(" [");
|
||||
|
||||
if (idx1 < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Name = Line.Substring(0, idx1).Trim();
|
||||
GUID = Line.Substring(idx1 + 2, 36).Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using Bit.UITests.Categories;
|
||||
using Bit.UITests.Pages;
|
||||
using Bit.UITests.Pages.Accounts;
|
||||
using Bit.UITests.Setup;
|
||||
using NUnit.Framework;
|
||||
using Xamarin.UITest;
|
||||
|
||||
namespace Bit.UiTests.Tests
|
||||
{
|
||||
public class LoginTests : BaseTestFixture
|
||||
{
|
||||
record AccountCredentials(string ServerUrl, string Email, string Password);
|
||||
|
||||
private AccountCredentials[] _accounts =
|
||||
{
|
||||
new ("", "", ""),
|
||||
new ("", "", ""),
|
||||
new ("", "", ""),
|
||||
};
|
||||
|
||||
public LoginTests(Platform platform)
|
||||
: base(platform)
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
[SmokeTest]
|
||||
public void WaitForAppToLoad()
|
||||
{
|
||||
new HomePage();
|
||||
|
||||
App.Screenshot("App loaded with success!");
|
||||
}
|
||||
|
||||
//[Test]
|
||||
public void LoginWithSuccess()
|
||||
{
|
||||
Login(_accounts[0]);
|
||||
new TabsPage();
|
||||
App.Repl();
|
||||
|
||||
App.Screenshot("After logging in with success, I can see the vault");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AccountSwitchWithSuccess()
|
||||
{
|
||||
Login(_accounts[0], true);
|
||||
new TabsPage()
|
||||
.TapAccountSwitchingAvatar()
|
||||
.TapAccountSwitchingAddAccount();
|
||||
|
||||
Login(_accounts[1]);
|
||||
new TabsPage()
|
||||
.TapAccountSwitchingAvatar()
|
||||
.TapAccountSwitchingAddAccount();
|
||||
|
||||
Login(_accounts[2]);
|
||||
|
||||
new TabsPage()
|
||||
.TapAccountSwitchingAvatar();
|
||||
}
|
||||
|
||||
private void Login(AccountCredentials account, bool changeEnvironment = false)
|
||||
{
|
||||
if (changeEnvironment)
|
||||
{
|
||||
new HomePage()
|
||||
.TapEnvironmentAndNavigate();
|
||||
new EnvironmentPage()
|
||||
.InputServerUrl(account.ServerUrl)
|
||||
.TapSaveAndNavigate();
|
||||
}
|
||||
|
||||
new HomePage()
|
||||
.TapLoginAndNavigate();
|
||||
|
||||
new LoginPage()
|
||||
.InputEmail(account.Email)
|
||||
.InputPassword(account.Password)
|
||||
.TapLoginAndNavigate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{23FB637B-1705-485F-9464-078FCAF361A8}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>Bit.UiTests</RootNamespace>
|
||||
<AssemblyName>UiTests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile />
|
||||
<LangVersion>9.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Xamarin.UITest" Version="3.2.6" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Categories\SmokeTest.cs" />
|
||||
<Compile Include="Extensions\IAppExtension.cs" />
|
||||
<Compile Include="Setup\SimulatorManager\InstrumentsRunner.cs" />
|
||||
<Compile Include="Setup\SimulatorManager\IosSimulatorsManager.cs" />
|
||||
<Compile Include="Setup\SimulatorManager\Simulator.cs" />
|
||||
<Compile Include="Setup\AppManager.cs" />
|
||||
<Compile Include="Setup\BasePage.cs" />
|
||||
<Compile Include="Setup\BaseTestFixture.cs" />
|
||||
<Compile Include="Setup\Csharp9Support.cs" />
|
||||
<Compile Include="Setup\PlatformQuery.cs" />
|
||||
<Compile Include="Helpers\CustomWaitTimes.cs" />
|
||||
<Compile Include="Helpers\AppState.cs" />
|
||||
<Compile Include="Tests\LoginTests.cs" />
|
||||
<Compile Include="Pages\Accounts\EnvironmentPage.cs" />
|
||||
<Compile Include="Pages\Accounts\HomePage.cs" />
|
||||
<Compile Include="Pages\Accounts\LoginPage.cs" />
|
||||
<Compile Include="Pages\TabsPage.cs" />
|
||||
<Compile Include="Pages\ExamplePage.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -20,7 +20,6 @@ namespace Bit.iOS.Autofill
|
||||
public partial class CredentialProviderViewController : ASCredentialProviderViewController
|
||||
{
|
||||
private Context _context;
|
||||
private bool _initedAppCenter;
|
||||
private NFCNdefReaderSession _nfcSession = null;
|
||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||
|
||||
@@ -330,11 +329,7 @@ namespace Bit.iOS.Autofill
|
||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent,
|
||||
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
||||
if (!_initedAppCenter)
|
||||
{
|
||||
iOSCoreHelpers.RegisterAppCenter();
|
||||
_initedAppCenter = true;
|
||||
}
|
||||
iOSCoreHelpers.InitLogger();
|
||||
iOSCoreHelpers.Bootstrap();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.8bit.bitwarden.autofill</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.18.1</string>
|
||||
<string>2022.05.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
MinNumbersCell.Value = options.MinNumber.GetValueOrDefault(1);
|
||||
MinSpecialCell.Value = options.MinSpecial.GetValueOrDefault(1);
|
||||
LengthCell.Value = options.Length.GetValueOrDefault(14);
|
||||
AmbiguousCell.Switch.On = options.Ambiguous.GetValueOrDefault();
|
||||
AmbiguousCell.Switch.On = !options.AllowAmbiguousChar.GetValueOrDefault();
|
||||
|
||||
NumWordsCell.Value = options.NumWords.GetValueOrDefault(3);
|
||||
WordSeparatorCell.TextField.Text = options.WordSeparator ?? "";
|
||||
@@ -219,7 +219,7 @@ namespace Bit.iOS.Core.Controllers
|
||||
Special = SpecialCell.Switch.On,
|
||||
MinSpecial = MinSpecialCell.Value,
|
||||
MinNumber = MinNumbersCell.Value,
|
||||
Ambiguous = AmbiguousCell.Switch.On,
|
||||
AllowAmbiguousChar = !AmbiguousCell.Switch.On,
|
||||
NumWords = NumWordsCell.Value,
|
||||
WordSeparator = WordSeparatorCell.TextField.Text,
|
||||
Capitalize = CapitalizeCell.Switch.On,
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
using Bit.Core.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AppCenter;
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
public class AppCenterHelper
|
||||
{
|
||||
private const string AppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3";
|
||||
|
||||
private readonly IAppIdService _appIdService;
|
||||
private readonly IStateService _stateService;
|
||||
|
||||
private string _userId;
|
||||
private string _appId;
|
||||
|
||||
public AppCenterHelper(
|
||||
IAppIdService appIdService,
|
||||
IStateService stateService)
|
||||
{
|
||||
_appIdService = appIdService;
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
_userId = await _stateService.GetActiveUserIdAsync();
|
||||
_appId = await _appIdService.GetAppIdAsync();
|
||||
|
||||
AppCenter.Start(AppSecret, typeof(Crashes));
|
||||
AppCenter.SetUserId(_userId);
|
||||
|
||||
Crashes.GetErrorAttachments = (ErrorReport report) =>
|
||||
{
|
||||
return new ErrorAttachmentLog[]
|
||||
{
|
||||
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return JsonConvert.SerializeObject(new
|
||||
{
|
||||
AppId = _appId,
|
||||
UserId = _userId
|
||||
}, Formatting.Indented);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,9 @@ namespace Bit.iOS.Core.Utilities
|
||||
public static string AppGroupId = "group.com.8bit.bitwarden";
|
||||
public static string AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden";
|
||||
|
||||
public static void RegisterAppCenter()
|
||||
public static void InitLogger()
|
||||
{
|
||||
#if !DEBUG
|
||||
var appCenterHelper = new AppCenterHelper(
|
||||
ServiceContainer.Resolve<IAppIdService>("appIdService"),
|
||||
ServiceContainer.Resolve<IStateService>("stateService"));
|
||||
var appCenterTask = appCenterHelper.InitAsync();
|
||||
#endif
|
||||
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||
}
|
||||
|
||||
public static void RegisterLocalServices()
|
||||
|
||||
@@ -172,7 +172,6 @@
|
||||
<Compile Include="Services\DeviceActionService.cs" />
|
||||
<Compile Include="Utilities\ASHelpers.cs" />
|
||||
<Compile Include="Utilities\Dialogs.cs" />
|
||||
<Compile Include="Utilities\AppCenterHelper.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\CryptoPrimitiveService.cs" />
|
||||
<Compile Include="Services\KeyChainStorageService.cs" />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.18.1</string>
|
||||
<string>2022.05.1</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace Bit.iOS.Extension
|
||||
public partial class LoadingViewController : ExtendedUIViewController
|
||||
{
|
||||
private Context _context = new Context();
|
||||
private bool _initedAppCenter;
|
||||
private NFCNdefReaderSession _nfcSession = null;
|
||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||
|
||||
@@ -408,11 +407,7 @@ namespace Bit.iOS.Extension
|
||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent,
|
||||
Bit.Core.Constants.iOSExtensionClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
||||
if (!_initedAppCenter)
|
||||
{
|
||||
iOSCoreHelpers.RegisterAppCenter();
|
||||
_initedAppCenter = true;
|
||||
}
|
||||
iOSCoreHelpers.InitLogger();
|
||||
iOSCoreHelpers.Bootstrap();
|
||||
var app = new App.App(new AppOptions { IosExtension = true });
|
||||
ThemeManager.SetTheme(app.Resources);
|
||||
|
||||
@@ -9,6 +9,8 @@ using MobileCoreServices;
|
||||
using Bit.iOS.Core.Controllers;
|
||||
using Bit.App.Resources;
|
||||
using Bit.iOS.Core.Views;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.iOS.Extension
|
||||
{
|
||||
@@ -18,10 +20,12 @@ namespace Bit.iOS.Extension
|
||||
: base(handle)
|
||||
{
|
||||
DismissModalAction = Cancel;
|
||||
PasswordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||
}
|
||||
|
||||
public Context Context { get; set; }
|
||||
public LoadingViewController LoadingController { get; set; }
|
||||
public IPasswordRepromptService PasswordRepromptService { get; private set; }
|
||||
|
||||
public async override void ViewDidLoad()
|
||||
{
|
||||
@@ -104,7 +108,7 @@ namespace Bit.iOS.Extension
|
||||
_controller = controller;
|
||||
}
|
||||
|
||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||
public async override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
tableView.DeselectRow(indexPath, true);
|
||||
tableView.EndEditing(true);
|
||||
@@ -122,6 +126,11 @@ namespace Bit.iOS.Extension
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.Reprompt != Bit.Core.Enums.CipherRepromptType.None && !await _controller.PasswordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_controller.CanAutoFill() && !string.IsNullOrWhiteSpace(item.Password))
|
||||
{
|
||||
string totp = null;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.18.1</string>
|
||||
<string>2022.05.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user