mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
74 Commits
beeep/envi
...
feature/pm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c25906206e | ||
|
|
dfc7c55b77 | ||
|
|
080aabfe82 | ||
|
|
c0688c584e | ||
|
|
c09672ff88 | ||
|
|
635b6bc184 | ||
|
|
da7a1964ef | ||
|
|
73b8d8e6b8 | ||
|
|
c61f9f0357 | ||
|
|
a3183857b9 | ||
|
|
bedbca841d | ||
|
|
0ff314f076 | ||
|
|
c9a7c29190 | ||
|
|
546bf8dcb1 | ||
|
|
7fdc5597fc | ||
|
|
7c664f58b3 | ||
|
|
bdfe806846 | ||
|
|
5ed567ab90 | ||
|
|
cd4f44e6f6 | ||
|
|
d58f0b281b | ||
|
|
5ba3fac0c0 | ||
|
|
1e30524985 | ||
|
|
515decb4c9 | ||
|
|
bf28d373e9 | ||
|
|
69d38d4d75 | ||
|
|
c1619536aa | ||
|
|
079e02e4e5 | ||
|
|
15d3da607b | ||
|
|
b5cf9fd79d | ||
|
|
dd52ff0dcc | ||
|
|
c678c17ebc | ||
|
|
cd9e49b13b | ||
|
|
6d7970f767 | ||
|
|
9adc4d3080 | ||
|
|
1f20f70d13 | ||
|
|
a25da68437 | ||
|
|
fdc0313d10 | ||
|
|
b688b85d0f | ||
|
|
a5df6c0c65 | ||
|
|
f31c87b52e | ||
|
|
c2d4fa4429 | ||
|
|
1e79e1182f | ||
|
|
548bd12a8e | ||
|
|
58542fd255 | ||
|
|
800b4c71de | ||
|
|
11947ce99a | ||
|
|
3053eaa036 | ||
|
|
4abb472998 | ||
|
|
6268f0776b | ||
|
|
cbbc41be67 | ||
|
|
1d541e5b8e | ||
|
|
175b9936b6 | ||
|
|
72e67bd6f2 | ||
|
|
e164fb9823 | ||
|
|
87866304a6 | ||
|
|
216c6abcf6 | ||
|
|
1014563c75 | ||
|
|
3506269811 | ||
|
|
31487a31bb | ||
|
|
1407aa5655 | ||
|
|
16f59e2698 | ||
|
|
d876b54f45 | ||
|
|
6644e3b449 | ||
|
|
8d98d1d5bd | ||
|
|
3e9711f8f2 | ||
|
|
3af37f01d3 | ||
|
|
43d2d386b1 | ||
|
|
bc5c11b47f | ||
|
|
52843b4181 | ||
|
|
98705e443f | ||
|
|
1332ef7b43 | ||
|
|
04e30c2146 | ||
|
|
f604da13a1 | ||
|
|
84a82f0876 |
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -71,6 +71,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
nuget-version: 5.9.0
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
|
- name: Set up .NET
|
||||||
|
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '3.1.x'
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
|
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
|
||||||
|
|
||||||
@@ -793,7 +798,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Upload Sources
|
- name: Upload Sources
|
||||||
uses: crowdin/github-action@ecd7eb0ef6f3cfa16293c79e9cbc4bc5b5fd9c49 # v1.4.9
|
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||||
|
|||||||
2
.github/workflows/crowdin-pull.yml
vendored
2
.github/workflows/crowdin-pull.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: crowdin/github-action@ecd7eb0ef6f3cfa16293c79e9cbc4bc5b5fd9c49 # v1.4.9
|
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||||
|
|||||||
2
.github/workflows/enforce-labels.yml
vendored
2
.github/workflows/enforce-labels.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Enforce Label
|
- name: Enforce Label
|
||||||
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # v2.2.2
|
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2
|
||||||
with:
|
with:
|
||||||
BANNED_LABELS: "hold,needs-qa"
|
BANNED_LABELS: "hold,needs-qa"
|
||||||
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged"
|
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged"
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Dry Run - Download all artifacts
|
- name: Dry Run - Download all artifacts
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@@ -130,7 +130,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download F-Droid .apk artifact
|
- name: Download F-Droid .apk artifact
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@@ -139,7 +139,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Dry Run - Download F-Droid .apk artifact
|
- name: Dry Run - Download F-Droid .apk artifact
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
|
|||||||
18
.github/workflows/version-auto-bump.yml
vendored
18
.github/workflows/version-auto-bump.yml
vendored
@@ -32,14 +32,10 @@ jobs:
|
|||||||
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
trigger_version_bump:
|
trigger_version_bump:
|
||||||
name: "Version bump"
|
name: Bump version to ${{ needs.setup.outputs.version_number }}
|
||||||
runs-on: ubuntu-22.04
|
needs: setup
|
||||||
needs:
|
uses: ./.github/workflows/version-bump.yml
|
||||||
- setup
|
secrets:
|
||||||
steps:
|
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
- name: Bump version to ${{ needs.setup.outputs.version_number }}
|
with:
|
||||||
uses: ./.github/workflows/version-bump.yml
|
version_number: ${{ needs.setup.outputs.version_number }}
|
||||||
secrets:
|
|
||||||
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
|
||||||
with:
|
|
||||||
version_number: ${{ needs.setup.outputs.version_number }}
|
|
||||||
|
|||||||
12
crowdin.yml
12
crowdin.yml
@@ -38,3 +38,15 @@ files:
|
|||||||
pt-PT: pt-PT
|
pt-PT: pt-PT
|
||||||
en-GB: en-GB
|
en-GB: en-GB
|
||||||
en-IN: en-IN
|
en-IN: en-IN
|
||||||
|
- source: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/Localizable.strings"
|
||||||
|
dest: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/%original_file_name%"
|
||||||
|
translation: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization//%two_letters_code%.lproj/%original_file_name%"
|
||||||
|
update_option: update_as_unapproved
|
||||||
|
languages_mapping:
|
||||||
|
two_letters_code:
|
||||||
|
zh-CN: zh-Hans
|
||||||
|
zh-TW: zh-Hant
|
||||||
|
pt-BR: pt-BR
|
||||||
|
pt-PT: pt-PT
|
||||||
|
en-GB: en-GB
|
||||||
|
en-IN: en-IN
|
||||||
|
|||||||
@@ -159,6 +159,7 @@
|
|||||||
<Compile Include="Constants.cs" />
|
<Compile Include="Constants.cs" />
|
||||||
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
|
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
|
||||||
<Compile Include="Services\WatchDeviceService.cs" />
|
<Compile Include="Services\WatchDeviceService.cs" />
|
||||||
|
<Compile Include="Renderers\CustomLabelRenderer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
||||||
@@ -232,6 +233,18 @@
|
|||||||
<SubType></SubType>
|
<SubType></SubType>
|
||||||
<Generator></Generator>
|
<Generator></Generator>
|
||||||
</AndroidResource>
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\layout\validatable_input_dialog_layout.xml">
|
||||||
|
<SubType></SubType>
|
||||||
|
<Generator></Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable\empty_uris_placeholder.xml">
|
||||||
|
<SubType></SubType>
|
||||||
|
<Generator></Generator>
|
||||||
|
</AndroidResource>
|
||||||
|
<AndroidResource Include="Resources\drawable\empty_uris_placeholder_dark.xml">
|
||||||
|
<SubType></SubType>
|
||||||
|
<Generator></Generator>
|
||||||
|
</AndroidResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
|
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Bit.Droid.Autofill
|
|||||||
private List<Field> _passwordFields = null;
|
private List<Field> _passwordFields = null;
|
||||||
private List<Field> _usernameFields = null;
|
private List<Field> _usernameFields = null;
|
||||||
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
|
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
|
||||||
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username"};
|
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username" };
|
||||||
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
|
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
|
||||||
|
|
||||||
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
|
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
|
||||||
@@ -54,15 +54,14 @@ namespace Bit.Droid.Autofill
|
|||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
||||||
{
|
{
|
||||||
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
|
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
|
||||||
|
return _passwordFields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
||||||
|
if (!_passwordFields.Any())
|
||||||
{
|
{
|
||||||
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
||||||
if (!_passwordFields.Any())
|
|
||||||
{
|
|
||||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return _passwordFields;
|
return _passwordFields;
|
||||||
}
|
}
|
||||||
@@ -87,24 +86,26 @@ namespace Bit.Droid.Autofill
|
|||||||
{
|
{
|
||||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
|
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
|
||||||
}
|
}
|
||||||
|
if (_usernameFields.Any())
|
||||||
|
{
|
||||||
|
return _usernameFields;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var passwordField in PasswordFields)
|
|
||||||
{
|
|
||||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
|
||||||
.LastOrDefault();
|
|
||||||
if (usernameField != null)
|
|
||||||
{
|
|
||||||
_usernameFields.Add(usernameField);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_usernameFields.Any())
|
foreach (var passwordField in PasswordFields)
|
||||||
|
{
|
||||||
|
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
||||||
|
.LastOrDefault();
|
||||||
|
if (usernameField != null)
|
||||||
{
|
{
|
||||||
_usernameFields = Fields.Where(f => FieldIsUsername(f)).ToList();
|
_usernameFields.Add(usernameField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_usernameFields.Any())
|
||||||
|
{
|
||||||
|
_usernameFields = Fields.Where(f => FieldIsUsername(f)).ToList();
|
||||||
|
}
|
||||||
return _usernameFields;
|
return _usernameFields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ namespace Bit.Droid
|
|||||||
private IAppIdService _appIdService;
|
private IAppIdService _appIdService;
|
||||||
private IEventService _eventService;
|
private IEventService _eventService;
|
||||||
private IPushNotificationListenerService _pushNotificationListenerService;
|
private IPushNotificationListenerService _pushNotificationListenerService;
|
||||||
private IVaultTimeoutService _vaultTimeoutService;
|
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private PendingIntent _eventUploadPendingIntent;
|
private PendingIntent _eventUploadPendingIntent;
|
||||||
private AppOptions _appOptions;
|
private AppOptions _appOptions;
|
||||||
@@ -69,7 +68,6 @@ namespace Bit.Droid
|
|||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
|
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>();
|
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
TabLayoutResource = Resource.Layout.Tabbar;
|
TabLayoutResource = Resource.Layout.Tabbar;
|
||||||
@@ -234,7 +232,6 @@ namespace Bit.Droid
|
|||||||
|
|
||||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||||
{
|
{
|
||||||
_vaultTimeoutService.ResetTimeoutDelay = true;
|
|
||||||
if (resultCode == Result.Ok &&
|
if (resultCode == Result.Ok &&
|
||||||
(requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
|
(requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.5.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="2023.7.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|||||||
43
src/Android/Renderers/CustomLabelRenderer.cs
Normal file
43
src/Android/Renderers/CustomLabelRenderer.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class CustomLabelRenderer : LabelRenderer
|
||||||
|
{
|
||||||
|
public CustomLabelRenderer(Context context)
|
||||||
|
: base(context)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
if (Control != null && e.NewElement is CustomLabel label)
|
||||||
|
{
|
||||||
|
if (label.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
|
||||||
|
{
|
||||||
|
Control.Typeface = Android.Graphics.Typeface.Create(null, label.FontWeight.Value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var label = sender as CustomLabel;
|
||||||
|
switch (e.PropertyName)
|
||||||
|
{
|
||||||
|
case nameof(CustomLabel.AutomationId):
|
||||||
|
Control.ContentDescription = label.AutomationId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/Android/Resources/drawable/empty_uris_placeholder.xml
Normal file
35
src/Android/Resources/drawable/empty_uris_placeholder.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:viewportWidth="129"
|
||||||
|
android:viewportHeight="124"
|
||||||
|
android:width="129dp"
|
||||||
|
android:height="124dp">
|
||||||
|
<path
|
||||||
|
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
|
||||||
|
android:fillColor="#F0F0F0"
|
||||||
|
android:strokeColor="#89929F"
|
||||||
|
android:strokeWidth="3" />
|
||||||
|
<path
|
||||||
|
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
|
||||||
|
android:strokeColor="#89929F"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
|
||||||
|
android:strokeColor="#89929F"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
|
||||||
|
android:strokeColor="#89929F"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
|
||||||
|
android:fillColor="#89929F" />
|
||||||
|
<path
|
||||||
|
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
|
||||||
|
android:fillColor="#89929F" />
|
||||||
|
<path
|
||||||
|
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
|
||||||
|
android:fillColor="#89929F" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:viewportWidth="129"
|
||||||
|
android:viewportHeight="124"
|
||||||
|
android:width="129dp"
|
||||||
|
android:height="124dp">
|
||||||
|
<path
|
||||||
|
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
|
||||||
|
android:fillColor="@android:color/transparent"
|
||||||
|
android:strokeColor="#A3A3A3"
|
||||||
|
android:strokeWidth="3" />
|
||||||
|
<path
|
||||||
|
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
|
||||||
|
android:strokeColor="#A3A3A3"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
|
||||||
|
android:strokeColor="#A3A3A3"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
|
||||||
|
android:strokeColor="#A3A3A3"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
|
||||||
|
android:fillColor="#A3A3A3" />
|
||||||
|
<path
|
||||||
|
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
|
||||||
|
android:fillColor="#A3A3A3" />
|
||||||
|
<path
|
||||||
|
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
|
||||||
|
android:fillColor="#A3A3A3" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="30dp"
|
||||||
|
android:paddingRight="30dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lblHeader"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="@dimen/dialog_header_text_size"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginBottom="-3dp"
|
||||||
|
android:labelFor="@+id/txtValue"/>
|
||||||
|
<EditText
|
||||||
|
android:id="@id/txtValue"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="@dimen/dialog_input_text_size"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lblValueSubinfo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="@dimen/dialog_sub_value_info_text_size"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -2,4 +2,7 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen>
|
<dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen>
|
||||||
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen>
|
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen>
|
||||||
|
<dimen name="dialog_input_text_size">16sp</dimen>
|
||||||
|
<dimen name="dialog_header_text_size">12sp</dimen>
|
||||||
|
<dimen name="dialog_sub_value_info_text_size">12sp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
|
using Android.Media;
|
||||||
using Android.Nfc;
|
using Android.Nfc;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Provider;
|
using Android.Provider;
|
||||||
@@ -13,11 +14,17 @@ using Android.Views.InputMethods;
|
|||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Utilities.Prompts;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Utilities;
|
using Bit.Droid.Utilities;
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using static Android.Icu.Text.CaseMap;
|
||||||
|
using static Android.Renderscripts.ScriptGroup;
|
||||||
|
using static Android.Util.EventLogTags;
|
||||||
using static Bit.App.Pages.SettingsPageViewModel;
|
using static Bit.App.Pages.SettingsPageViewModel;
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
@@ -209,10 +216,7 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
if (numericKeyboard)
|
if (numericKeyboard)
|
||||||
{
|
{
|
||||||
input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
|
SetNumericKeyboardTo(input);
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||||
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||||
}
|
}
|
||||||
if (password)
|
if (password)
|
||||||
{
|
{
|
||||||
@@ -248,6 +252,82 @@ namespace Bit.Droid.Services
|
|||||||
return result.Task;
|
return result.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
if (activity == null)
|
||||||
|
{
|
||||||
|
return Task.FromResult<ValidatablePromptResponse?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var alertBuilder = new AlertDialog.Builder(activity);
|
||||||
|
alertBuilder.SetTitle(config.Title);
|
||||||
|
var view = activity.LayoutInflater.Inflate(Resource.Layout.validatable_input_dialog_layout, null);
|
||||||
|
alertBuilder.SetView(view);
|
||||||
|
|
||||||
|
var result = new TaskCompletionSource<ValidatablePromptResponse?>();
|
||||||
|
|
||||||
|
alertBuilder.SetPositiveButton(config.OkButtonText ?? AppResources.Ok, listener: null);
|
||||||
|
alertBuilder.SetNegativeButton(config.CancelButtonText ?? AppResources.Cancel, (sender, args) => result.TrySetResult(null));
|
||||||
|
if (!string.IsNullOrEmpty(config.ThirdButtonText))
|
||||||
|
{
|
||||||
|
alertBuilder.SetNeutralButton(config.ThirdButtonText, (sender, args) => result.TrySetResult(new ValidatablePromptResponse(null, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var alert = alertBuilder.Create();
|
||||||
|
|
||||||
|
var input = view.FindViewById<EditText>(Resource.Id.txtValue);
|
||||||
|
var lblHeader = view.FindViewById<TextView>(Resource.Id.lblHeader);
|
||||||
|
var lblValueSubinfo = view.FindViewById<TextView>(Resource.Id.lblValueSubinfo);
|
||||||
|
|
||||||
|
lblHeader.Text = config.Subtitle;
|
||||||
|
lblValueSubinfo.Text = config.ValueSubInfo;
|
||||||
|
|
||||||
|
var defaultSubInfoColor = lblValueSubinfo.TextColors;
|
||||||
|
|
||||||
|
input.InputType = InputTypes.ClassText;
|
||||||
|
|
||||||
|
if (config.NumericKeyboard)
|
||||||
|
{
|
||||||
|
SetNumericKeyboardTo(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning | (ImeAction)ImeFlags.NoExtractUi;
|
||||||
|
input.Text = config.Text ?? string.Empty;
|
||||||
|
input.SetSelection(config.Text?.Length ?? 0);
|
||||||
|
input.AfterTextChanged += (sender, args) =>
|
||||||
|
{
|
||||||
|
if (lblValueSubinfo.Text != config.ValueSubInfo)
|
||||||
|
{
|
||||||
|
lblValueSubinfo.Text = config.ValueSubInfo;
|
||||||
|
lblHeader.SetTextColor(defaultSubInfoColor);
|
||||||
|
lblValueSubinfo.SetTextColor(defaultSubInfoColor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
alert.Window.SetSoftInputMode(SoftInput.StateVisible);
|
||||||
|
alert.Show();
|
||||||
|
|
||||||
|
var positiveButton = alert.GetButton((int)DialogButtonType.Positive);
|
||||||
|
positiveButton.Click += (sender, args) =>
|
||||||
|
{
|
||||||
|
var error = config.ValidateText(input.Text);
|
||||||
|
if (error != null)
|
||||||
|
{
|
||||||
|
lblHeader.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
|
||||||
|
lblValueSubinfo.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
|
||||||
|
lblValueSubinfo.Text = error;
|
||||||
|
lblValueSubinfo.SendAccessibilityEvent(Android.Views.Accessibility.EventTypes.ViewFocused);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.TrySetResult(new ValidatablePromptResponse(input.Text, false));
|
||||||
|
alert.Dismiss();
|
||||||
|
};
|
||||||
|
|
||||||
|
return result.Task;
|
||||||
|
}
|
||||||
|
|
||||||
public void RateApp()
|
public void RateApp()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
@@ -525,5 +605,13 @@ namespace Bit.Droid.Services
|
|||||||
// only used by iOS
|
// only used by iOS
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetNumericKeyboardTo(EditText editText)
|
||||||
|
{
|
||||||
|
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
editText.KeyListener = DigitsKeyListener.GetInstance(false, false);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
|
using Java.Lang;
|
||||||
|
|
||||||
namespace Bit.Droid.Utilities
|
namespace Bit.Droid.Utilities
|
||||||
{
|
{
|
||||||
@@ -13,7 +14,12 @@ namespace Bit.Droid.Utilities
|
|||||||
// Note: getting the bundle like this will cause to call unparcel() internally
|
// Note: getting the bundle like this will cause to call unparcel() internally
|
||||||
var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel");
|
var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel");
|
||||||
}
|
}
|
||||||
catch (BadParcelableException)
|
catch (Exception ex) when
|
||||||
|
(
|
||||||
|
ex is BadParcelableException ||
|
||||||
|
ex is ClassNotFoundException ||
|
||||||
|
ex is RuntimeException
|
||||||
|
)
|
||||||
{
|
{
|
||||||
intent.ReplaceExtras((Bundle)null);
|
intent.ReplaceExtras((Bundle)null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities.Prompts;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ namespace Bit.App.Abstractions
|
|||||||
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
|
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
|
||||||
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
|
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
|
||||||
bool autofocus = true, bool password = false);
|
bool autofocus = true, bool password = false);
|
||||||
|
Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config);
|
||||||
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
|
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
|
||||||
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
|
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,8 @@
|
|||||||
<Folder Include="Controls\DateTime\" />
|
<Folder Include="Controls\DateTime\" />
|
||||||
<Folder Include="Controls\IconLabelButton\" />
|
<Folder Include="Controls\IconLabelButton\" />
|
||||||
<Folder Include="Controls\PasswordStrengthProgressBar\" />
|
<Folder Include="Controls\PasswordStrengthProgressBar\" />
|
||||||
|
<Folder Include="Utilities\Automation\" />
|
||||||
|
<Folder Include="Utilities\Prompts\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -440,5 +442,7 @@
|
|||||||
<None Remove="MessagePack" />
|
<None Remove="MessagePack" />
|
||||||
<None Remove="MessagePack.MSBuild.Tasks" />
|
<None Remove="MessagePack.MSBuild.Tasks" />
|
||||||
<None Remove="Controls\PasswordStrengthProgressBar\" />
|
<None Remove="Controls\PasswordStrengthProgressBar\" />
|
||||||
|
<None Remove="Utilities\Automation\" />
|
||||||
|
<None Remove="Utilities\Prompts\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ namespace Bit.App
|
|||||||
{
|
{
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
// Reset delay on every start
|
// Reset delay on every start
|
||||||
_vaultTimeoutService.DelayTimeoutMs = null;
|
_vaultTimeoutService.DelayLockAndLogoutMs = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _configService.GetAsync();
|
await _configService.GetAsync();
|
||||||
|
|||||||
@@ -30,13 +30,15 @@
|
|||||||
BackgroundColor="{DynamicResource BackgroundColor}"
|
BackgroundColor="{DynamicResource BackgroundColor}"
|
||||||
VerticalOptions="Start"
|
VerticalOptions="Start"
|
||||||
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
|
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
|
||||||
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
|
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never"
|
||||||
|
AutomationId="AccountListView">
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="view:AccountView">
|
<DataTemplate x:DataType="view:AccountView">
|
||||||
<controls:AccountViewCell
|
<controls:AccountViewCell
|
||||||
Account="{Binding .}"
|
Account="{Binding .}"
|
||||||
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
|
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
|
||||||
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
|
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
|
||||||
|
AutomationId="AccountViewCell"
|
||||||
/>
|
/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
|
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
||||||
@@ -60,20 +60,23 @@
|
|||||||
Text="{Binding AccountView.Email}"
|
Text="{Binding AccountView.Email}"
|
||||||
IsVisible="{Binding IsActive}"
|
IsVisible="{Binding IsActive}"
|
||||||
StyleClass="accountlist-title, accountlist-title-platform"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation"
|
||||||
|
AutomationId="AccountEmailLabel" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Text="{Binding AccountView.Email}"
|
Text="{Binding AccountView.Email}"
|
||||||
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="accountlist-title, accountlist-title-platform"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
TextColor="{DynamicResource MutedColor}"
|
TextColor="{DynamicResource MutedColor}"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation"
|
||||||
|
AutomationId="AccountEmailLabel" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
IsVisible="{Binding ShowHostname}"
|
IsVisible="{Binding ShowHostname}"
|
||||||
Text="{Binding AccountView.Hostname}"
|
Text="{Binding AccountView.Hostname}"
|
||||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation"
|
||||||
|
AutomationId="AccountHostUrlLabel" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountUnlocked}"
|
Text="{u:I18n AccountUnlocked}"
|
||||||
@@ -81,7 +84,8 @@
|
|||||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation"
|
||||||
|
AutomationId="AccountStatusLabel" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLocked}"
|
Text="{u:I18n AccountLocked}"
|
||||||
@@ -89,7 +93,8 @@
|
|||||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation"
|
||||||
|
AutomationId="AccountStatusLabel" />
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLoggedOut}"
|
Text="{u:I18n AccountLoggedOut}"
|
||||||
@@ -97,7 +102,8 @@
|
|||||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation" />
|
LineBreakMode="TailTruncation"
|
||||||
|
AutomationId="AccountStatusLabel" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
@@ -107,7 +113,8 @@
|
|||||||
Margin="12,0"
|
Margin="12,0"
|
||||||
HorizontalOptions="Center"
|
HorizontalOptions="Center"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
StyleClass="list-icon, list-icon-platform" />
|
StyleClass="list-icon, list-icon-platform"
|
||||||
|
AutomationId="InactiveVaultIcon" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Text="{Binding AuthStatusIconActive}"
|
Text="{Binding AuthStatusIconActive}"
|
||||||
@@ -116,7 +123,8 @@
|
|||||||
HorizontalOptions="Center"
|
HorizontalOptions="Center"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
StyleClass="list-icon, list-icon-platform"
|
StyleClass="list-icon, list-icon-platform"
|
||||||
TextColor="{DynamicResource TextColor}"/>
|
TextColor="{DynamicResource TextColor}"
|
||||||
|
AutomationId="ActiveVaultIcon" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
@@ -147,7 +155,8 @@
|
|||||||
StyleClass="accountlist-title, accountlist-title-platform"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
Grid.Column="1" />
|
Grid.Column="1"
|
||||||
|
AutomationId="AddAccountButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ViewCell>
|
</ViewCell>
|
||||||
@@ -9,7 +9,8 @@
|
|||||||
StyleClass="list-row, list-row-platform"
|
StyleClass="list-row, list-row-platform"
|
||||||
RowSpacing="0"
|
RowSpacing="0"
|
||||||
ColumnSpacing="0"
|
ColumnSpacing="0"
|
||||||
x:DataType="controls:CipherViewCellViewModel">
|
x:DataType="controls:CipherViewCellViewModel"
|
||||||
|
AutomationId="CipherCell">
|
||||||
|
|
||||||
<Grid.Resources>
|
<Grid.Resources>
|
||||||
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
||||||
@@ -36,7 +37,8 @@
|
|||||||
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
||||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
|
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
|
||||||
AutomationProperties.IsInAccessibleTree="False" />
|
AutomationProperties.IsInAccessibleTree="False"
|
||||||
|
AutomationId="CipherTypeIcon" />
|
||||||
|
|
||||||
<ff:CachedImage
|
<ff:CachedImage
|
||||||
x:Name="_iconImage"
|
x:Name="_iconImage"
|
||||||
@@ -52,7 +54,8 @@
|
|||||||
Aspect="AspectFit"
|
Aspect="AspectFit"
|
||||||
IsVisible="{Binding ShowIconImage}"
|
IsVisible="{Binding ShowIconImage}"
|
||||||
Source="{Binding IconImageSource, Mode=OneTime}"
|
Source="{Binding IconImageSource, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="False" />
|
AutomationProperties.IsInAccessibleTree="False"
|
||||||
|
AutomationId="CipherWebsiteIcon" />
|
||||||
|
|
||||||
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -71,7 +74,8 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
StyleClass="list-title, list-title-platform"
|
StyleClass="list-title, list-title-platform"
|
||||||
Text="{Binding Cipher.Name}" />
|
Text="{Binding Cipher.Name}"
|
||||||
|
AutomationId="CipherNameLabel" />
|
||||||
<Label
|
<Label
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -80,7 +84,8 @@
|
|||||||
StyleClass="list-subtitle, list-subtitle-platform"
|
StyleClass="list-subtitle, list-subtitle-platform"
|
||||||
Text="{Binding Cipher.SubTitle}"
|
Text="{Binding Cipher.SubTitle}"
|
||||||
IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
|
IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
|
||||||
Converter={StaticResource stringHasValueConverter}}"/>
|
Converter={StaticResource stringHasValueConverter}}"
|
||||||
|
AutomationId="CipherSubTitleLabel" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -91,7 +96,8 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
|
||||||
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
|
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Shared}" />
|
AutomationProperties.Name="{u:I18n Shared}"
|
||||||
|
AutomationId="CipherInCollectionIcon" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -102,7 +108,8 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Paperclip}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Paperclip}}"
|
||||||
IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}"
|
IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Attachments}" />
|
AutomationProperties.Name="{u:I18n Attachments}"
|
||||||
|
AutomationId="CipherWithAttachmentsIcon" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
@@ -114,6 +121,7 @@
|
|||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="EndAndExpand"
|
HorizontalOptions="EndAndExpand"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
|
AutomationId="CipherOptionsButton" />
|
||||||
|
|
||||||
</controls:ExtendedGrid>
|
</controls:ExtendedGrid>
|
||||||
13
src/App/Controls/CustomLabel.cs
Normal file
13
src/App/Controls/CustomLabel.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Controls
|
||||||
|
{
|
||||||
|
public class CustomLabel : Label
|
||||||
|
{
|
||||||
|
public CustomLabel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public int? FontWeight { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
x:Class="Bit.App.Controls.SendViewCell"
|
x:Class="Bit.App.Controls.SendViewCell"
|
||||||
@@ -54,14 +54,16 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
StyleClass="list-title, list-title-platform"
|
StyleClass="list-title, list-title-platform"
|
||||||
Text="{Binding Send.Name}" />
|
Text="{Binding Send.Name}"
|
||||||
|
AutomationId="SendNameLabel" />
|
||||||
<Label
|
<Label
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.ColumnSpan="6"
|
Grid.ColumnSpan="6"
|
||||||
StyleClass="list-subtitle, list-subtitle-platform"
|
StyleClass="list-subtitle, list-subtitle-platform"
|
||||||
Text="{Binding Send.DisplayDate}" />
|
Text="{Binding Send.DisplayDate}"
|
||||||
|
AutomationId="SendDateLabel" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -72,7 +74,8 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.ExclamationTriangle}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.ExclamationTriangle}}"
|
||||||
IsVisible="{Binding Send.Disabled, Mode=OneTime}"
|
IsVisible="{Binding Send.Disabled, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Disabled}" />
|
AutomationProperties.Name="{u:I18n Disabled}"
|
||||||
|
AutomationId="DisabledSendLabel" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -83,7 +86,8 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Key}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Key}}"
|
||||||
IsVisible="{Binding Send.HasPassword, Mode=OneTime}"
|
IsVisible="{Binding Send.HasPassword, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Password}" />
|
AutomationProperties.Name="{u:I18n Password}"
|
||||||
|
AutomationId="PasswordProtectedSendLabel" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="3"
|
Grid.Column="3"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -94,7 +98,8 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Ban}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Ban}}"
|
||||||
IsVisible="{Binding Send.MaxAccessCountReached, Mode=OneTime}"
|
IsVisible="{Binding Send.MaxAccessCountReached, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n MaxAccessCountReached}" />
|
AutomationProperties.Name="{u:I18n MaxAccessCountReached}"
|
||||||
|
AutomationId="SendMaxAccessCountReachedLabel" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="4"
|
Grid.Column="4"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -105,7 +110,8 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clock}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clock}}"
|
||||||
IsVisible="{Binding Send.Expired, Mode=OneTime}"
|
IsVisible="{Binding Send.Expired, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Expired}" />
|
AutomationProperties.Name="{u:I18n Expired}"
|
||||||
|
AutomationId="ExpiredSendLabel" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="5"
|
Grid.Column="5"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -116,7 +122,8 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
||||||
IsVisible="{Binding Send.PendingDelete, Mode=OneTime}"
|
IsVisible="{Binding Send.PendingDelete, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n PendingDelete}" />
|
AutomationProperties.Name="{u:I18n PendingDelete}"
|
||||||
|
AutomationId="SendWithPendingDeletionLabel" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
@@ -129,6 +136,7 @@
|
|||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="EndAndExpand"
|
HorizontalOptions="EndAndExpand"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
|
AutomationId="SendOptionsButton" />
|
||||||
|
|
||||||
</controls:ExtendedGrid>
|
</controls:ExtendedGrid>
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||||
|
AutomationId="BooleanCustomFieldNameLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
IsVisible="{Binding IsEditing}"
|
IsVisible="{Binding IsEditing}"
|
||||||
@@ -49,13 +50,15 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="0, 5, 0, 0"
|
Margin="0, 5, 0, 0"
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||||
|
AutomationId="BooleanCustomFieldValueLabel" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding BooleanValue}"
|
IsToggled="{Binding BooleanValue}"
|
||||||
IsVisible="{Binding IsEditing}"
|
IsVisible="{Binding IsEditing}"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2" />
|
Grid.RowSpan="2"
|
||||||
|
AutomationId="BooleanCustomFieldValueToggle" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||||
|
|||||||
@@ -31,7 +31,8 @@
|
|||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="HiddenCustomFieldNameLabel" />
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -39,7 +40,8 @@
|
|||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
IsVisible="{Binding ShowHiddenValue}" />
|
IsVisible="{Binding ShowHiddenValue}"
|
||||||
|
AutomationId="HiddenCustomFieldValueLabel" />
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
@@ -56,7 +58,8 @@
|
|||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{Binding Field.Name}">
|
AutomationProperties.Name="{Binding Field.Name}"
|
||||||
|
AutomationId="HiddenCustomFieldValueEntry">
|
||||||
<Entry.Keyboard>
|
<Entry.Keyboard>
|
||||||
<Keyboard x:FactoryMethod="Create">
|
<Keyboard x:FactoryMethod="Create">
|
||||||
<x:Arguments>
|
<x:Arguments>
|
||||||
@@ -74,7 +77,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationId="HiddenCustomFieldShowValueButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
|
|||||||
@@ -29,13 +29,15 @@
|
|||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="LinkedCustomFieldNameLabel" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||||
|
AutomationId="LinkedCustomFieldValueLabel" />
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row, box-row-input"
|
StyleClass="box-row, box-row-input"
|
||||||
IsVisible="{Binding IsEditing}">
|
IsVisible="{Binding IsEditing}">
|
||||||
@@ -44,7 +46,8 @@
|
|||||||
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
||||||
ItemDisplayBinding="{Binding Key}"
|
ItemDisplayBinding="{Binding Key}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="LinkedCustomFieldValuePicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
@@ -55,7 +58,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
|
AutomationId="LinkedCustomFieldOptionsButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -29,13 +29,15 @@
|
|||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="TextCustomFieldNameLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||||
|
AutomationId="TextCustomFieldValueLabel" />
|
||||||
<Entry
|
<Entry
|
||||||
Text="{Binding Field.Value}"
|
Text="{Binding Field.Value}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
@@ -43,7 +45,8 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding IsEditing}"
|
IsVisible="{Binding IsEditing}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{Binding Field.Name}" />
|
AutomationProperties.Name="{Binding Field.Name}"
|
||||||
|
AutomationId="TextCustomFieldValueEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -53,7 +56,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Copy}" />
|
AutomationProperties.Name="{u:I18n Copy}"
|
||||||
|
AutomationId="TextCustomFieldCopyValue" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||||
@@ -63,7 +67,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
|
AutomationId="TextCustomFieldOptionsButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}" />
|
AutomationProperties.Name="{u:I18n Account}"
|
||||||
|
AutomationId="AccountIconButton" />
|
||||||
<ToolbarItem x:Name="_closeButton" Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"/>
|
<ToolbarItem x:Name="_closeButton" Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"/>
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}" />
|
AutomationProperties.Name="{u:I18n Account}"
|
||||||
|
AutomationId="AccountIconButton" />
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<Grid
|
<Grid
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
IsVisible="{Binding PinLock}"
|
IsVisible="{Binding PinEnabled}"
|
||||||
Padding="0, 10, 0, 0">
|
Padding="0, 10, 0, 0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}"
|
ReturnCommand="{Binding SubmitCommand}"
|
||||||
AutomationId="PinEntry"/>
|
AutomationId="PinEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -83,12 +84,12 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationId="PinVisibilityToggle"/>
|
AutomationId="PinVisibilityToggle" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="_passwordGrid"
|
x:Name="_passwordGrid"
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
|
||||||
Padding="0, 10, 0, 0">
|
Padding="0, 10, 0, 0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -114,7 +115,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}"
|
ReturnCommand="{Binding SubmitCommand}"
|
||||||
AutomationId="MasterPasswordEntry"/>
|
AutomationId="MasterPasswordEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -153,7 +154,7 @@
|
|||||||
Text="{u:I18n Unlock}"
|
Text="{u:I18n Unlock}"
|
||||||
StyleClass="btn-primary"
|
StyleClass="btn-primary"
|
||||||
Clicked="Unlock_Clicked"
|
Clicked="Unlock_Clicked"
|
||||||
AutomationId="UnlockVaultButton"/>
|
AutomationId="UnlockVaultButton" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_vm?.PinLock ?? false)
|
if (_vm?.PinEnabled ?? false)
|
||||||
{
|
{
|
||||||
return _pin;
|
return _pin;
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task PromptBiometricAfterResumeAsync()
|
public async Task PromptBiometricAfterResumeAsync()
|
||||||
{
|
{
|
||||||
if (_vm.BiometricLock)
|
if (_vm.BiometricEnabled)
|
||||||
{
|
{
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
if (!_promptedAfterResume)
|
if (!_promptedAfterResume)
|
||||||
@@ -91,13 +91,13 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
||||||
|
|
||||||
if (!_vm.BiometricLock)
|
if (!_vm.BiometricEnabled)
|
||||||
{
|
{
|
||||||
RequestFocus(SecretEntry);
|
RequestFocus(SecretEntry);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_vm.UsingKeyConnector && !_vm.PinLock)
|
if (_vm.UsingKeyConnector && !_vm.PinEnabled)
|
||||||
{
|
{
|
||||||
_passwordGrid.IsVisible = false;
|
_passwordGrid.IsVisible = false;
|
||||||
_unlockButton.IsVisible = false;
|
_unlockButton.IsVisible = false;
|
||||||
|
|||||||
@@ -33,21 +33,21 @@ namespace Bit.App.Pages
|
|||||||
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
private string _email;
|
private string _email;
|
||||||
private string _masterPassword;
|
private string _masterPassword;
|
||||||
private string _pin;
|
private string _pin;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private bool _pinLock;
|
private PinLockEnum _pinStatus;
|
||||||
private bool _biometricLock;
|
private bool _pinEnabled;
|
||||||
|
private bool _biometricEnabled;
|
||||||
private bool _biometricIntegrityValid = true;
|
private bool _biometricIntegrityValid = true;
|
||||||
private bool _biometricButtonVisible;
|
private bool _biometricButtonVisible;
|
||||||
private bool _usingKeyConnector;
|
private bool _usingKeyConnector;
|
||||||
private string _biometricButtonText;
|
private string _biometricButtonText;
|
||||||
private string _loggedInAsText;
|
private string _loggedInAsText;
|
||||||
private string _lockedVerifyText;
|
private string _lockedVerifyText;
|
||||||
private bool _isPinProtected;
|
|
||||||
private bool _isPinProtectedWithKey;
|
|
||||||
|
|
||||||
public LockPageViewModel()
|
public LockPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -65,6 +65,8 @@ namespace Bit.App.Pages
|
|||||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||||
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
||||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
_syncService = ServiceContainer.Resolve<ISyncService>();
|
||||||
|
|
||||||
PageTitle = AppResources.VerifyMasterPassword;
|
PageTitle = AppResources.VerifyMasterPassword;
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
@@ -100,10 +102,10 @@ namespace Bit.App.Pages
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool PinLock
|
public bool PinEnabled
|
||||||
{
|
{
|
||||||
get => _pinLock;
|
get => _pinEnabled;
|
||||||
set => SetProperty(ref _pinLock, value);
|
set => SetProperty(ref _pinEnabled, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UsingKeyConnector
|
public bool UsingKeyConnector
|
||||||
@@ -111,10 +113,10 @@ namespace Bit.App.Pages
|
|||||||
get => _usingKeyConnector;
|
get => _usingKeyConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BiometricLock
|
public bool BiometricEnabled
|
||||||
{
|
{
|
||||||
get => _biometricLock;
|
get => _biometricEnabled;
|
||||||
set => SetProperty(ref _biometricLock, value);
|
set => SetProperty(ref _biometricEnabled, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BiometricIntegrityValid
|
public bool BiometricIntegrityValid
|
||||||
@@ -162,14 +164,18 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
_pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
|
||||||
_isPinProtectedWithKey;
|
var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
|
||||||
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
|
?? await _stateService.GetPinProtectedKeyAsync();
|
||||||
|
PinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
|
||||||
|
_pinStatus == PinLockEnum.Persistent;
|
||||||
|
|
||||||
|
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasEncryptedUserKeyAsync();
|
||||||
|
|
||||||
// Users with key connector and without biometric or pin has no MP to unlock with
|
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||||
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
if (_usingKeyConnector && !(BiometricLock || PinLock))
|
if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
|
||||||
{
|
{
|
||||||
await _vaultTimeoutService.LogOutAsync();
|
await _vaultTimeoutService.LogOutAsync();
|
||||||
return;
|
return;
|
||||||
@@ -188,7 +194,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
var webVaultHostname = CoreHelpers.GetHostname(webVault);
|
var webVaultHostname = CoreHelpers.GetHostname(webVault);
|
||||||
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
|
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
|
||||||
if (PinLock)
|
if (PinEnabled)
|
||||||
{
|
{
|
||||||
PageTitle = AppResources.VerifyPIN;
|
PageTitle = AppResources.VerifyPIN;
|
||||||
LockedVerifyText = AppResources.VaultLockedPIN;
|
LockedVerifyText = AppResources.VaultLockedPIN;
|
||||||
@@ -207,7 +213,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BiometricLock)
|
if (BiometricEnabled)
|
||||||
{
|
{
|
||||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||||
if (!_biometricIntegrityValid)
|
if (!_biometricIntegrityValid)
|
||||||
@@ -229,14 +235,14 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
if (PinLock && string.IsNullOrWhiteSpace(Pin))
|
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
|
||||||
{
|
{
|
||||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||||
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
|
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
|
||||||
AppResources.Ok);
|
AppResources.Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword))
|
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
|
||||||
{
|
{
|
||||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
||||||
@@ -247,34 +253,54 @@ namespace Bit.App.Pages
|
|||||||
ShowPassword = false;
|
ShowPassword = false;
|
||||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||||
|
|
||||||
if (PinLock)
|
if (PinEnabled)
|
||||||
{
|
{
|
||||||
var failed = true;
|
var failed = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_isPinProtected)
|
EncString userKeyPin = null;
|
||||||
|
EncString oldPinProtected = null;
|
||||||
|
if (_pinStatus == PinLockEnum.Persistent)
|
||||||
{
|
{
|
||||||
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
|
userKeyPin = await _stateService.GetUserKeyPinAsync();
|
||||||
|
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||||
|
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||||
|
}
|
||||||
|
else if (_pinStatus == PinLockEnum.Transient)
|
||||||
|
{
|
||||||
|
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
|
||||||
|
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
UserKey userKey;
|
||||||
|
if (oldPinProtected != null)
|
||||||
|
{
|
||||||
|
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||||
|
_pinStatus == PinLockEnum.Transient,
|
||||||
|
Pin,
|
||||||
|
_email,
|
||||||
kdfConfig,
|
kdfConfig,
|
||||||
await _stateService.GetPinProtectedKeyAsync());
|
oldPinProtected
|
||||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
);
|
||||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
|
||||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
|
||||||
failed = decPin != Pin;
|
|
||||||
if (!failed)
|
|
||||||
{
|
|
||||||
Pin = string.Empty;
|
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
|
||||||
await SetKeyAndContinueAsync(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
|
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||||
failed = false;
|
Pin,
|
||||||
|
_email,
|
||||||
|
kdfConfig,
|
||||||
|
userKeyPin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||||
|
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||||
|
failed = decryptedPin != Pin;
|
||||||
|
if (!failed)
|
||||||
|
{
|
||||||
Pin = string.Empty;
|
Pin = string.Empty;
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
await SetKeyAndContinueAsync(key);
|
await SetKeyAndContinueAsync(userKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -295,19 +321,21 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
|
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
||||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
var storedKeyHash = await _cryptoService.GetPasswordHashAsync();
|
||||||
var passwordValid = false;
|
var passwordValid = false;
|
||||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||||
|
|
||||||
if (storedKeyHash != null)
|
if (storedKeyHash != null)
|
||||||
{
|
{
|
||||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
|
// Offline unlock possible
|
||||||
|
passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(MasterPassword, masterKey);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Online unlock required
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
|
||||||
var request = new PasswordVerificationRequest();
|
var request = new PasswordVerificationRequest();
|
||||||
request.MasterPasswordHash = keyHash;
|
request.MasterPasswordHash = keyHash;
|
||||||
|
|
||||||
@@ -316,8 +344,8 @@ namespace Bit.App.Pages
|
|||||||
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||||
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
||||||
passwordValid = true;
|
passwordValid = true;
|
||||||
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
|
||||||
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
await _cryptoService.SetPasswordHashAsync(localKeyHash);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -327,15 +355,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (passwordValid)
|
if (passwordValid)
|
||||||
{
|
{
|
||||||
if (_isPinProtected)
|
|
||||||
{
|
|
||||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
|
||||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
|
||||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
|
||||||
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
|
|
||||||
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
||||||
{
|
{
|
||||||
// Save the ForcePasswordResetReason to force a password reset after unlock
|
// Save the ForcePasswordResetReason to force a password reset after unlock
|
||||||
@@ -345,10 +364,13 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
MasterPassword = string.Empty;
|
MasterPassword = string.Empty;
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
await SetKeyAndContinueAsync(key);
|
|
||||||
|
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||||
|
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||||
|
await SetKeyAndContinueAsync(userKey);
|
||||||
|
|
||||||
// Re-enable biometrics
|
// Re-enable biometrics
|
||||||
if (BiometricLock & !BiometricIntegrityValid)
|
if (BiometricEnabled & !BiometricIntegrityValid)
|
||||||
{
|
{
|
||||||
await _biometricService.SetupBiometricAsync();
|
await _biometricService.SetupBiometricAsync();
|
||||||
}
|
}
|
||||||
@@ -425,7 +447,7 @@ namespace Bit.App.Pages
|
|||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
ShowPassword = !ShowPassword;
|
ShowPassword = !ShowPassword;
|
||||||
var secret = PinLock ? Pin : MasterPassword;
|
var secret = PinEnabled ? Pin : MasterPassword;
|
||||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,12 +455,12 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||||
BiometricButtonVisible = BiometricIntegrityValid;
|
BiometricButtonVisible = BiometricIntegrityValid;
|
||||||
if (!BiometricLock || !BiometricIntegrityValid)
|
if (!BiometricEnabled || !BiometricIntegrityValid)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
PinLock ? AppResources.PIN : AppResources.MasterPassword,
|
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
||||||
await _stateService.SetBiometricLockedAsync(!success);
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
if (success)
|
if (success)
|
||||||
@@ -447,18 +469,20 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
|
private async Task SetKeyAndContinueAsync(UserKey key)
|
||||||
{
|
{
|
||||||
var hasKey = await _cryptoService.HasKeyAsync();
|
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
await _cryptoService.SetKeyAsync(key);
|
await _cryptoService.SetUserKeyAsync(key);
|
||||||
}
|
}
|
||||||
|
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
|
||||||
await DoContinueAsync();
|
await DoContinueAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DoContinueAsync()
|
private async Task DoContinueAsync()
|
||||||
{
|
{
|
||||||
|
_syncService.FullSyncAsync(false).FireAndForget();
|
||||||
await _stateService.SetBiometricLockedAsync(false);
|
await _stateService.SetBiometricLockedAsync(false);
|
||||||
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
|
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
|
||||||
_messagingService.Send("unlocked");
|
_messagingService.Send("unlocked");
|
||||||
|
|||||||
76
src/App/Pages/Accounts/LoginApproveDevicePage.xaml
Normal file
76
src/App/Pages/Accounts/LoginApproveDevicePage.xaml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?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.LoginApproveDevicePage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
x:DataType="pages:LoginApproveDeviceViewModel"
|
||||||
|
x:Name="_page"
|
||||||
|
Title="{Binding PageTitle}">
|
||||||
|
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:LoginApproveDeviceViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<StackLayout Padding="10, 10">
|
||||||
|
<StackLayout Padding="5, 10" Orientation="Horizontal">
|
||||||
|
<StackLayout HorizontalOptions="FillAndExpand">
|
||||||
|
<Label
|
||||||
|
StyleClass="text-md"
|
||||||
|
Text="{u:I18n RememberThisDevice}"/>
|
||||||
|
<Label
|
||||||
|
StyleClass="box-sub-label"
|
||||||
|
Text="{u:I18n TurnOffUsingPublicDevice}"/>
|
||||||
|
</StackLayout>
|
||||||
|
<Switch
|
||||||
|
Scale="0.8"
|
||||||
|
IsToggled="{Binding RememberThisDevice}"
|
||||||
|
VerticalOptions="Center"/>
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout Margin="0, 20, 0, 0">
|
||||||
|
<Button
|
||||||
|
x:Name="_continue"
|
||||||
|
Text="{u:I18n Continue}"
|
||||||
|
StyleClass="btn-primary"
|
||||||
|
Command="{Binding ContinueCommand}"
|
||||||
|
IsVisible="{Binding ContinueEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_approveWithMyOtherDevice"
|
||||||
|
Text="{u:I18n ApproveWithMyOtherDevice}"
|
||||||
|
StyleClass="btn-primary"
|
||||||
|
Command="{Binding ApproveWithMyOtherDeviceCommand}"
|
||||||
|
IsVisible="{Binding ApproveWithMyOtherDeviceEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_requestAdminApproval"
|
||||||
|
Text="{u:I18n RequestAdminApproval}"
|
||||||
|
StyleClass="box-button-row"
|
||||||
|
Command="{Binding RequestAdminApprovalCommand}"
|
||||||
|
IsVisible="{Binding RequestAdminApprovalEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_approveWithMasterPassword"
|
||||||
|
Text="{u:I18n ApproveWithMasterPassword}"
|
||||||
|
StyleClass="box-button-row"
|
||||||
|
Command="{Binding ApproveWithMasterPasswordCommand}"
|
||||||
|
IsVisible="{Binding ApproveWithMasterPasswordEnabled}"/>
|
||||||
|
<Label
|
||||||
|
Text="{Binding LoggingInAsText}"
|
||||||
|
StyleClass="text-sm"
|
||||||
|
Margin="0,40,0,0"
|
||||||
|
AutomationId="LoggingInAsLabel"
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NotYou}"
|
||||||
|
StyleClass="text-md"
|
||||||
|
HorizontalOptions="Start"
|
||||||
|
TextColor="{DynamicResource HyperlinkColor}"
|
||||||
|
AutomationId="NotYouLabel">
|
||||||
|
<Label.GestureRecognizers>
|
||||||
|
<TapGestureRecognizer Tapped="Cancel_Clicked" />
|
||||||
|
</Label.GestureRecognizers>
|
||||||
|
</Label>
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</pages:BaseContentPage>
|
||||||
|
|
||||||
|
|
||||||
62
src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs
Normal file
62
src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class LoginApproveDevicePage : BaseContentPage
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly LoginApproveDeviceViewModel _vm;
|
||||||
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
|
public LoginApproveDevicePage(AppOptions appOptions = null)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_vm = BindingContext as LoginApproveDeviceViewModel;
|
||||||
|
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPassword().FireAndForget();
|
||||||
|
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
|
||||||
|
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
|
||||||
|
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
|
||||||
|
_vm.Page = this;
|
||||||
|
_appOptions = appOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAppearing()
|
||||||
|
{
|
||||||
|
_vm.InitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
_vm.CloseAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartLogInWithMasterPassword()
|
||||||
|
{
|
||||||
|
var page = new LockPage(_appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartLoginWithDeviceAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RequestAdminApprovalAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
132
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
132
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class LoginApproveDeviceViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
private bool _rememberThisDevice;
|
||||||
|
private bool _approveWithMyOtherDeviceEnabled;
|
||||||
|
private bool _requestAdminApprovalEnabled;
|
||||||
|
private bool _approveWithMasterPasswordEnabled;
|
||||||
|
private bool _continueEnabled;
|
||||||
|
private string _email;
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
|
||||||
|
public ICommand ApproveWithMyOtherDeviceCommand { get; }
|
||||||
|
public ICommand RequestAdminApprovalCommand { get; }
|
||||||
|
public ICommand ApproveWithMasterPasswordCommand { get; }
|
||||||
|
public ICommand ContinueCommand { get; }
|
||||||
|
|
||||||
|
public Action LogInWithMasterPasswordAction { get; set; }
|
||||||
|
public Action LogInWithDeviceAction { get; set; }
|
||||||
|
public Action RequestAdminApprovalAction { get; set; }
|
||||||
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
|
public LoginApproveDeviceViewModel()
|
||||||
|
{
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
|
_apiService = ServiceContainer.Resolve<IApiService>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
|
||||||
|
PageTitle = AppResources.LoggedIn;
|
||||||
|
|
||||||
|
ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
ContinueCommand = new AsyncCommand(InitAsync,
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
|
||||||
|
|
||||||
|
public bool RememberThisDevice
|
||||||
|
{
|
||||||
|
get => _rememberThisDevice;
|
||||||
|
set => SetProperty(ref _rememberThisDevice, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ApproveWithMyOtherDeviceEnabled
|
||||||
|
{
|
||||||
|
get => _approveWithMyOtherDeviceEnabled;
|
||||||
|
set => SetProperty(ref _approveWithMyOtherDeviceEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RequestAdminApprovalEnabled
|
||||||
|
{
|
||||||
|
get => _requestAdminApprovalEnabled;
|
||||||
|
set => SetProperty(ref _requestAdminApprovalEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ApproveWithMasterPasswordEnabled
|
||||||
|
{
|
||||||
|
get => _approveWithMasterPasswordEnabled;
|
||||||
|
set => SetProperty(ref _approveWithMasterPasswordEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContinueEnabled
|
||||||
|
{
|
||||||
|
get => _continueEnabled;
|
||||||
|
set => SetProperty(ref _continueEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Email
|
||||||
|
{
|
||||||
|
get => _email;
|
||||||
|
set => SetProperty(ref _email, value, additionalPropertyNames:
|
||||||
|
new string[] {
|
||||||
|
nameof(LoggingInAsText)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Email = await _stateService.GetRememberedEmailAsync();
|
||||||
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
|
RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
|
||||||
|
ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
|
||||||
|
ApproveWithMyOtherDeviceEnabled = decryptOptions?.TrustedDeviceOption?.HasLoginApprovingDevice ?? false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
HandleException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change this expression to, Appear if the browser is trusted and shared the key with the app
|
||||||
|
ContinueEnabled = !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled && !ApproveWithMyOtherDeviceEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetDeviceTrustAndInvokeAsync(Action action)
|
||||||
|
{
|
||||||
|
await _deviceTrustCryptoService.SetShouldTrustDeviceAsync(RememberThisDevice);
|
||||||
|
await Device.InvokeOnMainThreadAsync(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,8 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}" />
|
AutomationProperties.Name="{u:I18n Account}"
|
||||||
|
AutomationId="AccountIconButton" />
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
x:Name="_moreItem" x:Key="moreItem"
|
x:Name="_moreItem" x:Key="moreItem"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
AutomationId="OptionsButton"/>
|
AutomationId="OptionsButton" />
|
||||||
<ToolbarItem Text="{u:I18n GetPasswordHint}"
|
<ToolbarItem Text="{u:I18n GetPasswordHint}"
|
||||||
x:Key="getPasswordHint"
|
x:Key="getPasswordHint"
|
||||||
x:Name="_getPasswordHint"
|
x:Name="_getPasswordHint"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Bit.App.Models;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -135,7 +136,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task StartLoginWithDeviceAsync()
|
private async Task StartLoginWithDeviceAsync()
|
||||||
{
|
{
|
||||||
var page = new LoginPasswordlessRequestPage(_vm.Email, _appOptions);
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,8 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding LogInAttemptByLabel}"
|
Text="{Binding LogInAttemptByLabel}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,24"/>
|
Margin="0,0,0,24"
|
||||||
|
AutomationId="LogInAttemptByLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n FingerprintPhrase}"
|
Text="{u:I18n FingerprintPhrase}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
@@ -41,7 +42,8 @@
|
|||||||
FormattedText="{Binding LoginRequest.FingerprintPhrase}"
|
FormattedText="{Binding LoginRequest.FingerprintPhrase}"
|
||||||
FontSize="Medium"
|
FontSize="Medium"
|
||||||
TextColor="{DynamicResource FingerprintPhrase}"
|
TextColor="{DynamicResource FingerprintPhrase}"
|
||||||
Margin="0,0,0,27"/>
|
Margin="0,0,0,27"
|
||||||
|
AutomationId="FingerprintValueLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DeviceType}"
|
Text="{u:I18n DeviceType}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
@@ -49,7 +51,8 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding LoginRequest.DeviceType}"
|
Text="{Binding LoginRequest.DeviceType}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,21"/>
|
Margin="0,0,0,21"
|
||||||
|
AutomationId="DeviceTypeValueLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n IpAddress}"
|
Text="{u:I18n IpAddress}"
|
||||||
IsVisible="{Binding ShowIpAddress}"
|
IsVisible="{Binding ShowIpAddress}"
|
||||||
@@ -59,7 +62,8 @@
|
|||||||
Text="{Binding LoginRequest.IpAddress}"
|
Text="{Binding LoginRequest.IpAddress}"
|
||||||
IsVisible="{Binding ShowIpAddress}"
|
IsVisible="{Binding ShowIpAddress}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,21"/>
|
Margin="0,0,0,21"
|
||||||
|
AutomationId="IpAddressValueLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Time}"
|
Text="{u:I18n Time}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
@@ -67,7 +71,8 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding TimeOfRequestText}"
|
Text="{Binding TimeOfRequestText}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,57"/>
|
Margin="0,0,0,57"
|
||||||
|
AutomationId="TimeOfRequestValueLabel" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
@@ -75,11 +80,13 @@
|
|||||||
Text="{u:I18n ConfirmLogIn}"
|
Text="{u:I18n ConfirmLogIn}"
|
||||||
Command="{Binding AcceptRequestCommand}"
|
Command="{Binding AcceptRequestCommand}"
|
||||||
Margin="0,0,0,17"
|
Margin="0,0,0,17"
|
||||||
StyleClass="btn-primary"/>
|
StyleClass="btn-primary"
|
||||||
|
AutomationId="ConfirmLoginButton" />
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n DenyLogIn}"
|
Text="{u:I18n DenyLogIn}"
|
||||||
Command="{Binding RejectRequestCommand}"
|
Command="{Binding RejectRequestCommand}"
|
||||||
StyleClass="btn-secundary"/>
|
StyleClass="btn-secundary"
|
||||||
|
AutomationId="DenyLoginButton" />
|
||||||
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -21,16 +21,17 @@
|
|||||||
<StackLayout
|
<StackLayout
|
||||||
Padding="7, 0, 7, 20">
|
Padding="7, 0, 7, 20">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n LogInInitiated}"
|
Text="{Binding Tittle}"
|
||||||
FontSize="Title"
|
FontSize="Title"
|
||||||
FontAttributes="Bold"
|
FontAttributes="Bold"
|
||||||
Margin="0,14,0,21"/>
|
Margin="0,14,0,21"
|
||||||
|
AutomationId="LogInInitiatedLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
|
Text="{Binding SubTittle}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,10"/>
|
Margin="0,0,0,10"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
|
Text="{Binding Description}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,24"/>
|
Margin="0,0,0,24"/>
|
||||||
<Label
|
<Label
|
||||||
@@ -40,13 +41,16 @@
|
|||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
FormattedText="{Binding FingerprintPhrase}"
|
FormattedText="{Binding FingerprintPhrase}"
|
||||||
FontSize="Medium"
|
FontSize="Medium"
|
||||||
TextColor="{DynamicResource FingerprintPhrase}"/>
|
TextColor="{DynamicResource FingerprintPhrase}"
|
||||||
|
AutomationId="FingerprintPhraseValue" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ResendNotification}"
|
Text="{u:I18n ResendNotification}"
|
||||||
|
IsVisible="{Binding ResendNotificationVisible}"
|
||||||
StyleClass="text-md"
|
StyleClass="text-md"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
Margin="0,40,0,0"
|
Margin="0,40,0,0"
|
||||||
TextColor="{DynamicResource HyperlinkColor}">
|
TextColor="{DynamicResource HyperlinkColor}"
|
||||||
|
AutomationId="ResendNotificationButton">
|
||||||
<Label.GestureRecognizers>
|
<Label.GestureRecognizers>
|
||||||
<TapGestureRecognizer Command="{Binding CreatePasswordlessLoginCommand}" />
|
<TapGestureRecognizer Command="{Binding CreatePasswordlessLoginCommand}" />
|
||||||
</Label.GestureRecognizers>
|
</Label.GestureRecognizers>
|
||||||
@@ -55,7 +59,7 @@
|
|||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Margin="0,30,0,0">
|
Margin="0,30,0,0">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n NeedAnotherOption}"
|
Text="{Binding OtherOptions}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
VerticalTextAlignment="End"/>
|
VerticalTextAlignment="End"/>
|
||||||
<Label
|
<Label
|
||||||
@@ -64,7 +68,8 @@
|
|||||||
VerticalTextAlignment="End"
|
VerticalTextAlignment="End"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Margin="5, 0"
|
Margin="5, 0"
|
||||||
TextColor="{DynamicResource HyperlinkColor}">
|
TextColor="{DynamicResource HyperlinkColor}"
|
||||||
|
AutomationId="ViewAllLoginOptionsButton">
|
||||||
<Label.GestureRecognizers>
|
<Label.GestureRecognizers>
|
||||||
<TapGestureRecognizer Command="{Binding CloseCommand}" />
|
<TapGestureRecognizer Command="{Binding CloseCommand}" />
|
||||||
</Label.GestureRecognizers>
|
</Label.GestureRecognizers>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -12,13 +13,14 @@ namespace Bit.App.Pages
|
|||||||
private LoginPasswordlessRequestViewModel _vm;
|
private LoginPasswordlessRequestViewModel _vm;
|
||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null)
|
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.Email = email;
|
_vm.Email = email;
|
||||||
|
_vm.AuthRequestType = authRequestType;
|
||||||
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||||
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -12,6 +13,7 @@ using Bit.Core;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -32,6 +34,9 @@ namespace Bit.App.Pages
|
|||||||
private IPlatformUtilsService _platformUtilsService;
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
private IEnvironmentService _environmentService;
|
private IEnvironmentService _environmentService;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
|
|
||||||
protected override II18nService i18nService => _i18nService;
|
protected override II18nService i18nService => _i18nService;
|
||||||
protected override IEnvironmentService environmentService => _environmentService;
|
protected override IEnvironmentService environmentService => _environmentService;
|
||||||
@@ -44,6 +49,7 @@ namespace Bit.App.Pages
|
|||||||
private string _email;
|
private string _email;
|
||||||
private string _requestId;
|
private string _requestId;
|
||||||
private string _requestAccessCode;
|
private string _requestAccessCode;
|
||||||
|
private AuthRequestType _authRequestType;
|
||||||
// Item1 publicKey, Item2 privateKey
|
// Item1 publicKey, Item2 privateKey
|
||||||
private Tuple<byte[], byte[]> _requestKeyPair;
|
private Tuple<byte[], byte[]> _requestKeyPair;
|
||||||
|
|
||||||
@@ -57,6 +63,9 @@ namespace Bit.App.Pages
|
|||||||
_i18nService = ServiceContainer.Resolve<II18nService>();
|
_i18nService = ServiceContainer.Resolve<II18nService>();
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
_logger = ServiceContainer.Resolve<ILogger>();
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
|
||||||
|
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
||||||
|
|
||||||
PageTitle = AppResources.LogInWithAnotherDevice;
|
PageTitle = AppResources.LogInWithAnotherDevice;
|
||||||
|
|
||||||
@@ -77,6 +86,70 @@ namespace Bit.App.Pages
|
|||||||
public ICommand CreatePasswordlessLoginCommand { get; }
|
public ICommand CreatePasswordlessLoginCommand { get; }
|
||||||
public ICommand CloseCommand { get; }
|
public ICommand CloseCommand { get; }
|
||||||
|
|
||||||
|
public string Tittle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.LogInInitiated;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.AdminApprovalRequested;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SubTittle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.ANotificationHasBeenSentToYourDevice;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.YourRequestHasBeenSentToYourAdmin;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.YouWillBeNotifiedOnceApproved;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string OtherOptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.NeedAnotherOption;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.TroubleLoggingIn;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string FingerprintPhrase
|
public string FingerprintPhrase
|
||||||
{
|
{
|
||||||
get => _fingerprintPhrase;
|
get => _fingerprintPhrase;
|
||||||
@@ -89,6 +162,21 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _email, value);
|
set => SetProperty(ref _email, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthRequestType AuthRequestType
|
||||||
|
{
|
||||||
|
get => _authRequestType;
|
||||||
|
set => SetProperty(ref _authRequestType, value, additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(Tittle),
|
||||||
|
nameof(SubTittle),
|
||||||
|
nameof(Description),
|
||||||
|
nameof(OtherOptions),
|
||||||
|
nameof(ResendNotificationVisible)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ResendNotificationVisible => AuthRequestType == AuthRequestType.AuthenticateAndUnlock;
|
||||||
|
|
||||||
public void StartCheckLoginRequestStatus()
|
public void StartCheckLoginRequestStatus()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -119,14 +207,22 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task CheckLoginRequestStatus()
|
private async Task CheckLoginRequestStatus()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_requestAccessCode))
|
if (string.IsNullOrEmpty(_requestId))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _authService.GetPasswordlessLoginResponseAsync(_requestId, _requestAccessCode);
|
PasswordlessLoginResponse response = null;
|
||||||
|
if (await _stateService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.RequestApproved == null || !response.RequestApproved.Value)
|
if (response.RequestApproved == null || !response.RequestApproved.Value)
|
||||||
{
|
{
|
||||||
@@ -138,6 +234,12 @@ namespace Bit.App.Pages
|
|||||||
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
|
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
|
||||||
|
if (authResult == null && await _stateService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
await HandleLoginCompleteAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
|
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -153,8 +255,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
await HandleLoginCompleteAsync();
|
||||||
LogInSuccessAction?.Invoke();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -164,22 +265,66 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleLoginCompleteAsync()
|
||||||
|
{
|
||||||
|
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||||
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
|
LogInSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CreatePasswordlessLoginAsync()
|
private async Task CreatePasswordlessLoginAsync()
|
||||||
{
|
{
|
||||||
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
||||||
|
|
||||||
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
|
PasswordlessLoginResponse response = null;
|
||||||
if (response != null)
|
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
||||||
|
if (pendingRequest != null && _authRequestType == AuthRequestType.AdminApproval)
|
||||||
{
|
{
|
||||||
FingerprintPhrase = response.FingerprintPhrase;
|
response = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
|
||||||
_requestId = response.Id;
|
if (response == null || (response.IsAnswered && !response.RequestApproved.Value))
|
||||||
_requestAccessCode = response.RequestAccessCode;
|
{
|
||||||
_requestKeyPair = response.RequestKeyPair;
|
// handle pending auth request not valid remove it from state
|
||||||
|
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
||||||
|
pendingRequest = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Derive pubKey from privKey in state to avoid MITM attacks
|
||||||
|
// Also generate FingerprintPhrase locally for the same reason
|
||||||
|
var derivedPublicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(pendingRequest.PrivateKey);
|
||||||
|
response.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(Email, derivedPublicKey));
|
||||||
|
response.RequestKeyPair = new Tuple<byte[], byte[]>(derivedPublicKey, pendingRequest.PrivateKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
|
||||||
|
}
|
||||||
|
|
||||||
|
await HandlePasswordlessLoginAsync(response, pendingRequest == null && _authRequestType == AuthRequestType.AdminApproval);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandlePasswordlessLoginAsync(PasswordlessLoginResponse response, bool createPendingAdminRequest)
|
||||||
|
{
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createPendingAdminRequest)
|
||||||
|
{
|
||||||
|
var pendingAuthRequest = new PendingAdminAuthRequest { Id = response.Id, PrivateKey = response.RequestKeyPair.Item2 };
|
||||||
|
await _stateService.SetPendingAdminAuthRequestAsync(pendingAuthRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
FingerprintPhrase = response.FingerprintPhrase;
|
||||||
|
_requestId = response.Id;
|
||||||
|
_requestAccessCode = response.RequestAccessCode;
|
||||||
|
_requestKeyPair = response.RequestKeyPair;
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleException(Exception ex)
|
private void HandleException(Exception ex)
|
||||||
{
|
{
|
||||||
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ namespace Bit.App.Pages
|
|||||||
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
|
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction =
|
_vm.UpdateTempPasswordAction =
|
||||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
|
_vm.StartDeviceApprovalOptionsAction =
|
||||||
|
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
||||||
_vm.CloseAction = async () =>
|
_vm.CloseAction = async () =>
|
||||||
{
|
{
|
||||||
await Navigation.PopModalAsync();
|
await Navigation.PopModalAsync();
|
||||||
@@ -106,10 +108,17 @@ namespace Bit.App.Pages
|
|||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartDeviceApprovalOptionsAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginApproveDevicePage();
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SsoAuthSuccessAsync()
|
private async Task SsoAuthSuccessAsync()
|
||||||
{
|
{
|
||||||
RestoreAppOptionsFromCopy();
|
RestoreAppOptionsFromCopy();
|
||||||
await AppHelpers.ClearPreviousPage();
|
await AppHelpers.ClearPreviousPage();
|
||||||
|
|
||||||
if (await _vaultTimeoutService.IsLockedAsync())
|
if (await _vaultTimeoutService.IsLockedAsync())
|
||||||
{
|
{
|
||||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
@@ -29,6 +30,8 @@ namespace Bit.App.Pages
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
|
|
||||||
private string _orgIdentifier;
|
private string _orgIdentifier;
|
||||||
|
|
||||||
@@ -45,7 +48,8 @@ namespace Bit.App.Pages
|
|||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
||||||
|
|
||||||
PageTitle = AppResources.Bitwarden;
|
PageTitle = AppResources.Bitwarden;
|
||||||
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
|
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
|
||||||
@@ -61,6 +65,7 @@ namespace Bit.App.Pages
|
|||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action StartSetPasswordAction { get; set; }
|
public Action StartSetPasswordAction { get; set; }
|
||||||
public Action SsoAuthSuccessAction { get; set; }
|
public Action SsoAuthSuccessAction { get; set; }
|
||||||
|
public Action StartDeviceApprovalOptionsAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
|
|
||||||
@@ -197,6 +202,7 @@ namespace Bit.App.Pages
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
|
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
|
||||||
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
|
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
@@ -212,9 +218,31 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
UpdateTempPasswordAction?.Invoke();
|
UpdateTempPasswordAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
else if (decryptOptions?.TrustedDeviceOption != null)
|
||||||
|
{
|
||||||
|
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||||
|
if (!decryptOptions.HasMasterPassword &&
|
||||||
|
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
||||||
|
{
|
||||||
|
StartSetPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (response.ForcePasswordReset)
|
||||||
|
{
|
||||||
|
UpdateTempPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
|
||||||
|
{
|
||||||
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
|
SsoAuthSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartDeviceApprovalOptionsAction?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
SsoAuthSuccessAction?.Invoke();
|
SsoAuthSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,25 +177,28 @@ namespace Bit.App.Pages
|
|||||||
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
|
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
|
||||||
Email = Email.Trim().ToLower();
|
Email = Email.Trim().ToLower();
|
||||||
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
||||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig);
|
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
|
||||||
var encKey = await _cryptoService.MakeEncKeyAsync(key);
|
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
|
||||||
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
newMasterKey,
|
||||||
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
await _cryptoService.MakeUserKeyAsync()
|
||||||
|
);
|
||||||
|
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey);
|
||||||
|
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
||||||
var request = new RegisterRequest
|
var request = new RegisterRequest
|
||||||
{
|
{
|
||||||
Email = Email,
|
Email = Email,
|
||||||
Name = Name,
|
Name = Name,
|
||||||
MasterPasswordHash = hashedPassword,
|
MasterPasswordHash = hashedPassword,
|
||||||
MasterPasswordHint = Hint,
|
MasterPasswordHint = Hint,
|
||||||
Key = encKey.Item2.EncryptedString,
|
Key = newProtectedUserKey.EncryptedString,
|
||||||
Kdf = kdfConfig.Type,
|
Kdf = kdfConfig.Type,
|
||||||
KdfIterations = kdfConfig.Iterations,
|
KdfIterations = kdfConfig.Iterations,
|
||||||
KdfMemory = kdfConfig.Memory,
|
KdfMemory = kdfConfig.Memory,
|
||||||
KdfParallelism = kdfConfig.Parallelism,
|
KdfParallelism = kdfConfig.Parallelism,
|
||||||
Keys = new KeysRequest
|
Keys = new KeysRequest
|
||||||
{
|
{
|
||||||
PublicKey = keys.Item1,
|
PublicKey = newPublicKey,
|
||||||
EncryptedPrivateKey = keys.Item2.EncryptedString
|
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
||||||
},
|
},
|
||||||
CaptchaResponse = _captchaToken,
|
CaptchaResponse = _captchaToken,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -165,26 +165,18 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
||||||
var email = await _stateService.GetEmailAsync();
|
var email = await _stateService.GetEmailAsync();
|
||||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
||||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
|
||||||
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
|
||||||
|
|
||||||
Tuple<SymmetricCryptoKey, EncString> encKey;
|
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
|
||||||
var existingEncKey = await _cryptoService.GetEncKeyAsync();
|
await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());
|
||||||
if (existingEncKey == null)
|
|
||||||
{
|
|
||||||
encKey = await _cryptoService.MakeEncKeyAsync(key);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
encKey = await _cryptoService.RemakeEncKeyAsync(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
var keys = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
||||||
var request = new SetPasswordRequest
|
var request = new SetPasswordRequest
|
||||||
{
|
{
|
||||||
MasterPasswordHash = masterPasswordHash,
|
MasterPasswordHash = masterPasswordHash,
|
||||||
Key = encKey.Item2.EncryptedString,
|
Key = newProtectedUserKey.EncryptedString,
|
||||||
MasterPasswordHint = Hint,
|
MasterPasswordHint = Hint,
|
||||||
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
|
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
|
||||||
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
|
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
|
||||||
@@ -204,19 +196,19 @@ namespace Bit.App.Pages
|
|||||||
// Set Password and relevant information
|
// Set Password and relevant information
|
||||||
await _apiService.SetPasswordAsync(request);
|
await _apiService.SetPasswordAsync(request);
|
||||||
await _stateService.SetKdfConfigurationAsync(kdfConfig);
|
await _stateService.SetKdfConfigurationAsync(kdfConfig);
|
||||||
await _cryptoService.SetKeyAsync(key);
|
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
||||||
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
|
await _cryptoService.SetPasswordHashAsync(localMasterPasswordHash);
|
||||||
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
|
||||||
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
|
await _cryptoService.SetPrivateKeyAsync(keys.Item2.EncryptedString);
|
||||||
|
|
||||||
if (ResetPasswordAutoEnroll)
|
if (ResetPasswordAutoEnroll)
|
||||||
{
|
{
|
||||||
// Grab Organization Keys
|
// Grab Organization Keys
|
||||||
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
|
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
|
||||||
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
|
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
|
||||||
// Grab user's Encryption Key and encrypt with Org Public Key
|
// Grab User Key and encrypt with Org Public Key
|
||||||
var userEncKey = await _cryptoService.GetEncKeyAsync();
|
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey);
|
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, publicKey);
|
||||||
// Request
|
// Request
|
||||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ namespace Bit.App.Pages
|
|||||||
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
|
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction =
|
_vm.UpdateTempPasswordAction =
|
||||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
|
_vm.StartDeviceApprovalOptionsAction =
|
||||||
|
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
||||||
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
||||||
DuoWebView = _duoWebView;
|
DuoWebView = _duoWebView;
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
@@ -180,6 +182,12 @@ namespace Bit.App.Pages
|
|||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartDeviceApprovalOptionsAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginApproveDevicePage();
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TwoFactorAuthSuccessAsync()
|
private async Task TwoFactorAuthSuccessAsync()
|
||||||
{
|
{
|
||||||
if (_authingWithSso)
|
if (_authingWithSso)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -33,7 +34,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly IAppIdService _appIdService;
|
private readonly IAppIdService _appIdService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
private TwoFactorProviderType? _selectedProviderType;
|
private TwoFactorProviderType? _selectedProviderType;
|
||||||
private string _totpInstruction;
|
private string _totpInstruction;
|
||||||
private string _webVaultUrl = "https://vault.bitwarden.com";
|
private string _webVaultUrl = "https://vault.bitwarden.com";
|
||||||
@@ -55,6 +56,7 @@ namespace Bit.App.Pages
|
|||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>();
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
|
||||||
PageTitle = AppResources.TwoStepLogin;
|
PageTitle = AppResources.TwoStepLogin;
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
@@ -118,6 +120,7 @@ namespace Bit.App.Pages
|
|||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public ICommand MoreCommand { get; }
|
public ICommand MoreCommand { get; }
|
||||||
public Action TwoFactorAuthSuccessAction { get; set; }
|
public Action TwoFactorAuthSuccessAction { get; set; }
|
||||||
|
public Action StartDeviceApprovalOptionsAction { get; set; }
|
||||||
public Action StartSetPasswordAction { get; set; }
|
public Action StartSetPasswordAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
@@ -315,6 +318,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
_messagingService.Send("listenYubiKeyOTP", false);
|
_messagingService.Send("listenYubiKeyOTP", false);
|
||||||
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
||||||
|
|
||||||
@@ -326,6 +330,27 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
UpdateTempPasswordAction?.Invoke();
|
UpdateTempPasswordAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
else if (decryptOptions?.TrustedDeviceOption != null)
|
||||||
|
{
|
||||||
|
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
||||||
|
if (!decryptOptions.HasMasterPassword &&
|
||||||
|
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
||||||
|
{
|
||||||
|
StartSetPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (result.ForcePasswordReset)
|
||||||
|
{
|
||||||
|
UpdateTempPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
|
||||||
|
{
|
||||||
|
TwoFactorAuthSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartDeviceApprovalOptionsAction?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TwoFactorAuthSuccessAction?.Invoke();
|
TwoFactorAuthSuccessAction?.Invoke();
|
||||||
|
|||||||
@@ -93,12 +93,12 @@ namespace Bit.App.Pages
|
|||||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||||
var email = await _stateService.GetEmailAsync();
|
var email = await _stateService.GetEmailAsync();
|
||||||
|
|
||||||
// Create new key and hash new password
|
// Create new master key and hash new password
|
||||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
||||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey);
|
||||||
|
|
||||||
// Create new encKey for the User
|
// Encrypt user key with new master key
|
||||||
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
|
var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
|
||||||
|
|
||||||
// Initiate API action
|
// Initiate API action
|
||||||
try
|
try
|
||||||
@@ -108,10 +108,10 @@ namespace Bit.App.Pages
|
|||||||
switch (_reason)
|
switch (_reason)
|
||||||
{
|
{
|
||||||
case ForcePasswordResetReason.AdminForcePasswordReset:
|
case ForcePasswordResetReason.AdminForcePasswordReset:
|
||||||
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
await UpdateTempPasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
||||||
break;
|
break;
|
||||||
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
||||||
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
Clicked="Clear_Clicked"
|
Clicked="Clear_Clicked"
|
||||||
Order="Secondary"
|
Order="Secondary"
|
||||||
x:Name="_clearItem"
|
x:Name="_clearItem"
|
||||||
x:Key="clearItem" />
|
x:Key="clearItem"
|
||||||
|
AutomationId="ClearPasswordList" />
|
||||||
<ToolbarItem Icon="more_vert.png"
|
<ToolbarItem Icon="more_vert.png"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
@@ -43,7 +44,8 @@
|
|||||||
Margin="20, 0"
|
Margin="20, 0"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="CenterAndExpand"
|
HorizontalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="Center"></Label>
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="NoPasswordsDisplayedLabel"></Label>
|
||||||
<controls:ExtendedCollectionView
|
<controls:ExtendedCollectionView
|
||||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||||
ItemsSource="{Binding History}"
|
ItemsSource="{Binding History}"
|
||||||
@@ -56,7 +58,8 @@
|
|||||||
StyleClass="list-row, list-row-platform"
|
StyleClass="list-row, list-row-platform"
|
||||||
Padding="10"
|
Padding="10"
|
||||||
RowSpacing="0"
|
RowSpacing="0"
|
||||||
ColumnSpacing="10">
|
ColumnSpacing="10"
|
||||||
|
AutomationId="GeneratedPasswordRow">
|
||||||
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -71,12 +74,14 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
StyleClass="list-title, list-title-platform, text-html"
|
StyleClass="list-title, list-title-platform, text-html"
|
||||||
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}" />
|
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}"
|
||||||
|
AutomationId="GeneratedPasswordValue" />
|
||||||
<Label LineBreakMode="TailTruncation"
|
<Label LineBreakMode="TailTruncation"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
StyleClass="list-subtitle, list-subtitle-platform"
|
StyleClass="list-subtitle, list-subtitle-platform"
|
||||||
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}" />
|
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}"
|
||||||
|
AutomationId="GeneratedPasswordDateLabel" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="list-row-button, list-row-button-platform"
|
StyleClass="list-row-button, list-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Paste}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Paste}}"
|
||||||
@@ -86,7 +91,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyPassword}" />
|
AutomationProperties.Name="{u:I18n CopyPassword}"
|
||||||
|
AutomationId="CopyPasswordValueButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
|
|||||||
@@ -71,7 +71,8 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordGeneratorPolicyInEffect}"
|
Text="{u:I18n PasswordGeneratorPolicyInEffect}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="PasswordGeneratorPolicyInEffectLabel" />
|
||||||
</Frame>
|
</Frame>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid IsVisible="{Binding IsUsername, Converter={StaticResource inverseBool}}"
|
<Grid IsVisible="{Binding IsUsername, Converter={StaticResource inverseBool}}"
|
||||||
@@ -82,21 +83,24 @@
|
|||||||
x:Name="lblPassword"
|
x:Name="lblPassword"
|
||||||
StyleClass="text-lg, text-html"
|
StyleClass="text-lg, text-html"
|
||||||
Text="{Binding ColoredPassword, Mode=OneWay}"
|
Text="{Binding ColoredPassword, Mode=OneWay}"
|
||||||
Margin="0, 20" />
|
Margin="0, 20"
|
||||||
|
AutomationId="GeneratedPasswordLabel" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
Command="{Binding CopyCommand}"
|
Command="{Binding CopyCommand}"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyPassword}" />
|
AutomationProperties.Name="{u:I18n CopyPassword}"
|
||||||
|
AutomationId="CopyValueButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
||||||
Command="{Binding RegenerateCommand}"
|
Command="{Binding RegenerateCommand}"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n GeneratePassword}" />
|
AutomationProperties.Name="{u:I18n GeneratePassword}"
|
||||||
|
AutomationId="RegenerateValueButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid IsVisible="{Binding IsUsername}"
|
<Grid IsVisible="{Binding IsUsername}"
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
@@ -107,21 +111,24 @@
|
|||||||
StyleClass="text-lg, text-html"
|
StyleClass="text-lg, text-html"
|
||||||
Text="{Binding ColoredUsername, Mode=OneWay}"
|
Text="{Binding ColoredUsername, Mode=OneWay}"
|
||||||
Margin="0, 20"
|
Margin="0, 20"
|
||||||
HorizontalOptions="Start" />
|
HorizontalOptions="Start"
|
||||||
|
AutomationId="GeneratedPasswordLabel" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
Command="{Binding CopyCommand}"
|
Command="{Binding CopyCommand}"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyUsername}" />
|
AutomationProperties.Name="{u:I18n CopyUsername}"
|
||||||
|
AutomationId="CopyValueButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
||||||
Command="{Binding RegenerateUsernameCommand}"
|
Command="{Binding RegenerateUsernameCommand}"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n GenerateUsername}" />
|
AutomationProperties.Name="{u:I18n GenerateUsername}"
|
||||||
|
AutomationId="RegenerateValueButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator"/>
|
<BoxView StyleClass="box-row-separator"/>
|
||||||
<StackLayout StyleClass="box"
|
<StackLayout StyleClass="box"
|
||||||
@@ -135,7 +142,8 @@
|
|||||||
ItemsSource="{Binding GeneratorTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding GeneratorTypeOptions, Mode=OneTime}"
|
||||||
SelectedItem="{Binding GeneratorTypeSelected}"
|
SelectedItem="{Binding GeneratorTypeSelected}"
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="GeneratorTypePicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label Text="{u:I18n Options, Header=True}"
|
<Label Text="{u:I18n Options, Header=True}"
|
||||||
StyleClass="box-header, box-header-platform"
|
StyleClass="box-header, box-header-platform"
|
||||||
@@ -161,7 +169,8 @@
|
|||||||
ItemsSource="{Binding UsernameTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding UsernameTypeOptions, Mode=OneTime}"
|
||||||
SelectedItem="{Binding UsernameTypeSelected}"
|
SelectedItem="{Binding UsernameTypeSelected}"
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="UsernameTypePicker" />
|
||||||
<Label
|
<Label
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
Text="{Binding UsernameTypeDescriptionLabel}" />
|
Text="{Binding UsernameTypeDescriptionLabel}" />
|
||||||
@@ -172,7 +181,8 @@
|
|||||||
StyleClass="box-label" />
|
StyleClass="box-label" />
|
||||||
<Entry x:Name="_plusAddressedEmailEntry"
|
<Entry x:Name="_plusAddressedEmailEntry"
|
||||||
Text="{Binding PlusAddressedEmail}"
|
Text="{Binding PlusAddressedEmail}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="PlusAddressedEmailEntry" />
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
||||||
Text="{u:I18n EmailType}"
|
Text="{u:I18n EmailType}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
@@ -203,7 +213,8 @@
|
|||||||
<Entry
|
<Entry
|
||||||
x:Name="_catchAllEmailDomainNameEntry"
|
x:Name="_catchAllEmailDomainNameEntry"
|
||||||
Text="{Binding CatchAllEmailDomain}"
|
Text="{Binding CatchAllEmailDomain}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="CatchAllEmailDomainEntry" />
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
||||||
Text="{u:I18n EmailType}"
|
Text="{u:I18n EmailType}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
@@ -236,26 +247,27 @@
|
|||||||
ItemsSource="{Binding ForwardedEmailServiceTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding ForwardedEmailServiceTypeOptions, Mode=OneTime}"
|
||||||
SelectedItem="{Binding ForwardedEmailServiceSelected}"
|
SelectedItem="{Binding ForwardedEmailServiceSelected}"
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
<!--ANONADDY OPTIONS-->
|
AutomationId="ServiceTypePicker" />
|
||||||
<Grid IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
<Grid
|
||||||
Grid.RowDefinitions="Auto,*"
|
Grid.RowDefinitions="Auto,*"
|
||||||
Grid.ColumnDefinitions="*,Auto">
|
Grid.ColumnDefinitions="*,Auto">
|
||||||
<Label
|
<Label
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
Text="{u:I18n APIAccessToken}"
|
Text="{Binding ForwardedEmailApiSecretLabel}"
|
||||||
StyleClass="box-label"/>
|
StyleClass="box-label"/>
|
||||||
<Entry
|
<Entry
|
||||||
x:Name="_anonAddyApiAccessTokenEntry"
|
Text="{Binding ForwardedEmailApiSecret}"
|
||||||
Text="{Binding AnonAddyApiAccessToken}"
|
IsPassword="{Binding ShowForwardedEmailApiSecret, Converter={StaticResource inverseBool}}"
|
||||||
IsPassword="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool}}"
|
Grid.Row="1"
|
||||||
Grid.Row="1"/>
|
AutomationId="ForwardedEmailApiSecretEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
Text="{Binding ShowForwardedEmailApiSecret, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"/>
|
Grid.Column="1"
|
||||||
|
AutomationId="ShowForwardedEmailApiSecretButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
<Label IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
||||||
Text="{u:I18n DomainNameRequiredParenthesis}"
|
Text="{u:I18n DomainNameRequiredParenthesis}"
|
||||||
@@ -264,91 +276,8 @@
|
|||||||
<Entry IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
<Entry IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
||||||
x:Name="_anonAddyDomainNameEntry"
|
x:Name="_anonAddyDomainNameEntry"
|
||||||
Text="{Binding AnonAddyDomainName}"
|
Text="{Binding AnonAddyDomainName}"
|
||||||
StyleClass="box-value"/>
|
StyleClass="box-value"
|
||||||
<!--FIREFOX RELAY OPTIONS-->
|
AutomationId="AnonAddyDomainNameEntry" />
|
||||||
<Grid StyleClass="box-row, box-row-input"
|
|
||||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.FirefoxRelay}}"
|
|
||||||
Grid.RowDefinitions="Auto,*"
|
|
||||||
Grid.ColumnDefinitions="*,Auto">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n APIAccessToken}"
|
|
||||||
StyleClass="box-label"/>
|
|
||||||
<Entry
|
|
||||||
x:Name="_firefoxRelayApiAccessTokenEntry"
|
|
||||||
Text="{Binding FirefoxRelayApiAccessToken}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
IsPassword="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool}}"/>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
|
||||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="1"/>
|
|
||||||
</Grid>
|
|
||||||
<!--SIMPLELOGIN OPTIONS-->
|
|
||||||
<Grid StyleClass="box-row, box-row-input"
|
|
||||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.SimpleLogin}}"
|
|
||||||
Grid.RowDefinitions="Auto,*"
|
|
||||||
Grid.ColumnDefinitions="*,Auto">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n APIKeyRequiredParenthesis}"
|
|
||||||
StyleClass="box-label"/>
|
|
||||||
<Entry
|
|
||||||
x:Name="_simpleLoginApiKeyEntry"
|
|
||||||
Text="{Binding SimpleLoginApiKey}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
IsPassword="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool}}"/>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
|
||||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="1"/>
|
|
||||||
</Grid>
|
|
||||||
<!--DUCKDUCKGO OPTIONS-->
|
|
||||||
<Grid StyleClass="box-row, box-row-input"
|
|
||||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.DuckDuckGo}}"
|
|
||||||
Grid.RowDefinitions="Auto,*"
|
|
||||||
Grid.ColumnDefinitions="*,Auto">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n APIKeyRequiredParenthesis}"
|
|
||||||
StyleClass="box-label"/>
|
|
||||||
<Entry
|
|
||||||
x:Name="_duckDuckGoApiAccessTokenEntry"
|
|
||||||
Text="{Binding DuckDuckGoApiKey}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
IsPassword="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool}}"/>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
|
||||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="1"/>
|
|
||||||
</Grid>
|
|
||||||
<!--FASTMAIL OPTIONS-->
|
|
||||||
<Grid StyleClass="box-row, box-row-input"
|
|
||||||
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.Fastmail}}"
|
|
||||||
Grid.RowDefinitions="Auto,*"
|
|
||||||
Grid.ColumnDefinitions="*,Auto">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n APIKeyRequiredParenthesis}"
|
|
||||||
StyleClass="box-label"/>
|
|
||||||
<Entry
|
|
||||||
x:Name="_fastmailApiAccessTokenEntry"
|
|
||||||
Text="{Binding FastmailApiKey}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
IsPassword="{Binding ShowFastmailApiKey, Converter={StaticResource inverseBool}}"/>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding ShowFastmailApiKey, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
|
||||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="1"/>
|
|
||||||
</Grid>
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<!--RANDOM WORD OPTIONS-->
|
<!--RANDOM WORD OPTIONS-->
|
||||||
<Grid IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}">
|
<Grid IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}">
|
||||||
@@ -359,7 +288,8 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding CapitalizeRandomWordUsername}"
|
IsToggled="{Binding CapitalizeRandomWordUsername}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="CapitalizeRandomWordUsernameToggle" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
||||||
StyleClass="box-row-separator" />
|
StyleClass="box-row-separator" />
|
||||||
@@ -371,7 +301,8 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding IncludeNumberRandomWordUsername}"
|
IsToggled="{Binding IncludeNumberRandomWordUsername}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="IncludeNumberRandomWordUsernameToggle" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
||||||
StyleClass="box-row-separator" />
|
StyleClass="box-row-separator" />
|
||||||
@@ -386,7 +317,8 @@
|
|||||||
x:Name="_passwordTypePicker"
|
x:Name="_passwordTypePicker"
|
||||||
ItemsSource="{Binding PasswordTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding PasswordTypeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding PasswordTypeSelectedIndex}"
|
SelectedIndex="{Binding PasswordTypeSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="PasswordTypePicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Spacing="0"
|
<StackLayout Spacing="0"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
@@ -403,12 +335,14 @@
|
|||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
VerticalTextAlignment="Center" />
|
VerticalTextAlignment="Center"
|
||||||
|
AutomationId="NumberOfWordsLabel" />
|
||||||
<controls:ExtendedStepper
|
<controls:ExtendedStepper
|
||||||
Value="{Binding NumWords}"
|
Value="{Binding NumWords}"
|
||||||
Maximum="20"
|
Maximum="20"
|
||||||
Minimum="3"
|
Minimum="3"
|
||||||
Increment="1" />
|
Increment="1"
|
||||||
|
AutomationId="NumberOfWordsStepper" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
@@ -419,7 +353,8 @@
|
|||||||
Text="{Binding WordSeparator}"
|
Text="{Binding WordSeparator}"
|
||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
StyleClass="box-value">
|
StyleClass="box-value"
|
||||||
|
AutomationId="WordSeparatorEntry">
|
||||||
<Entry.Effects>
|
<Entry.Effects>
|
||||||
<effects:NoEmojiKeyboardEffect />
|
<effects:NoEmojiKeyboardEffect />
|
||||||
</Entry.Effects>
|
</Entry.Effects>
|
||||||
@@ -435,7 +370,8 @@
|
|||||||
IsEnabled="{Binding EnforcedPolicyOptions.Capitalize,
|
IsEnabled="{Binding EnforcedPolicyOptions.Capitalize,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="CapitalizePassphraseToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -448,7 +384,8 @@
|
|||||||
IsEnabled="{Binding EnforcedPolicyOptions.IncludeNumber,
|
IsEnabled="{Binding EnforcedPolicyOptions.IncludeNumber,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="IncludeNumbersToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding IsPassword}">
|
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding IsPassword}">
|
||||||
@@ -462,7 +399,8 @@
|
|||||||
StyleClass="box-sub-label"
|
StyleClass="box-sub-label"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
WidthRequest="50" />
|
WidthRequest="50"
|
||||||
|
AutomationId="PasswordLengthLabel" />
|
||||||
<controls:ExtendedSlider
|
<controls:ExtendedSlider
|
||||||
DragCompleted="LengthSlider_DragCompleted"
|
DragCompleted="LengthSlider_DragCompleted"
|
||||||
Value="{Binding Length}"
|
Value="{Binding Length}"
|
||||||
@@ -471,7 +409,8 @@
|
|||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
Maximum="128"
|
Maximum="128"
|
||||||
Minimum="5" />
|
Minimum="5"
|
||||||
|
AutomationId="PasswordLengthSlider" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -488,7 +427,8 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
|
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"
|
||||||
|
AutomationId="UppercaseAtoZToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -505,7 +445,8 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
|
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"
|
||||||
|
AutomationId="LowercaseAtoZToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -522,7 +463,8 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
|
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"
|
||||||
|
AutomationId="NumbersZeroToNineToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -539,7 +481,8 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
|
AutomationProperties.Name="{u:I18n SpecialCharacters}"
|
||||||
|
AutomationId="SpecialCharactersToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-stepper">
|
<StackLayout StyleClass="box-row, box-row-stepper">
|
||||||
@@ -554,12 +497,14 @@
|
|||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
VerticalTextAlignment="Center" />
|
VerticalTextAlignment="Center"
|
||||||
|
AutomationId="MinNumberValueLabel" />
|
||||||
<controls:ExtendedStepper
|
<controls:ExtendedStepper
|
||||||
Value="{Binding MinNumber}"
|
Value="{Binding MinNumber}"
|
||||||
Maximum="5"
|
Maximum="5"
|
||||||
Minimum="0"
|
Minimum="0"
|
||||||
Increment="1" />
|
Increment="1"
|
||||||
|
AutomationId="MinNumberStepper" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-stepper">
|
<StackLayout StyleClass="box-row, box-row-stepper">
|
||||||
@@ -574,12 +519,14 @@
|
|||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
VerticalTextAlignment="Center" />
|
VerticalTextAlignment="Center"
|
||||||
|
AutomationId="MinSpecialValueLabel" />
|
||||||
<controls:ExtendedStepper
|
<controls:ExtendedStepper
|
||||||
Value="{Binding MinSpecial}"
|
Value="{Binding MinSpecial}"
|
||||||
Maximum="5"
|
Maximum="5"
|
||||||
Minimum="0"
|
Minimum="0"
|
||||||
Increment="1" />
|
Increment="1"
|
||||||
|
AutomationId="MinSpecialStepper" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -590,7 +537,8 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AvoidAmbiguousChars}"
|
IsToggled="{Binding AvoidAmbiguousChars}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="AvoidAmbiguousCharsToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Bit.App.Utilities;
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -23,7 +24,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IUsernameGenerationService _usernameGenerationService;
|
private readonly IUsernameGenerationService _usernameGenerationService;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
|
||||||
|
|
||||||
private PasswordGenerationOptions _options;
|
private PasswordGenerationOptions _options;
|
||||||
private UsernameGenerationOptions _usernameOptions;
|
private UsernameGenerationOptions _usernameOptions;
|
||||||
@@ -49,11 +50,7 @@ namespace Bit.App.Pages
|
|||||||
private bool _doneIniting;
|
private bool _doneIniting;
|
||||||
private bool _showTypePicker;
|
private bool _showTypePicker;
|
||||||
private string _emailWebsite;
|
private string _emailWebsite;
|
||||||
private bool _showFirefoxRelayApiAccessToken;
|
private bool _showForwardedEmailApiSecret;
|
||||||
private bool _showAnonAddyApiAccessToken;
|
|
||||||
private bool _showSimpleLoginApiKey;
|
|
||||||
private bool _showDuckDuckGoApiKey;
|
|
||||||
private bool _showFastmailApiKey;
|
|
||||||
private bool _editMode;
|
private bool _editMode;
|
||||||
|
|
||||||
public GeneratorPageViewModel()
|
public GeneratorPageViewModel()
|
||||||
@@ -96,7 +93,7 @@ namespace Bit.App.Pages
|
|||||||
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
|
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
|
||||||
RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||||
RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||||
ToggleForwardedEmailHiddenValueCommand = new AsyncCommand(ToggleForwardedEmailHiddenValueAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
ToggleForwardedEmailHiddenValueCommand = new Command(() => ShowForwardedEmailApiSecret = !ShowForwardedEmailApiSecret);
|
||||||
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
||||||
CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
@@ -415,7 +412,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public string UsernameTypeDescriptionLabel => GetUsernameTypeLabelDescription(UsernameTypeSelected);
|
public string UsernameTypeDescriptionLabel => GetUsernameTypeLabelDescription(UsernameTypeSelected);
|
||||||
|
|
||||||
|
|
||||||
public ForwardedEmailServiceType ForwardedEmailServiceSelected
|
public ForwardedEmailServiceType ForwardedEmailServiceSelected
|
||||||
{
|
{
|
||||||
get => _usernameOptions.ServiceType;
|
get => _usernameOptions.ServiceType;
|
||||||
@@ -425,7 +421,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_usernameOptions.ServiceType = value;
|
_usernameOptions.ServiceType = value;
|
||||||
Username = Constants.DefaultUsernameGenerated;
|
Username = Constants.DefaultUsernameGenerated;
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected), new string[]
|
||||||
|
{
|
||||||
|
nameof(ForwardedEmailApiSecret),
|
||||||
|
nameof(ForwardedEmailApiSecretLabel)
|
||||||
|
});
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,27 +445,104 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AnonAddyApiAccessToken
|
public string ForwardedEmailApiSecret
|
||||||
{
|
{
|
||||||
get => _usernameOptions.AnonAddyApiAccessToken;
|
get
|
||||||
|
{
|
||||||
|
switch (ForwardedEmailServiceSelected)
|
||||||
|
{
|
||||||
|
case ForwardedEmailServiceType.AnonAddy:
|
||||||
|
return _usernameOptions.AnonAddyApiAccessToken;
|
||||||
|
case ForwardedEmailServiceType.DuckDuckGo:
|
||||||
|
return _usernameOptions.DuckDuckGoApiKey;
|
||||||
|
case ForwardedEmailServiceType.Fastmail:
|
||||||
|
return _usernameOptions.FastMailApiKey;
|
||||||
|
case ForwardedEmailServiceType.FirefoxRelay:
|
||||||
|
return _usernameOptions.FirefoxRelayApiAccessToken;
|
||||||
|
case ForwardedEmailServiceType.SimpleLogin:
|
||||||
|
return _usernameOptions.SimpleLoginApiKey;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (_usernameOptions.AnonAddyApiAccessToken != value)
|
bool changed = false;
|
||||||
|
switch (ForwardedEmailServiceSelected)
|
||||||
{
|
{
|
||||||
_usernameOptions.AnonAddyApiAccessToken = value;
|
case ForwardedEmailServiceType.AnonAddy:
|
||||||
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
if (_usernameOptions.AnonAddyApiAccessToken != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.AnonAddyApiAccessToken = value;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ForwardedEmailServiceType.DuckDuckGo:
|
||||||
|
if (_usernameOptions.DuckDuckGoApiKey != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.DuckDuckGoApiKey = value;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ForwardedEmailServiceType.Fastmail:
|
||||||
|
if (_usernameOptions.FastMailApiKey != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.FastMailApiKey = value;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ForwardedEmailServiceType.FirefoxRelay:
|
||||||
|
if (_usernameOptions.FirefoxRelayApiAccessToken != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.FirefoxRelayApiAccessToken = value;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ForwardedEmailServiceType.SimpleLogin:
|
||||||
|
if (_usernameOptions.SimpleLoginApiKey != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.SimpleLoginApiKey = value;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowAnonAddyApiAccessToken
|
public string ForwardedEmailApiSecretLabel
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _showAnonAddyApiAccessToken;
|
switch (ForwardedEmailServiceSelected)
|
||||||
|
{
|
||||||
|
case ForwardedEmailServiceType.AnonAddy:
|
||||||
|
case ForwardedEmailServiceType.FirefoxRelay:
|
||||||
|
return AppResources.APIAccessToken;
|
||||||
|
case ForwardedEmailServiceType.DuckDuckGo:
|
||||||
|
case ForwardedEmailServiceType.Fastmail:
|
||||||
|
case ForwardedEmailServiceType.SimpleLogin:
|
||||||
|
return AppResources.APIKeyRequiredParenthesis;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
set => SetProperty(ref _showAnonAddyApiAccessToken, value);
|
}
|
||||||
|
|
||||||
|
public bool ShowForwardedEmailApiSecret
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _showForwardedEmailApiSecret;
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _showForwardedEmailApiSecret, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AnonAddyDomainName
|
public string AnonAddyDomainName
|
||||||
@@ -482,99 +559,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FirefoxRelayApiAccessToken
|
|
||||||
{
|
|
||||||
get => _usernameOptions.FirefoxRelayApiAccessToken;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.FirefoxRelayApiAccessToken != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.FirefoxRelayApiAccessToken = value;
|
|
||||||
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowFirefoxRelayApiAccessToken
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _showFirefoxRelayApiAccessToken;
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _showFirefoxRelayApiAccessToken, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SimpleLoginApiKey
|
|
||||||
{
|
|
||||||
get => _usernameOptions.SimpleLoginApiKey;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.SimpleLoginApiKey != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.SimpleLoginApiKey = value;
|
|
||||||
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowSimpleLoginApiKey
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _showSimpleLoginApiKey;
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _showSimpleLoginApiKey, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DuckDuckGoApiKey
|
|
||||||
{
|
|
||||||
get => _usernameOptions.DuckDuckGoApiKey;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.DuckDuckGoApiKey != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.DuckDuckGoApiKey = value;
|
|
||||||
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowDuckDuckGoApiKey
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _showDuckDuckGoApiKey;
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _showDuckDuckGoApiKey, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public string FastmailApiKey
|
|
||||||
{
|
|
||||||
get => _usernameOptions.FastMailApiKey;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.FastMailApiKey != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.FastMailApiKey = value;
|
|
||||||
TriggerPropertyChanged(nameof(FastmailApiKey));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowFastmailApiKey
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _showFastmailApiKey;
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _showFastmailApiKey, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CapitalizeRandomWordUsername
|
public bool CapitalizeRandomWordUsername
|
||||||
{
|
{
|
||||||
get => _usernameOptions.CapitalizeRandomWordUsername;
|
get => _usernameOptions.CapitalizeRandomWordUsername;
|
||||||
@@ -807,12 +791,9 @@ namespace Bit.App.Pages
|
|||||||
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
|
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
|
||||||
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
|
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
|
||||||
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
|
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
|
||||||
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
|
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
|
||||||
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
|
TriggerPropertyChanged(nameof(ForwardedEmailApiSecretLabel));
|
||||||
TriggerPropertyChanged(nameof(AnonAddyDomainName));
|
TriggerPropertyChanged(nameof(AnonAddyDomainName));
|
||||||
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
|
||||||
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
|
|
||||||
TriggerPropertyChanged(nameof(FastmailApiKey));
|
|
||||||
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
|
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
||||||
TriggerPropertyChanged(nameof(UsernameTypeSelected));
|
TriggerPropertyChanged(nameof(UsernameTypeSelected));
|
||||||
@@ -845,15 +826,23 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_logger.Value.Exception(ex);
|
_logger.Value.Exception(ex);
|
||||||
|
|
||||||
|
string message = AppResources.GenericErrorMessage;
|
||||||
|
|
||||||
if (IsUsername && UsernameTypeSelected == UsernameType.ForwardedEmailAlias)
|
if (IsUsername && UsernameTypeSelected == UsernameType.ForwardedEmailAlias)
|
||||||
{
|
{
|
||||||
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(
|
if (ex is ForwardedEmailInvalidSecretException)
|
||||||
AppResources.AnErrorHasOccurred, string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected), AppResources.Ok));
|
{
|
||||||
}
|
message = ForwardedEmailServiceSelected == ForwardedEmailServiceType.AnonAddy || ForwardedEmailServiceSelected == ForwardedEmailServiceType.FirefoxRelay
|
||||||
else
|
? AppResources.InvalidAPIToken
|
||||||
{
|
: AppResources.InvalidAPIKey;
|
||||||
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok));
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message = string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, message, AppResources.Ok));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetUsernameTypeLabelDescription(UsernameType value)
|
private string GetUsernameTypeLabelDescription(UsernameType value)
|
||||||
@@ -870,27 +859,5 @@ namespace Bit.App.Pages
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ToggleForwardedEmailHiddenValueAsync()
|
|
||||||
{
|
|
||||||
switch (ForwardedEmailServiceSelected)
|
|
||||||
{
|
|
||||||
case ForwardedEmailServiceType.AnonAddy:
|
|
||||||
ShowAnonAddyApiAccessToken = !ShowAnonAddyApiAccessToken;
|
|
||||||
break;
|
|
||||||
case ForwardedEmailServiceType.FirefoxRelay:
|
|
||||||
ShowFirefoxRelayApiAccessToken = !ShowFirefoxRelayApiAccessToken;
|
|
||||||
break;
|
|
||||||
case ForwardedEmailServiceType.SimpleLogin:
|
|
||||||
ShowSimpleLoginApiKey = !ShowSimpleLoginApiKey;
|
|
||||||
break;
|
|
||||||
case ForwardedEmailServiceType.DuckDuckGo:
|
|
||||||
ShowDuckDuckGoApiKey = !ShowDuckDuckGoApiKey;
|
|
||||||
break;
|
|
||||||
case ForwardedEmailServiceType.Fastmail:
|
|
||||||
ShowFastmailApiKey = !ShowFastmailApiKey;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,8 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n SendDisabledWarning}"
|
Text="{u:I18n SendDisabledWarning}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="SendDisabledWarningMessageLabel" />
|
||||||
</Frame>
|
</Frame>
|
||||||
<Frame
|
<Frame
|
||||||
IsVisible="{Binding SendOptionsPolicyInEffect}"
|
IsVisible="{Binding SendOptionsPolicyInEffect}"
|
||||||
@@ -83,7 +84,8 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n SendOptionsPolicyInEffect}"
|
Text="{u:I18n SendOptionsPolicyInEffect}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="SendOptionsPolicyInEffectLabel" />
|
||||||
</Frame>
|
</Frame>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
@@ -93,7 +95,8 @@
|
|||||||
x:Name="_nameEntry"
|
x:Name="_nameEntry"
|
||||||
Text="{Binding Send.Name}"
|
Text="{Binding Send.Name}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="SendNameEntry" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n NameInfo}"
|
Text="{u:I18n NameInfo}"
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
@@ -123,6 +126,7 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n File}"
|
AutomationProperties.Name="{u:I18n File}"
|
||||||
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
|
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
|
||||||
|
AutomationId="SendFileButton"
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -135,6 +139,7 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Text}"
|
AutomationProperties.Name="{u:I18n Text}"
|
||||||
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
|
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
|
||||||
|
AutomationId="SendTextButton"
|
||||||
Grid.Column="1">
|
Grid.Column="1">
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -152,12 +157,14 @@
|
|||||||
Text="{Binding Send.File.FileName, Mode=OneWay}"
|
Text="{Binding Send.File.FileName, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationId="SendFileNameLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Send.File.SizeName, Mode=OneWay}"
|
Text="{Binding Send.File.SizeName, Mode=OneWay}"
|
||||||
StyleClass="box-sub-label"
|
StyleClass="box-sub-label"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalTextAlignment="Center" />
|
VerticalTextAlignment="Center"
|
||||||
|
AutomationId="SendFileSizeLabel" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
|
||||||
@@ -168,20 +175,23 @@
|
|||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="SendNoFileChosenLabel" />
|
||||||
<Label
|
<Label
|
||||||
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
|
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
|
||||||
Text="{Binding FileName}"
|
Text="{Binding FileName}"
|
||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="SendCurrentFileNameLabel" />
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n ChooseFile}"
|
Text="{u:I18n ChooseFile}"
|
||||||
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-button-row"
|
StyleClass="box-button-row"
|
||||||
Clicked="ChooseFile_Clicked" />
|
Clicked="ChooseFile_Clicked"
|
||||||
|
AutomationId="SendChooseFileButton" />
|
||||||
<Label
|
<Label
|
||||||
Margin="0, 5, 0, 0"
|
Margin="0, 5, 0, 0"
|
||||||
Text="{u:I18n MaxFileSize}"
|
Text="{u:I18n MaxFileSize}"
|
||||||
@@ -208,6 +218,7 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Margin="{Binding EditorMargins}"
|
Margin="{Binding EditorMargins}"
|
||||||
|
AutomationId="SendTextContentEntry"
|
||||||
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
||||||
<Editor.Behaviors>
|
<Editor.Behaviors>
|
||||||
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
||||||
@@ -235,7 +246,8 @@
|
|||||||
IsToggled="{Binding Send.Text.Hidden}"
|
IsToggled="{Binding Send.Text.Hidden}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
Margin="10,0,0,0" />
|
Margin="10,0,0,0"
|
||||||
|
AutomationId="SendHideTextByDefaultToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -249,7 +261,8 @@
|
|||||||
IsToggled="{Binding ShareOnSave}"
|
IsToggled="{Binding ShareOnSave}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
Margin="10,0,0,0" />
|
Margin="10,0,0,0"
|
||||||
|
AutomationId="SendShareSendAfterSaveToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
@@ -263,21 +276,24 @@
|
|||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
AutomationProperties.IsInAccessibleTree="False"/>
|
AutomationProperties.IsInAccessibleTree="False"
|
||||||
|
AutomationId="SendShowHideOptionsButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
x:Name="_btnOptionsUp"
|
x:Name="_btnOptionsUp"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
IsVisible="{Binding ShowOptions}"
|
IsVisible="{Binding ShowOptions}"
|
||||||
AutomationProperties.IsInAccessibleTree="False"/>
|
AutomationProperties.IsInAccessibleTree="False"
|
||||||
|
AutomationId="SendOptionsDisplayed" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
x:Name="_btnOptionsDown"
|
x:Name="_btnOptionsDown"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
|
||||||
AutomationProperties.IsInAccessibleTree="False"/>
|
AutomationProperties.IsInAccessibleTree="False"
|
||||||
|
AutomationId="SendOptionsHidden" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding ShowOptions}">
|
<StackLayout IsVisible="{Binding ShowOptions}">
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -294,7 +310,8 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n DeletionTime}" />
|
AutomationProperties.Name="{u:I18n DeletionTime}"
|
||||||
|
AutomationId="SendDeletionOptionsPicker" />
|
||||||
<Grid
|
<Grid
|
||||||
IsVisible="{Binding ShowDeletionCustomPickers}"
|
IsVisible="{Binding ShowDeletionCustomPickers}"
|
||||||
Margin="0,5,0,0">
|
Margin="0,5,0,0">
|
||||||
@@ -308,14 +325,16 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n DeletionDate}"
|
AutomationProperties.Name="{u:I18n DeletionDate}"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="SendCustomDeletionDatePicker" />
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
|
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
|
||||||
Format="t"
|
Format="t"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n DeletionTime}"
|
AutomationProperties.Name="{u:I18n DeletionTime}"
|
||||||
Grid.Column="1" />
|
Grid.Column="1"
|
||||||
|
AutomationId="SendCustomDeletionTimePicker" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DeletionDateInfo}"
|
Text="{u:I18n DeletionDateInfo}"
|
||||||
@@ -334,7 +353,8 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ExpirationTime}" />
|
AutomationProperties.Name="{u:I18n ExpirationTime}"
|
||||||
|
AutomationId="SendExpirationOptionsPicker" />
|
||||||
<Grid
|
<Grid
|
||||||
IsVisible="{Binding ShowExpirationCustomPickers}"
|
IsVisible="{Binding ShowExpirationCustomPickers}"
|
||||||
Margin="0,5,0,0">
|
Margin="0,5,0,0">
|
||||||
@@ -349,7 +369,8 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="SendCustomExpirationDatePicker" />
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
|
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
|
||||||
PlaceHolder="--:-- --"
|
PlaceHolder="--:-- --"
|
||||||
@@ -357,7 +378,8 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ExpirationTime}"
|
AutomationProperties.Name="{u:I18n ExpirationTime}"
|
||||||
Grid.Column="1" />
|
Grid.Column="1"
|
||||||
|
AutomationId="SendCustomExpirationTimePicker" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
@@ -374,7 +396,8 @@
|
|||||||
FontSize="{Binding SegmentedButtonFontSize}"
|
FontSize="{Binding SegmentedButtonFontSize}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
Clicked="ClearExpirationDate_Clicked" />
|
Clicked="ClearExpirationDate_Clicked"
|
||||||
|
AutomationId="SendClearExpirationDateButton" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -393,13 +416,15 @@
|
|||||||
Keyboard="Numeric"
|
Keyboard="Numeric"
|
||||||
MaxLength="9"
|
MaxLength="9"
|
||||||
TextChanged="OnMaxAccessCountTextChanged"
|
TextChanged="OnMaxAccessCountTextChanged"
|
||||||
HorizontalOptions="FillAndExpand" />
|
HorizontalOptions="FillAndExpand"
|
||||||
|
AutomationId="SendMaxAccessCountEntry" />
|
||||||
<controls:ExtendedStepper
|
<controls:ExtendedStepper
|
||||||
x:Name="_maxAccessCountStepper"
|
x:Name="_maxAccessCountStepper"
|
||||||
Value="{Binding MaxAccessCount}"
|
Value="{Binding MaxAccessCount}"
|
||||||
Maximum="999999999"
|
Maximum="999999999"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
Margin="10,0,0,0" />
|
Margin="10,0,0,0"
|
||||||
|
AutomationId="SendMaxAccessCountStepper" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MaximumAccessCountInfo}"
|
Text="{u:I18n MaximumAccessCountInfo}"
|
||||||
@@ -419,7 +444,8 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding Send.AccessCount, Mode=OneWay}"
|
Text="{Binding Send.AccessCount, Mode=OneWay}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
VerticalTextAlignment="Center" />
|
VerticalTextAlignment="Center"
|
||||||
|
AutomationId="SendCurrentAccessCountLabel" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -436,7 +462,8 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
HorizontalOptions="FillAndExpand" />
|
HorizontalOptions="FillAndExpand"
|
||||||
|
AutomationId="SendNewPasswordEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
@@ -445,7 +472,8 @@
|
|||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
|
AutomationId="SendShowHidePasswordButton" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordInfo}"
|
Text="{u:I18n PasswordInfo}"
|
||||||
@@ -464,7 +492,8 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Margin="{Binding EditorMargins}"
|
Margin="{Binding EditorMargins}"
|
||||||
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
effects:ScrollEnabledEffect.IsScrollEnabled="false"
|
||||||
|
AutomationId="SendNotesEntry">
|
||||||
<Editor.Behaviors>
|
<Editor.Behaviors>
|
||||||
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
||||||
</Editor.Behaviors>
|
</Editor.Behaviors>
|
||||||
@@ -492,7 +521,8 @@
|
|||||||
IsToggled="{Binding Send.HideEmail}"
|
IsToggled="{Binding Send.HideEmail}"
|
||||||
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
|
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
Margin="10,0,0,0" />
|
Margin="10,0,0,0"
|
||||||
|
AutomationId="SendHideEmailSwitch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row, box-row-switch"
|
StyleClass="box-row, box-row-switch"
|
||||||
@@ -506,7 +536,8 @@
|
|||||||
IsToggled="{Binding Send.Disabled}"
|
IsToggled="{Binding Send.Disabled}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
Margin="10,0,0,0" />
|
Margin="10,0,0,0"
|
||||||
|
AutomationId="SendDeactivateSwitch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
Priority="-2"
|
Priority="-2"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}" />
|
AutomationProperties.Name="{u:I18n Account}"
|
||||||
|
AutomationId="AccountIconButton" />
|
||||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" x:Name="_closeItem" />
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" x:Name="_closeItem" />
|
||||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" x:Name="_saveItem"/>
|
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" x:Name="_saveItem"/>
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|||||||
@@ -44,13 +44,15 @@
|
|||||||
<controls:SendViewCell
|
<controls:SendViewCell
|
||||||
Send="{Binding Send}"
|
Send="{Binding Send}"
|
||||||
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
|
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
|
||||||
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}" />
|
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}"
|
||||||
|
AutomationId="SendCell" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate x:Key="sendGroupTemplate"
|
<DataTemplate x:Key="sendGroupTemplate"
|
||||||
x:DataType="pages:SendGroupingsPageListItem">
|
x:DataType="pages:SendGroupingsPageListItem">
|
||||||
<controls:ExtendedStackLayout Orientation="Horizontal"
|
<controls:ExtendedStackLayout Orientation="Horizontal"
|
||||||
StyleClass="list-row, list-row-platform">
|
StyleClass="list-row, list-row-platform"
|
||||||
|
AutomationId="{Binding AutomationId}">
|
||||||
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
@@ -64,12 +66,14 @@
|
|||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
StyleClass="list-title" />
|
StyleClass="list-title"
|
||||||
|
AutomationId="SendFilterNameLabel" />
|
||||||
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
StyleClass="list-sub" />
|
StyleClass="list-sub"
|
||||||
|
AutomationId="SendFilterCountLabel" />
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
|||||||
@@ -66,5 +66,27 @@ namespace Bit.App.Pages
|
|||||||
return _icon;
|
return _icon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string AutomationId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_name != null)
|
||||||
|
{
|
||||||
|
return "SendItem";
|
||||||
|
}
|
||||||
|
if (Type != null)
|
||||||
|
{
|
||||||
|
switch (Type.Value)
|
||||||
|
{
|
||||||
|
case SendType.Text:
|
||||||
|
return "SendTextFilter";
|
||||||
|
case SendType.File:
|
||||||
|
return "SendFileFilter";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@
|
|||||||
Margin="20, 0"
|
Margin="20, 0"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="CenterAndExpand"
|
HorizontalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="NoSendDisplayedLabel" />
|
||||||
<controls:ExtendedCollectionView
|
<controls:ExtendedCollectionView
|
||||||
IsVisible="{Binding ShowList}"
|
IsVisible="{Binding ShowList}"
|
||||||
ItemsSource="{Binding Sends}"
|
ItemsSource="{Binding Sends}"
|
||||||
@@ -67,13 +68,15 @@
|
|||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform"
|
StyleClass="list, list-platform"
|
||||||
ExtraDataForLogging="Sends Page">
|
ExtraDataForLogging="Sends Page"
|
||||||
|
AutomationId="SendCellList">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:SendView">
|
<DataTemplate x:DataType="views:SendView">
|
||||||
<controls:SendViewCell
|
<controls:SendViewCell
|
||||||
Send="{Binding .}"
|
Send="{Binding .}"
|
||||||
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
|
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
|
||||||
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}" />
|
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}"
|
||||||
|
AutomationId="SendCell" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
</controls:ExtendedCollectionView>
|
</controls:ExtendedCollectionView>
|
||||||
|
|||||||
85
src/App/Pages/Settings/BlockAutofillUrisPage.xaml
Normal file
85
src/App/Pages/Settings/BlockAutofillUrisPage.xaml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?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.BlockAutofillUrisPage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||||
|
x:DataType="pages:BlockAutofillUrisPageViewModel"
|
||||||
|
Title="{u:I18n BlockAutoFill}">
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:BlockAutofillUrisPageViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
<StackLayout Orientation="Vertical">
|
||||||
|
<Image
|
||||||
|
x:Name="_emptyUrisPlaceholder"
|
||||||
|
HorizontalOptions="Center"
|
||||||
|
WidthRequest="120"
|
||||||
|
HeightRequest="120"
|
||||||
|
Margin="0,100,0,0"
|
||||||
|
IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ThereAreNoBlockedURIs}" />
|
||||||
|
<controls:CustomLabel
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
|
||||||
|
FontWeight="500"
|
||||||
|
HorizontalTextAlignment="Center"
|
||||||
|
Margin="14,10,14,0"/>
|
||||||
|
<controls:ExtendedCollectionView
|
||||||
|
ItemsSource="{Binding BlockedUris}"
|
||||||
|
IsVisible="{Binding ShowList}"
|
||||||
|
VerticalOptions="FillAndExpand"
|
||||||
|
Margin="0,5,0,0"
|
||||||
|
SelectionMode="None"
|
||||||
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Blocked Autofill Uris"
|
||||||
|
AutomationId="BlockedUrisCellList">
|
||||||
|
<CollectionView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="pages:BlockAutofillUriItemViewModel">
|
||||||
|
<StackLayout
|
||||||
|
Orientation="Vertical"
|
||||||
|
AutomationId="BlockedUriCell">
|
||||||
|
<StackLayout
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<controls:CustomLabel
|
||||||
|
VerticalOptions="Center"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
Text="{Binding Uri}"
|
||||||
|
MaxLines="2"
|
||||||
|
LineBreakMode="TailTruncation"
|
||||||
|
FontWeight="500"
|
||||||
|
Margin="15,0,0,0"
|
||||||
|
HorizontalOptions="StartAndExpand"/>
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button-muted, box-row-button-platform"
|
||||||
|
Text="{Binding Source={x:Static core:BitwardenIcons.PencilSquare}}"
|
||||||
|
Command="{Binding EditUriCommand}"
|
||||||
|
Margin="5,0,15,0"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n EditURI}"
|
||||||
|
AutomationId="EditUriButton" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView StyleClass="box-row-separator" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
</CollectionView.ItemTemplate>
|
||||||
|
</controls:ExtendedCollectionView>
|
||||||
|
<Button
|
||||||
|
Text="{u:I18n NewBlockedURI}"
|
||||||
|
Command="{Binding AddUriCommand}"
|
||||||
|
VerticalOptions="End"
|
||||||
|
HeightRequest="40"
|
||||||
|
Opacity="0.8"
|
||||||
|
Margin="14,5,14,10"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n NewBlockedURI}"
|
||||||
|
AutomationId="NewBlockedUriButton" />
|
||||||
|
</StackLayout>
|
||||||
|
</pages:BaseContentPage>
|
||||||
44
src/App/Pages/Settings/BlockAutofillUrisPage.xaml.cs
Normal file
44
src/App/Pages/Settings/BlockAutofillUrisPage.xaml.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Styles;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class BlockAutofillUrisPage : BaseContentPage, IThemeDirtablePage
|
||||||
|
{
|
||||||
|
private readonly BlockAutofillUrisPageViewModel _vm;
|
||||||
|
|
||||||
|
public BlockAutofillUrisPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_vm = BindingContext as BlockAutofillUrisPageViewModel;
|
||||||
|
_vm.Page = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAppearing()
|
||||||
|
{
|
||||||
|
base.OnAppearing();
|
||||||
|
|
||||||
|
_vm.InitAsync().FireAndForget(_ => Navigation.PopAsync());
|
||||||
|
|
||||||
|
UpdatePlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task UpdateOnThemeChanged()
|
||||||
|
{
|
||||||
|
await base.UpdateOnThemeChanged();
|
||||||
|
|
||||||
|
UpdatePlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePlaceholder()
|
||||||
|
{
|
||||||
|
MainThread.BeginInvokeOnMainThread(() =>
|
||||||
|
_emptyUrisPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_uris_placeholder" : "empty_uris_placeholder_dark"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
186
src/App/Pages/Settings/BlockAutofillUrisPageViewModel.cs
Normal file
186
src/App/Pages/Settings/BlockAutofillUrisPageViewModel.cs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class BlockAutofillUrisPageViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
private const char URI_SEPARARTOR = ',';
|
||||||
|
private const string URI_FORMAT = "https://domain.com";
|
||||||
|
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
|
||||||
|
public BlockAutofillUrisPageViewModel()
|
||||||
|
{
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
|
||||||
|
|
||||||
|
AddUriCommand = new AsyncCommand(AddUriAsync,
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
EditUriCommand = new AsyncCommand<BlockAutofillUriItemViewModel>(EditUriAsync,
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableRangeCollection<BlockAutofillUriItemViewModel> BlockedUris { get; set; } = new ObservableRangeCollection<BlockAutofillUriItemViewModel>();
|
||||||
|
|
||||||
|
public bool ShowList => BlockedUris.Any();
|
||||||
|
|
||||||
|
public ICommand AddUriCommand { get; }
|
||||||
|
|
||||||
|
public ICommand EditUriCommand { get; }
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
||||||
|
if (blockedUrisList?.Any() != true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
|
{
|
||||||
|
BlockedUris.AddRange(blockedUrisList.OrderBy(uri => uri).Select(u => new BlockAutofillUriItemViewModel(u, EditUriCommand)).ToList());
|
||||||
|
TriggerPropertyChanged(nameof(ShowList));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddUriAsync()
|
||||||
|
{
|
||||||
|
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
|
||||||
|
{
|
||||||
|
Title = AppResources.NewUri,
|
||||||
|
Subtitle = AppResources.EnterURI,
|
||||||
|
ValueSubInfo = string.Format(AppResources.FormatXSeparateMultipleURIsWithAComma, URI_FORMAT),
|
||||||
|
OkButtonText = AppResources.Save,
|
||||||
|
ValidateText = text => ValidateUris(text, true)
|
||||||
|
});
|
||||||
|
if (response?.Text is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
|
{
|
||||||
|
foreach (var uri in response.Value.Text.Split(URI_SEPARARTOR).Where(s => !string.IsNullOrEmpty(s)))
|
||||||
|
{
|
||||||
|
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
|
||||||
|
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
|
||||||
|
TriggerPropertyChanged(nameof(BlockedUris));
|
||||||
|
TriggerPropertyChanged(nameof(ShowList));
|
||||||
|
});
|
||||||
|
await UpdateAutofillBlacklistedUrisAsync();
|
||||||
|
_deviceActionService.Toast(AppResources.URISaved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EditUriAsync(BlockAutofillUriItemViewModel uriItemViewModel)
|
||||||
|
{
|
||||||
|
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
|
||||||
|
{
|
||||||
|
Title = AppResources.EditURI,
|
||||||
|
Subtitle = AppResources.EnterURI,
|
||||||
|
Text = uriItemViewModel.Uri,
|
||||||
|
ValueSubInfo = string.Format(AppResources.FormatX, URI_FORMAT),
|
||||||
|
OkButtonText = AppResources.Save,
|
||||||
|
ThirdButtonText = AppResources.Remove,
|
||||||
|
ValidateText = text => ValidateUris(text, false)
|
||||||
|
});
|
||||||
|
if (response is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.Value.ExecuteThirdAction)
|
||||||
|
{
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
|
{
|
||||||
|
BlockedUris.Remove(uriItemViewModel);
|
||||||
|
TriggerPropertyChanged(nameof(ShowList));
|
||||||
|
});
|
||||||
|
await UpdateAutofillBlacklistedUrisAsync();
|
||||||
|
_deviceActionService.Toast(AppResources.URIRemoved);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanedUri = response.Value.Text.Replace(Environment.NewLine, string.Empty).Trim();
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
|
{
|
||||||
|
BlockedUris.Remove(uriItemViewModel);
|
||||||
|
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
|
||||||
|
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
|
||||||
|
TriggerPropertyChanged(nameof(BlockedUris));
|
||||||
|
TriggerPropertyChanged(nameof(ShowList));
|
||||||
|
});
|
||||||
|
await UpdateAutofillBlacklistedUrisAsync();
|
||||||
|
_deviceActionService.Toast(AppResources.URISaved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ValidateUris(string uris, bool allowMultipleUris)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(uris))
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.FormatX, URI_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowMultipleUris && uris.Contains(URI_SEPARARTOR))
|
||||||
|
{
|
||||||
|
return AppResources.CannotEditMultipleURIsAtOnce;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var uri in uris.Split(URI_SEPARARTOR).Where(u => !string.IsNullOrWhiteSpace(u)))
|
||||||
|
{
|
||||||
|
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
|
||||||
|
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
|
||||||
|
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
|
||||||
|
{
|
||||||
|
return AppResources.InvalidFormatUseHttpsHttpOrAndroidApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Uri.TryCreate(cleanedUri, UriKind.Absolute, out var _))
|
||||||
|
{
|
||||||
|
return AppResources.InvalidURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BlockedUris.Any(uriItem => uriItem.Uri == cleanedUri))
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.TheURIXIsAlreadyBlocked, cleanedUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateAutofillBlacklistedUrisAsync()
|
||||||
|
{
|
||||||
|
await _stateService.SetAutofillBlacklistedUrisAsync(BlockedUris.Any() ? BlockedUris.Select(bu => bu.Uri).ToList() : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlockAutofillUriItemViewModel : ExtendedViewModel
|
||||||
|
{
|
||||||
|
public BlockAutofillUriItemViewModel(string uri, ICommand editUriCommand)
|
||||||
|
{
|
||||||
|
Uri = uri;
|
||||||
|
EditUriCommand = new Command(() => editUriCommand.Execute(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Uri { get; }
|
||||||
|
|
||||||
|
public ICommand EditUriCommand { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,13 @@
|
|||||||
</ContentPage.BindingContext>
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
<ToolbarItem Text="{u:I18n Cancel}"
|
||||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" />
|
Clicked="Close_Clicked"
|
||||||
|
Order="Primary"
|
||||||
|
Priority="-1" />
|
||||||
|
<ToolbarItem Text="{u:I18n Save}"
|
||||||
|
Clicked="Save_Clicked"
|
||||||
|
Order="Primary" />
|
||||||
<ToolbarItem Text="{u:I18n Delete}"
|
<ToolbarItem Text="{u:I18n Delete}"
|
||||||
Clicked="Delete_Clicked"
|
Clicked="Delete_Clicked"
|
||||||
Order="Secondary"
|
Order="Secondary"
|
||||||
@@ -43,7 +48,8 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
x:Name="_nameEntry"
|
x:Name="_nameEntry"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}" />
|
ReturnCommand="{Binding SubmitCommand}"
|
||||||
|
AutomationId="FolderNameEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -31,7 +31,8 @@
|
|||||||
Margin="20, 0"
|
Margin="20, 0"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="CenterAndExpand"
|
HorizontalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="Center"></Label>
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="NoFoldersLabel"></Label>
|
||||||
<controls:ExtendedCollectionView
|
<controls:ExtendedCollectionView
|
||||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||||
ItemsSource="{Binding Folders}"
|
ItemsSource="{Binding Folders}"
|
||||||
@@ -44,10 +45,12 @@
|
|||||||
<DataTemplate x:DataType="views:FolderView">
|
<DataTemplate x:DataType="views:FolderView">
|
||||||
<controls:ExtendedStackLayout
|
<controls:ExtendedStackLayout
|
||||||
StyleClass="list-row, list-row-platform"
|
StyleClass="list-row, list-row-platform"
|
||||||
Padding="10">
|
Padding="10"
|
||||||
|
AutomationId="FolderCell">
|
||||||
<Label LineBreakMode="TailTruncation"
|
<Label LineBreakMode="TailTruncation"
|
||||||
StyleClass="list-title, list-title-platform"
|
StyleClass="list-title, list-title-platform"
|
||||||
Text="{Binding Name, Mode=OneWay}" />
|
Text="{Binding Name, Mode=OneWay}"
|
||||||
|
AutomationId="FolderName" />
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
x:Name="_themePicker"
|
x:Name="_themePicker"
|
||||||
ItemsSource="{Binding ThemeOptions, Mode=OneTime}"
|
ItemsSource="{Binding ThemeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding ThemeSelectedIndex}"
|
SelectedIndex="{Binding ThemeSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ThemeSelectorPicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
@@ -44,7 +45,8 @@
|
|||||||
x:Name="_autoDarkThemePicker"
|
x:Name="_autoDarkThemePicker"
|
||||||
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
|
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
|
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="DefaultDarkThemePicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
@@ -59,7 +61,8 @@
|
|||||||
x:Name="_uriMatchPicker"
|
x:Name="_uriMatchPicker"
|
||||||
ItemsSource="{Binding UriMatchOptions, Mode=OneTime}"
|
ItemsSource="{Binding UriMatchOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding UriMatchSelectedIndex}"
|
SelectedIndex="{Binding UriMatchSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="DefaultUriMatchDetectionPicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DefaultUriMatchDetectionDescription}"
|
Text="{u:I18n DefaultUriMatchDetectionDescription}"
|
||||||
@@ -74,7 +77,8 @@
|
|||||||
x:Name="_clearClipboardPicker"
|
x:Name="_clearClipboardPicker"
|
||||||
ItemsSource="{Binding ClearClipboardOptions, Mode=OneTime}"
|
ItemsSource="{Binding ClearClipboardOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding ClearClipboardSelectedIndex}"
|
SelectedIndex="{Binding ClearClipboardSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ClearClipboardPicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ClearClipboardDescription}"
|
Text="{u:I18n ClearClipboardDescription}"
|
||||||
@@ -90,7 +94,8 @@
|
|||||||
ItemsSource="{Binding LocalesOptions, Mode=OneTime}"
|
ItemsSource="{Binding LocalesOptions, Mode=OneTime}"
|
||||||
SelectedItem="{Binding SelectedLocale}"
|
SelectedItem="{Binding SelectedLocale}"
|
||||||
ItemDisplayBinding="{Binding Value}"
|
ItemDisplayBinding="{Binding Value}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="LanguagePicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n LanguageChangeRequiresAppRestart}"
|
Text="{u:I18n LanguageChangeRequiresAppRestart}"
|
||||||
@@ -105,7 +110,8 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AutoTotpCopy}"
|
IsToggled="{Binding AutoTotpCopy}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="CopyTotpAutomaticallyToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n CopyTotpAutomaticallyDescription}"
|
Text="{u:I18n CopyTotpAutomaticallyDescription}"
|
||||||
@@ -120,7 +126,8 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Favicon}"
|
IsToggled="{Binding Favicon}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="ShowWebsiteIconsToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ShowWebsiteIconsDescription}"
|
Text="{u:I18n ShowWebsiteIconsDescription}"
|
||||||
@@ -146,22 +153,14 @@
|
|||||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout.GestureRecognizers>
|
||||||
<Label
|
<TapGestureRecognizer Command="{Binding GoToBlockAutofillUrisCommand}" />
|
||||||
Text="{u:I18n AutofillBlockedUris}"
|
</StackLayout.GestureRecognizers>
|
||||||
StyleClass="box-label" />
|
|
||||||
<Editor
|
|
||||||
x:Name="_autofillBlockedUrisEditor"
|
|
||||||
Text="{Binding AutofillBlockedUris}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
AutoSize="TextChanges"
|
|
||||||
IsSpellCheckEnabled="False"
|
|
||||||
IsTextPredictionEnabled="False"
|
|
||||||
Keyboard="Url"
|
|
||||||
Unfocused="AutofillBlockedUrisEditor_Unfocused" />
|
|
||||||
</StackLayout>
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n AutofillBlockedUrisDescription}"
|
Text="{u:I18n BlockAutoFill}"
|
||||||
|
StyleClass="box-label-regular" />
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
|
||||||
StyleClass="box-footer-label" />
|
StyleClass="box-footer-label" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using Bit.App.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.PlatformConfiguration;
|
using Xamarin.Forms.PlatformConfiguration;
|
||||||
@@ -44,17 +42,6 @@ namespace Bit.App.Pages
|
|||||||
await _vm.InitAsync();
|
await _vm.InitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async override void OnDisappearing()
|
|
||||||
{
|
|
||||||
base.OnDisappearing();
|
|
||||||
await _vm.UpdateAutofillBlockedUris();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void AutofillBlockedUrisEditor_Unfocused(object sender, FocusEventArgs e)
|
|
||||||
{
|
|
||||||
await _vm.UpdateAutofillBlockedUris();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
private async void Close_Clicked(object sender, System.EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -19,7 +20,6 @@ namespace Bit.App.Pages
|
|||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
|
|
||||||
private bool _autofillSavePrompt;
|
private bool _autofillSavePrompt;
|
||||||
private string _autofillBlockedUris;
|
|
||||||
private bool _favicon;
|
private bool _favicon;
|
||||||
private bool _autoTotpCopy;
|
private bool _autoTotpCopy;
|
||||||
private int _clearClipboardSelectedIndex;
|
private int _clearClipboardSelectedIndex;
|
||||||
@@ -84,6 +84,10 @@ namespace Bit.App.Pages
|
|||||||
new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
|
new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
|
||||||
};
|
};
|
||||||
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
|
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
|
||||||
|
|
||||||
|
GoToBlockAutofillUrisCommand = new AsyncCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
||||||
@@ -192,25 +196,18 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AutofillBlockedUris
|
|
||||||
{
|
|
||||||
get => _autofillBlockedUris;
|
|
||||||
set => SetProperty(ref _autofillBlockedUris, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowAndroidAutofillSettings
|
public bool ShowAndroidAutofillSettings
|
||||||
{
|
{
|
||||||
get => _showAndroidAutofillSettings;
|
get => _showAndroidAutofillSettings;
|
||||||
set => SetProperty(ref _showAndroidAutofillSettings, value);
|
set => SetProperty(ref _showAndroidAutofillSettings, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ICommand GoToBlockAutofillUrisCommand { get; }
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
||||||
|
|
||||||
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
|
||||||
AutofillBlockedUris = blockedUrisList != null ? string.Join(", ", blockedUrisList) : null;
|
|
||||||
|
|
||||||
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
|
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
|
||||||
|
|
||||||
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
@@ -288,41 +285,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateAutofillBlockedUris()
|
|
||||||
{
|
|
||||||
if (_inited)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(AutofillBlockedUris))
|
|
||||||
{
|
|
||||||
await _stateService.SetAutofillBlacklistedUrisAsync(null);
|
|
||||||
AutofillBlockedUris = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var csv = AutofillBlockedUris;
|
|
||||||
var urisList = new List<string>();
|
|
||||||
foreach (var uri in csv.Split(','))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(uri))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var cleanedUri = uri.Replace(System.Environment.NewLine, string.Empty).Trim();
|
|
||||||
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
|
|
||||||
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
urisList.Add(cleanedUri);
|
|
||||||
}
|
|
||||||
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
|
|
||||||
AutofillBlockedUris = string.Join(", ", urisList);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateCurrentLocaleAsync()
|
private async Task UpdateCurrentLocaleAsync()
|
||||||
{
|
{
|
||||||
if (!_inited)
|
if (!_inited)
|
||||||
|
|||||||
@@ -32,19 +32,21 @@
|
|||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center" />
|
||||||
</Frame>
|
</Frame>
|
||||||
<Label IsVisible="{Binding UseFrame, Converter={StaticResource inverseBool}}"
|
<controls:CustomLabel IsVisible="{Binding UseFrame, Converter={StaticResource inverseBool}}"
|
||||||
Text="{Binding Name, Mode=OneWay}"
|
Text="{Binding Name, Mode=OneWay}"
|
||||||
LineBreakMode="{Binding LineBreakMode}"
|
LineBreakMode="{Binding LineBreakMode}"
|
||||||
HorizontalOptions="StartAndExpand"
|
HorizontalOptions="StartAndExpand"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
StyleClass="list-title"/>
|
StyleClass="list-title"
|
||||||
<Label Text="{Binding SubLabel, Mode=OneWay}"
|
AutomationId="{Binding AutomationIdSettingName}" />
|
||||||
|
<controls:CustomLabel Text="{Binding SubLabel, Mode=OneWay}"
|
||||||
IsVisible="{Binding ShowSubLabel}"
|
IsVisible="{Binding ShowSubLabel}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
TextColor="{Binding SubLabelColor}"
|
TextColor="{Binding SubLabelColor}"
|
||||||
StyleClass="list-sub" />
|
StyleClass="list-sub"
|
||||||
|
AutomationId="{Binding AutomationIdSettingStatus}" />
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate
|
<DataTemplate
|
||||||
@@ -57,7 +59,8 @@
|
|||||||
Padding="10"
|
Padding="10"
|
||||||
HasShadow="False"
|
HasShadow="False"
|
||||||
BackgroundColor="Transparent"
|
BackgroundColor="Transparent"
|
||||||
BorderColor="{DynamicResource PrimaryColor}">
|
BorderColor="{DynamicResource PrimaryColor}"
|
||||||
|
AutomationId="SettingActivePolicyTextLabel">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Name, Mode=OneWay}"
|
Text="{Binding Name, Mode=OneWay}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
@@ -75,7 +78,8 @@
|
|||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
TextColor="{Binding SubLabelColor}"
|
TextColor="{Binding SubLabelColor}"
|
||||||
StyleClass="list-sub" Margin="-5"/>
|
StyleClass="list-sub" Margin="-5"
|
||||||
|
AutomationId="SettingCustomVaultTimeoutPicker" />
|
||||||
<controls:ExtendedStackLayout.GestureRecognizers>
|
<controls:ExtendedStackLayout.GestureRecognizers>
|
||||||
<TapGestureRecognizer Tapped="ActivateTimePicker"/>
|
<TapGestureRecognizer Tapped="ActivateTimePicker"/>
|
||||||
</controls:ExtendedStackLayout.GestureRecognizers>
|
</controls:ExtendedStackLayout.GestureRecognizers>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Utilities.Automation;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -22,5 +24,29 @@ namespace Bit.App.Pages
|
|||||||
public Color SubLabelColor => SubLabelTextEnabled ?
|
public Color SubLabelColor => SubLabelTextEnabled ?
|
||||||
ThemeManager.GetResourceColor("SuccessColor") :
|
ThemeManager.GetResourceColor("SuccessColor") :
|
||||||
ThemeManager.GetResourceColor("MutedColor");
|
ThemeManager.GetResourceColor("MutedColor");
|
||||||
|
|
||||||
|
public string AutomationIdSettingName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return AutomationIdsHelper.AddSuffixFor(
|
||||||
|
UseFrame ? "EnabledPolicy"
|
||||||
|
: AutomationIdsHelper.ToEnglishTitleCase(Name)
|
||||||
|
, SuffixType.Cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string AutomationIdSettingStatus
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (UseFrame)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AutomationIdsHelper.AddSuffixFor(AutomationIdsHelper.ToEnglishTitleCase(Name), SuffixType.SettingValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ namespace Bit.App.Pages
|
|||||||
private bool _reportLoggingEnabled;
|
private bool _reportLoggingEnabled;
|
||||||
private bool _approvePasswordlessLoginRequests;
|
private bool _approvePasswordlessLoginRequests;
|
||||||
private bool _shouldConnectToWatch;
|
private bool _shouldConnectToWatch;
|
||||||
|
private bool _hasMasterPassword;
|
||||||
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
|
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
|
||||||
new List<KeyValuePair<string, int?>>
|
new List<KeyValuePair<string, int?>>
|
||||||
{
|
{
|
||||||
@@ -100,12 +101,17 @@ namespace Bit.App.Pages
|
|||||||
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _biometric || _pin;
|
||||||
|
|
||||||
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
||||||
|
|
||||||
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
|
var decryptionOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
|
// set has true for backwards compatibility
|
||||||
|
_hasMasterPassword = decryptionOptions?.HasMasterPassword ?? true;
|
||||||
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
||||||
var lastSync = await _syncService.GetLastSyncAsync();
|
var lastSync = await _syncService.GetLastSyncAsync();
|
||||||
if (lastSync != null)
|
if (lastSync != null)
|
||||||
@@ -124,7 +130,13 @@ namespace Bit.App.Pages
|
|||||||
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
||||||
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
|
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
|
||||||
|
|
||||||
var action = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
|
var savedVaultTimeoutAction = await _vaultTimeoutService.GetVaultTimeoutAction();
|
||||||
|
var action = savedVaultTimeoutAction ?? VaultTimeoutAction.Lock;
|
||||||
|
if (!_hasMasterPassword && savedVaultTimeoutAction == null)
|
||||||
|
{
|
||||||
|
action = VaultTimeoutAction.Logout;
|
||||||
|
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
|
||||||
|
}
|
||||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
|
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
|
||||||
|
|
||||||
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
|
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
|
||||||
@@ -139,7 +151,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
_pin = pinSet.Item1 || pinSet.Item2;
|
_pin = pinSet != PinLockEnum.Disabled;
|
||||||
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
||||||
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
|
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
|
||||||
|
|
||||||
@@ -387,8 +399,11 @@ namespace Bit.App.Pages
|
|||||||
// do nothing if we have a policy set
|
// do nothing if we have a policy set
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var options = _vaultTimeoutActionOptions.Select(o =>
|
|
||||||
o.Key == _vaultTimeoutActionDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
|
var options = IsVaultTimeoutActionLockAllowed
|
||||||
|
? _vaultTimeoutActionOptions.Select(o => CreateSelectableOption(o.Key, _vaultTimeoutActionDisplayValue == o.Key)).ToArray()
|
||||||
|
: _vaultTimeoutActionOptions.Where(o => o.Value == VaultTimeoutAction.Logout).Select(v => ToSelectedOption(v.Key)).ToArray();
|
||||||
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
|
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
|
||||||
AppResources.Cancel, null, options);
|
AppResources.Cancel, null, options);
|
||||||
if (selection == null || selection == AppResources.Cancel)
|
if (selection == null || selection == AppResources.Cancel)
|
||||||
@@ -437,19 +452,20 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||||
var email = await _stateService.GetEmailAsync();
|
var email = await _stateService.GetEmailAsync();
|
||||||
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig);
|
var pinKey = await _cryptoService.MakePinKeyAsync(pin, email, kdfConfig);
|
||||||
var key = await _cryptoService.GetKeyAsync();
|
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||||
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
|
var pinProtectedKey = await _cryptoService.EncryptAsync(userKey.Key, pinKey);
|
||||||
|
|
||||||
|
var encPin = await _cryptoService.EncryptAsync(pin);
|
||||||
|
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
|
||||||
|
|
||||||
if (masterPassOnRestart)
|
if (masterPassOnRestart)
|
||||||
{
|
{
|
||||||
var encPin = await _cryptoService.EncryptAsync(pin);
|
await _stateService.SetUserKeyPinEphemeralAsync(pinProtectedKey);
|
||||||
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
|
|
||||||
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
|
await _stateService.SetUserKeyPinAsync(pinProtectedKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -459,7 +475,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (!_pin)
|
if (!_pin)
|
||||||
{
|
{
|
||||||
await _cryptoService.ClearPinProtectedKeyAsync();
|
|
||||||
await _vaultTimeoutService.ClearAsync();
|
await _vaultTimeoutService.ClearAsync();
|
||||||
}
|
}
|
||||||
BuildList();
|
BuildList();
|
||||||
@@ -489,9 +504,10 @@ namespace Bit.App.Pages
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _stateService.SetBiometricUnlockAsync(null);
|
await _stateService.SetBiometricUnlockAsync(null);
|
||||||
|
await UpdateVaultTimeoutActionIfNeededAsync();
|
||||||
}
|
}
|
||||||
await _stateService.SetBiometricLockedAsync(false);
|
await _stateService.SetBiometricLockedAsync(false);
|
||||||
await _cryptoService.ToggleKeyAsync();
|
await _cryptoService.ToggleKeysAsync();
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,9 +851,11 @@ namespace Bit.App.Pages
|
|||||||
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
|
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
private string CreateSelectableOption(string option, bool selected) => selected ? ToSelectedOption(option) : option;
|
||||||
|
|
||||||
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == ToSelectedOption(compareTo);
|
||||||
|
|
||||||
|
private string ToSelectedOption(string option) => $"✓ {option}";
|
||||||
|
|
||||||
public async Task SetScreenCaptureAllowedAsync()
|
public async Task SetScreenCaptureAllowedAsync()
|
||||||
{
|
{
|
||||||
@@ -869,5 +887,17 @@ namespace Bit.App.Pages
|
|||||||
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
|
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateVaultTimeoutActionIfNeededAsync()
|
||||||
|
{
|
||||||
|
if (IsVaultTimeoutActionLockAllowed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.First(o => o.Value == VaultTimeoutAction.Logout).Key;
|
||||||
|
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
|
||||||
|
_deviceActionService.Toast(AppResources.VaultTimeoutActionChangedToLogOut);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,23 +33,25 @@
|
|||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row" Padding="10, 20"
|
<StackLayout StyleClass="box-row" Padding="10, 20"
|
||||||
IsVisible="{Binding HasAttachments, Converter={StaticResource inverseBool}}">
|
IsVisible="{Binding HasAttachments, Converter={StaticResource inverseBool}}">
|
||||||
<Label Text="{u:I18n NoAttachments}" HorizontalTextAlignment="Center" />
|
<Label Text="{u:I18n NoAttachments}" HorizontalTextAlignment="Center" AutomationId="NoAttachmentsLabel" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<controls:RepeaterView ItemsSource="{Binding Attachments}" IsVisible="{Binding HasAttachments}">
|
<controls:RepeaterView ItemsSource="{Binding Attachments}" IsVisible="{Binding HasAttachments}" AutomationId="AttachmentsList">
|
||||||
<controls:RepeaterView.ItemTemplate>
|
<controls:RepeaterView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:AttachmentView">
|
<DataTemplate x:DataType="views:AttachmentView">
|
||||||
<StackLayout Spacing="0" Padding="0">
|
<StackLayout Spacing="0" Padding="0">
|
||||||
<StackLayout Orientation="Horizontal" StyleClass="box-row" Spacing="10">
|
<StackLayout Orientation="Horizontal" StyleClass="box-row" Spacing="10" AutomationId="AttachmentRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding FileName, Mode=OneWay}"
|
Text="{Binding FileName, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationId="AttachmentFileNameLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding SizeName, Mode=OneWay}"
|
Text="{Binding SizeName, Mode=OneWay}"
|
||||||
StyleClass="box-sub-label"
|
StyleClass="box-sub-label"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalTextAlignment="Center" />
|
VerticalTextAlignment="Center"
|
||||||
|
AutomationId="AttachmentFileSizeLabel" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
||||||
@@ -57,7 +59,8 @@
|
|||||||
CommandParameter="{Binding .}"
|
CommandParameter="{Binding .}"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Delete}" />
|
AutomationProperties.Name="{u:I18n Delete}"
|
||||||
|
AutomationId="AttachmentDeleteButton" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -77,17 +80,20 @@
|
|||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="NoFileChosenLabel" />
|
||||||
<Label
|
<Label
|
||||||
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
|
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
|
||||||
Text="{Binding FileName}"
|
Text="{Binding FileName}"
|
||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="NewAttachmentNameLabel" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Button Text="{u:I18n ChooseFile}" StyleClass="box-button-row"
|
<Button Text="{u:I18n ChooseFile}" StyleClass="box-button-row"
|
||||||
Clicked="ChooseFile_Clicked"></Button>
|
Clicked="ChooseFile_Clicked"
|
||||||
|
AutomationId="ChooseFileButton"></Button>
|
||||||
<Label
|
<Label
|
||||||
Margin="0, 10, 0, 0"
|
Margin="0, 10, 0, 0"
|
||||||
Text="{u:I18n MaxFileSize}"
|
Text="{u:I18n MaxFileSize}"
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ namespace Bit.App.Pages
|
|||||||
_cipherDomain = await _cipherService.GetAsync(CipherId);
|
_cipherDomain = await _cipherService.GetAsync(CipherId);
|
||||||
Cipher = await _cipherDomain.DecryptAsync();
|
Cipher = await _cipherDomain.DecryptAsync();
|
||||||
LoadAttachments();
|
LoadAttachments();
|
||||||
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
|
_hasUpdatedKey = await _cryptoService.HasUserKeyAsync();
|
||||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||||
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
|
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
|
||||||
if (!_canAccessAttachments)
|
if (!_canAccessAttachments)
|
||||||
@@ -156,7 +156,7 @@ namespace Bit.App.Pages
|
|||||||
// Prevent Android from locking if vault timeout set to "immediate"
|
// Prevent Android from locking if vault timeout set to "immediate"
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
_vaultTimeoutService.DelayTimeoutMs = 60000;
|
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
|
||||||
}
|
}
|
||||||
await _fileService.SelectFileAsync();
|
await _fileService.SelectFileAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,16 +57,16 @@
|
|||||||
x:Key="deleteItem" />
|
x:Key="deleteItem" />
|
||||||
|
|
||||||
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
||||||
<il:TextCustomFieldItemLayout />
|
<il:TextCustomFieldItemLayout AutomationId="TextCustomFieldItem" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
||||||
<il:BooleanCustomFieldItemLayout />
|
<il:BooleanCustomFieldItemLayout AutomationId="BooleanCustomFieldItem" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
||||||
<il:HiddenCustomFieldItemLayout />
|
<il:HiddenCustomFieldItemLayout AutomationId="HiddenCustomFieldItem" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
||||||
<il:LinkedCustomFieldItemLayout />
|
<il:LinkedCustomFieldItemLayout AutomationId="LinkedCustomFieldItem" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
||||||
@@ -100,7 +100,8 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PersonalOwnershipPolicyInEffect}"
|
Text="{u:I18n PersonalOwnershipPolicyInEffect}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="PersonalOwnershipPolicyLabel"/>
|
||||||
</Frame>
|
</Frame>
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
@@ -116,7 +117,8 @@
|
|||||||
x:Name="_typePicker"
|
x:Name="_typePicker"
|
||||||
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding TypeSelectedIndex}"
|
SelectedIndex="{Binding TypeSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemTypePicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -127,7 +129,8 @@
|
|||||||
Text="{Binding Cipher.Name}"
|
Text="{Binding Cipher.Name}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Name}" />
|
AutomationProperties.Name="{u:I18n Name}"
|
||||||
|
AutomationId="ItemNameEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding IsLogin}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsLogin}" Spacing="0" Padding="0">
|
||||||
<Grid StyleClass="box-row, box-row-input"
|
<Grid StyleClass="box-row, box-row-input"
|
||||||
@@ -142,7 +145,8 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Username}"/>
|
AutomationProperties.Name="{u:I18n Username}"
|
||||||
|
AutomationId="LoginUsernameEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
||||||
@@ -150,7 +154,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n GenerateUsername}" />
|
AutomationProperties.Name="{u:I18n GenerateUsername}"
|
||||||
|
AutomationId="GenerateUsernameButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -180,7 +185,8 @@
|
|||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
IsEnabled="{Binding Cipher.ViewPassword}"
|
IsEnabled="{Binding Cipher.ViewPassword}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Password}"/>
|
AutomationProperties.Name="{u:I18n Password}"
|
||||||
|
AutomationId="LoginPasswordEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
|
||||||
@@ -190,7 +196,8 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CheckPassword}"
|
AutomationProperties.Name="{u:I18n CheckPassword}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}"
|
||||||
|
AutomationId="CheckPasswordButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -201,7 +208,8 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}"
|
||||||
|
AutomationId="ViewPasswordButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
||||||
@@ -211,7 +219,8 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n GeneratePassword}"
|
AutomationProperties.Name="{u:I18n GeneratePassword}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}"
|
||||||
|
AutomationId="RegeneratePasswordButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
@@ -247,7 +256,8 @@
|
|||||||
Padding="0,15"
|
Padding="0,15"
|
||||||
HorizontalOptions="Center"
|
HorizontalOptions="Center"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
VerticalTextAlignment="Center" />
|
VerticalTextAlignment="Center"
|
||||||
|
AutomationId="SetupTotpButton" />
|
||||||
</Frame>
|
</Frame>
|
||||||
<controls:MonoEntry
|
<controls:MonoEntry
|
||||||
x:Name="_loginTotpEntry"
|
x:Name="_loginTotpEntry"
|
||||||
@@ -262,7 +272,8 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="{Binding TotpColumnSpan}"
|
Grid.ColumnSpan="{Binding TotpColumnSpan}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n AuthenticatorKey}" />
|
AutomationProperties.Name="{u:I18n AuthenticatorKey}"
|
||||||
|
AutomationId="LoginTotpEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -272,7 +283,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyTotp}" />
|
AutomationProperties.Name="{u:I18n CopyTotp}"
|
||||||
|
AutomationId="CopyTotpValueButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Camera}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Camera}}"
|
||||||
@@ -282,7 +294,8 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
IsVisible="{Binding HasTotpValue}"
|
IsVisible="{Binding HasTotpValue}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ScanQrTitle}" />
|
AutomationProperties.Name="{u:I18n ScanQrTitle}"
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
|
||||||
@@ -293,7 +306,8 @@
|
|||||||
<Entry
|
<Entry
|
||||||
x:Name="_cardholderNameEntry"
|
x:Name="_cardholderNameEntry"
|
||||||
Text="{Binding Cipher.Card.CardholderName}"
|
Text="{Binding Cipher.Card.CardholderName}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="CardholderNameEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -319,7 +333,8 @@
|
|||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Number}" />
|
AutomationProperties.Name="{u:I18n Number}"
|
||||||
|
AutomationId="CardNumberEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowCardNumberIcon}"
|
Text="{Binding ShowCardNumberIcon}"
|
||||||
@@ -328,7 +343,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationId="ShowCardNumberButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -338,7 +354,8 @@
|
|||||||
x:Name="_cardBrandPicker"
|
x:Name="_cardBrandPicker"
|
||||||
ItemsSource="{Binding CardBrandOptions, Mode=OneTime}"
|
ItemsSource="{Binding CardBrandOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding CardBrandSelectedIndex}"
|
SelectedIndex="{Binding CardBrandSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="CardBrandPicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -348,7 +365,8 @@
|
|||||||
x:Name="_cardExpMonthPicker"
|
x:Name="_cardExpMonthPicker"
|
||||||
ItemsSource="{Binding CardExpMonthOptions, Mode=OneTime}"
|
ItemsSource="{Binding CardExpMonthOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding CardExpMonthSelectedIndex}"
|
SelectedIndex="{Binding CardExpMonthSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="CardExpirationMonthPicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -360,7 +378,8 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Keyboard="Numeric"
|
Keyboard="Numeric"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ExpirationYear}" />
|
AutomationProperties.Name="{u:I18n ExpirationYear}"
|
||||||
|
AutomationId="CardExpirationYearEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -387,7 +406,8 @@
|
|||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n SecurityCode}" />
|
AutomationProperties.Name="{u:I18n SecurityCode}"
|
||||||
|
AutomationId="CardSecurityCodeEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowCardCodeIcon}"
|
Text="{Binding ShowCardCodeIcon}"
|
||||||
@@ -396,7 +416,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationId="CardShowSecurityCodeButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding IsIdentity}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsIdentity}" Spacing="0" Padding="0">
|
||||||
@@ -408,7 +429,8 @@
|
|||||||
x:Name="_identityTitlePicker"
|
x:Name="_identityTitlePicker"
|
||||||
ItemsSource="{Binding IdentityTitleOptions, Mode=OneTime}"
|
ItemsSource="{Binding IdentityTitleOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding IdentityTitleSelectedIndex}"
|
SelectedIndex="{Binding IdentityTitleSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="IdentityTitlePicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -419,7 +441,8 @@
|
|||||||
Text="{Binding Cipher.Identity.FirstName}"
|
Text="{Binding Cipher.Identity.FirstName}"
|
||||||
StyleClass="box-value,capitalize-word-input"
|
StyleClass="box-value,capitalize-word-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n FirstName}"/>
|
AutomationProperties.Name="{u:I18n FirstName}"
|
||||||
|
AutomationId="IdentityFirstNameEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -430,7 +453,8 @@
|
|||||||
Text="{Binding Cipher.Identity.MiddleName}"
|
Text="{Binding Cipher.Identity.MiddleName}"
|
||||||
StyleClass="box-value,capitalize-word-input"
|
StyleClass="box-value,capitalize-word-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n MiddleName}" />
|
AutomationProperties.Name="{u:I18n MiddleName}"
|
||||||
|
AutomationId="IdentityMiddleNameEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -441,7 +465,8 @@
|
|||||||
Text="{Binding Cipher.Identity.LastName}"
|
Text="{Binding Cipher.Identity.LastName}"
|
||||||
StyleClass="box-value,capitalize-word-input"
|
StyleClass="box-value,capitalize-word-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n LastName}" />
|
AutomationProperties.Name="{u:I18n LastName}"
|
||||||
|
AutomationId="IdentityLastNameEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -452,7 +477,8 @@
|
|||||||
Text="{Binding Cipher.Identity.Username}"
|
Text="{Binding Cipher.Identity.Username}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Username}" />
|
AutomationProperties.Name="{u:I18n Username}"
|
||||||
|
AutomationId="IdentityUsernameEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -463,7 +489,8 @@
|
|||||||
Text="{Binding Cipher.Identity.Company}"
|
Text="{Binding Cipher.Identity.Company}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Company}"/>
|
AutomationProperties.Name="{u:I18n Company}"
|
||||||
|
AutomationId="IdentityCompanyEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -474,7 +501,8 @@
|
|||||||
Text="{Binding Cipher.Identity.SSN}"
|
Text="{Binding Cipher.Identity.SSN}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n SSN}"/>
|
AutomationProperties.Name="{u:I18n SSN}"
|
||||||
|
AutomationId="IdentitySsnEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -485,7 +513,8 @@
|
|||||||
Text="{Binding Cipher.Identity.PassportNumber}"
|
Text="{Binding Cipher.Identity.PassportNumber}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n PassportNumber}"/>
|
AutomationProperties.Name="{u:I18n PassportNumber}"
|
||||||
|
AutomationId="IdentityPassportNumberEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -496,7 +525,8 @@
|
|||||||
Text="{Binding Cipher.Identity.LicenseNumber}"
|
Text="{Binding Cipher.Identity.LicenseNumber}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n LicenseNumber}" />
|
AutomationProperties.Name="{u:I18n LicenseNumber}"
|
||||||
|
AutomationId="IdentityLicenseNumberEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -508,7 +538,8 @@
|
|||||||
Text="{Binding Cipher.Identity.Email}"
|
Text="{Binding Cipher.Identity.Email}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Email}"/>
|
AutomationProperties.Name="{u:I18n Email}"
|
||||||
|
AutomationId="IdentityEmailEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -520,7 +551,8 @@
|
|||||||
Keyboard="Telephone"
|
Keyboard="Telephone"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Phone}" />
|
AutomationProperties.Name="{u:I18n Phone}"
|
||||||
|
AutomationId="IdentityPhoneEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -531,7 +563,8 @@
|
|||||||
Text="{Binding Cipher.Identity.Address1}"
|
Text="{Binding Cipher.Identity.Address1}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Address1}"/>
|
AutomationProperties.Name="{u:I18n Address1}"
|
||||||
|
AutomationId="IdentityAddressOneEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -542,7 +575,8 @@
|
|||||||
Text="{Binding Cipher.Identity.Address2}"
|
Text="{Binding Cipher.Identity.Address2}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Address2}" />
|
AutomationProperties.Name="{u:I18n Address2}"
|
||||||
|
AutomationId="IdentityAddressTwoEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -553,7 +587,8 @@
|
|||||||
Text="{Binding Cipher.Identity.Address3}"
|
Text="{Binding Cipher.Identity.Address3}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Address3}" />
|
AutomationProperties.Name="{u:I18n Address3}"
|
||||||
|
AutomationId="IdentityAddressThreeEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -564,7 +599,8 @@
|
|||||||
Text="{Binding Cipher.Identity.City}"
|
Text="{Binding Cipher.Identity.City}"
|
||||||
StyleClass="box-value,capitalize-sentence-input"
|
StyleClass="box-value,capitalize-sentence-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CityTown}" />
|
AutomationProperties.Name="{u:I18n CityTown}"
|
||||||
|
AutomationId="IdentityCityEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -575,7 +611,8 @@
|
|||||||
Text="{Binding Cipher.Identity.State}"
|
Text="{Binding Cipher.Identity.State}"
|
||||||
StyleClass="box-value,capitalize-sentence-input"
|
StyleClass="box-value,capitalize-sentence-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n StateProvince}" />
|
AutomationProperties.Name="{u:I18n StateProvince}"
|
||||||
|
AutomationId="IdentityStateEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -586,7 +623,8 @@
|
|||||||
Text="{Binding Cipher.Identity.PostalCode}"
|
Text="{Binding Cipher.Identity.PostalCode}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ZipPostalCode}" />
|
AutomationProperties.Name="{u:I18n ZipPostalCode}"
|
||||||
|
AutomationId="IdentityPostalCodeEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -597,7 +635,8 @@
|
|||||||
Text="{Binding Cipher.Identity.Country}"
|
Text="{Binding Cipher.Identity.Country}"
|
||||||
StyleClass="box-value,capitalize-sentence-input"
|
StyleClass="box-value,capitalize-sentence-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Country}" />
|
AutomationProperties.Name="{u:I18n Country}"
|
||||||
|
AutomationId="IdentityCountryEntry" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -609,7 +648,7 @@
|
|||||||
<controls:RepeaterView ItemsSource="{Binding Uris}">
|
<controls:RepeaterView ItemsSource="{Binding Uris}">
|
||||||
<controls:RepeaterView.ItemTemplate>
|
<controls:RepeaterView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:LoginUriView">
|
<DataTemplate x:DataType="views:LoginUriView">
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input" AutomationId="UriListGrid" >
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@@ -630,7 +669,8 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n URI}" />
|
AutomationProperties.Name="{u:I18n URI}"
|
||||||
|
AutomationId="LoginUriEntry" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||||
@@ -640,13 +680,15 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
|
AutomationId="LoginUriOptionsButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</controls:RepeaterView.ItemTemplate>
|
</controls:RepeaterView.ItemTemplate>
|
||||||
</controls:RepeaterView>
|
</controls:RepeaterView>
|
||||||
<Button Text="{u:I18n NewUri}" StyleClass="box-button-row"
|
<Button Text="{u:I18n NewUri}" StyleClass="box-button-row"
|
||||||
Clicked="NewUri_Clicked"></Button>
|
Clicked="NewUri_Clicked"
|
||||||
|
AutomationId="LoginAddNewUriButton"></Button>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
@@ -661,7 +703,8 @@
|
|||||||
x:Name="_folderPicker"
|
x:Name="_folderPicker"
|
||||||
ItemsSource="{Binding FolderOptions, Mode=OneTime}"
|
ItemsSource="{Binding FolderOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding FolderSelectedIndex}"
|
SelectedIndex="{Binding FolderSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="FolderPicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
@@ -671,7 +714,8 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Cipher.Favorite}"
|
IsToggled="{Binding Cipher.Favorite}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="ItemFavoriteToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout x:Name="_passwordPrompt" StyleClass="box-row, box-row-switch">
|
<StackLayout x:Name="_passwordPrompt" StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
@@ -689,7 +733,8 @@
|
|||||||
IsToggled="{Binding PasswordPrompt}"
|
IsToggled="{Binding PasswordPrompt}"
|
||||||
Toggled="PasswordPrompt_Toggled"
|
Toggled="PasswordPrompt_Toggled"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationId="MasterPasswordRepromptToggle" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -706,7 +751,8 @@
|
|||||||
effects:ScrollEnabledEffect.IsScrollEnabled="false"
|
effects:ScrollEnabledEffect.IsScrollEnabled="false"
|
||||||
Text="{Binding Cipher.Notes}"
|
Text="{Binding Cipher.Notes}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Notes}" >
|
AutomationProperties.Name="{u:I18n Notes}"
|
||||||
|
AutomationId="ItemNotesEntry">
|
||||||
<Editor.Behaviors>
|
<Editor.Behaviors>
|
||||||
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
||||||
</Editor.Behaviors>
|
</Editor.Behaviors>
|
||||||
@@ -725,9 +771,11 @@
|
|||||||
<StackLayout
|
<StackLayout
|
||||||
Spacing="0"
|
Spacing="0"
|
||||||
BindableLayout.ItemsSource="{Binding Fields}"
|
BindableLayout.ItemsSource="{Binding Fields}"
|
||||||
BindableLayout.ItemTemplateSelector="{StaticResource CustomFieldItemTemplateSelector}" />
|
BindableLayout.ItemTemplateSelector="{StaticResource CustomFieldItemTemplateSelector}"
|
||||||
|
AutomationId="CustomFieldsList" />
|
||||||
<Button Text="{u:I18n NewCustomField}" StyleClass="box-button-row"
|
<Button Text="{u:I18n NewCustomField}" StyleClass="box-button-row"
|
||||||
Clicked="NewField_Clicked"></Button>
|
Clicked="NewField_Clicked"
|
||||||
|
AutomationId="NewCustomFieldButton"></Button>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowOwnershipOptions}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowOwnershipOptions}">
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
@@ -742,7 +790,8 @@
|
|||||||
x:Name="_ownershipPicker"
|
x:Name="_ownershipPicker"
|
||||||
ItemsSource="{Binding OwnershipOptions, Mode=OneTime}"
|
ItemsSource="{Binding OwnershipOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding OwnershipSelectedIndex}"
|
SelectedIndex="{Binding OwnershipSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemOwnershipPicker" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowCollections}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowCollections}">
|
||||||
@@ -753,7 +802,8 @@
|
|||||||
<StackLayout Spacing="0" Padding="0"
|
<StackLayout Spacing="0" Padding="0"
|
||||||
IsVisible="{Binding HasCollections, Converter={StaticResource inverseBool}}">
|
IsVisible="{Binding HasCollections, Converter={StaticResource inverseBool}}">
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label Text="{u:I18n NoCollectionsToList}" />
|
<Label Text="{u:I18n NoCollectionsToList}"
|
||||||
|
AutomationId="NoCollectionsToListLabel" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -763,15 +813,17 @@
|
|||||||
<controls:RepeaterView.ItemTemplate>
|
<controls:RepeaterView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="pages:CollectionViewModel">
|
<DataTemplate x:DataType="pages:CollectionViewModel">
|
||||||
<StackLayout Spacing="0" Padding="0">
|
<StackLayout Spacing="0" Padding="0">
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch" AutomationId="CollectionItemCell">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Collection.Name}"
|
Text="{Binding Collection.Name}"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationId="CollectionItemNameLabel" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Checked}"
|
IsToggled="{Binding Checked}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"/>
|
HorizontalOptions="End"
|
||||||
|
AutomationId="CollectionItemSwitch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -781,5 +833,4 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -49,16 +49,16 @@
|
|||||||
x:Name="_cloneItem" x:Key="cloneItem" />
|
x:Name="_cloneItem" x:Key="cloneItem" />
|
||||||
|
|
||||||
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
||||||
<il:TextCustomFieldItemLayout />
|
<il:TextCustomFieldItemLayout AutomationId="TextCustomFieldItem" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
||||||
<il:BooleanCustomFieldItemLayout />
|
<il:BooleanCustomFieldItemLayout AutomationId="BooleanCustomFieldItem" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
||||||
<il:HiddenCustomFieldItemLayout />
|
<il:HiddenCustomFieldItemLayout AutomationId="HiddenCustomFieldItem" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
||||||
<il:LinkedCustomFieldItemLayout />
|
<il:LinkedCustomFieldItemLayout AutomationId="LinkedCustomFieldItem" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
||||||
@@ -69,23 +69,26 @@
|
|||||||
|
|
||||||
<ScrollView x:Key="scrollView" x:Name="_scrollView">
|
<ScrollView x:Key="scrollView" x:Name="_scrollView">
|
||||||
<StackLayout Spacing="20" x:Name="_mainLayout">
|
<StackLayout Spacing="20" x:Name="_mainLayout">
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box" AutomationId="ItemInformationSection">
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
<Label Text="{u:I18n ItemInformation, Header=True}"
|
<Label Text="{u:I18n ItemInformation, Header=True}"
|
||||||
StyleClass="box-header, box-header-platform" />
|
StyleClass="box-header, box-header-platform" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row" AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Name}"
|
Text="{u:I18n Name}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Name, Mode=OneWay}"
|
Text="{Binding Cipher.Name, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout IsVisible="{Binding IsLogin}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsLogin}" Spacing="0" Padding="0">
|
||||||
<Grid StyleClass="box-row"
|
<Grid StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Login.Username, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Login.Username, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@@ -98,12 +101,14 @@
|
|||||||
Text="{u:I18n Username}"
|
Text="{u:I18n Username}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Login.Username, Mode=OneWay}"
|
Text="{Binding Cipher.Login.Username, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -113,12 +118,14 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyUsername}" />
|
AutomationProperties.Name="{u:I18n CopyUsername}"
|
||||||
|
AutomationId="CopyValueButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Login.Username, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Login.Username, Converter={StaticResource stringHasValue}}" />
|
||||||
<Grid StyleClass="box-row"
|
<Grid StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@@ -133,20 +140,23 @@
|
|||||||
Text="{u:I18n Password}"
|
Text="{u:I18n Password}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding Cipher.Login.MaskedPassword, Mode=OneWay}"
|
Text="{Binding Cipher.Login.MaskedPassword, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding ShowPassword, Converter={StaticResource inverseBool}}" />
|
IsVisible="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding ColoredPassword, Mode=OneWay}"
|
Text="{Binding ColoredPassword, Mode=OneWay}"
|
||||||
StyleClass="box-value, text-html"
|
StyleClass="box-value, text-html"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
IsVisible="{Binding ShowPassword}" />
|
IsVisible="{Binding ShowPassword}"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
|
||||||
@@ -156,7 +166,8 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CheckPassword}"
|
AutomationProperties.Name="{u:I18n CheckPassword}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}"
|
||||||
|
AutomationId="CheckPasswordButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -167,7 +178,8 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}"
|
||||||
|
AutomationId="ShowValueButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -178,11 +190,14 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyPassword}"
|
AutomationProperties.Name="{u:I18n CopyPassword}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}"
|
||||||
|
AutomationId="CopyValueButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}" />
|
||||||
<Grid StyleClass="box-row" IsVisible="{Binding ShowTotp}">
|
<Grid StyleClass="box-row"
|
||||||
|
IsVisible="{Binding ShowTotp}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@@ -197,7 +212,8 @@
|
|||||||
Text="{u:I18n VerificationCodeTotp}"
|
Text="{u:I18n VerificationCodeTotp}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding TotpCodeFormatted, Mode=OneWay}"
|
Text="{Binding TotpCodeFormatted, Mode=OneWay}"
|
||||||
IsVisible="{Binding ShowUpgradePremiumTotpText, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowUpgradePremiumTotpText, Converter={StaticResource inverseBool}}"
|
||||||
@@ -205,14 +221,16 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
VerticalTextAlignment="Start"
|
VerticalTextAlignment="Start"
|
||||||
VerticalOptions="Start" />
|
VerticalOptions="Start"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
<controls:CircularProgressbarView
|
<controls:CircularProgressbarView
|
||||||
Progress="{Binding TotpProgress}"
|
Progress="{Binding TotpProgress}"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
VerticalOptions="FillAndExpand" />
|
VerticalOptions="FillAndExpand"
|
||||||
|
AutomationId="LoginTotpProgressBar" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding TotpSec, Mode=OneWay}"
|
Text="{Binding TotpSec, Mode=OneWay}"
|
||||||
Style="{DynamicResource textTotp}"
|
Style="{DynamicResource textTotp}"
|
||||||
@@ -234,7 +252,8 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyTotp}" />
|
AutomationProperties.Name="{u:I18n CopyTotp}"
|
||||||
|
AutomationId="CopyValueButton" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PremiumSubscriptionRequired}"
|
Text="{u:I18n PremiumSubscriptionRequired}"
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
@@ -242,24 +261,29 @@
|
|||||||
Margin="0,5,0,2"
|
Margin="0,5,0,2"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
HorizontalOptions="FillAndExpand" />
|
HorizontalOptions="FillAndExpand"
|
||||||
|
AutomationId="ShowUpgradePremiumTotpLabel" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding ShowTotp}" />
|
<BoxView StyleClass="box-row-separator" IsVisible="{Binding ShowTotp}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Card.CardholderName, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Card.CardholderName, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n CardholderName}"
|
Text="{u:I18n CardholderName}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Card.CardholderName, Mode=OneWay}"
|
Text="{Binding Cipher.Card.CardholderName, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Card.CardholderName, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Card.CardholderName, Converter={StaticResource stringHasValue}}" />
|
||||||
<Grid StyleClass="box-row"
|
<Grid StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Card.Number, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Card.Number, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@@ -273,19 +297,22 @@
|
|||||||
Text="{u:I18n Number}"
|
Text="{u:I18n Number}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding Cipher.Card.MaskedNumber, Mode=OneWay}"
|
Text="{Binding Cipher.Card.MaskedNumber, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding ShowCardNumber, Converter={StaticResource inverseBool}}" />
|
IsVisible="{Binding ShowCardNumber, Converter={StaticResource inverseBool}}"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding Cipher.Card.Number, Mode=OneWay}"
|
Text="{Binding Cipher.Card.Number, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding ShowCardNumber}" />
|
IsVisible="{Binding ShowCardNumber}"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowCardNumberIcon}"
|
Text="{Binding ShowCardNumberIcon}"
|
||||||
@@ -294,7 +321,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationId="ShowValueButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -304,34 +332,42 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyNumber}" />
|
AutomationProperties.Name="{u:I18n CopyNumber}"
|
||||||
|
AutomationId="CopyValueButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Card.Number, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Card.Number, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Card.Brand, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Card.Brand, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Brand}"
|
Text="{u:I18n Brand}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Card.Brand, Mode=OneWay}"
|
Text="{Binding Cipher.Card.Brand, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Card.Brand, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Card.Brand, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Card.Expiration, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Card.Expiration, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Expiration}"
|
Text="{u:I18n Expiration}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Card.Expiration, Mode=OneWay}"
|
Text="{Binding Cipher.Card.Expiration, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Card.Expiration, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Card.Expiration, Converter={StaticResource stringHasValue}}" />
|
||||||
<Grid StyleClass="box-row"
|
<Grid StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Card.Code, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Card.Code, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@@ -345,19 +381,22 @@
|
|||||||
Text="{u:I18n SecurityCode}"
|
Text="{u:I18n SecurityCode}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding Cipher.Card.MaskedCode, Mode=OneWay}"
|
Text="{Binding Cipher.Card.MaskedCode, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding ShowCardCode, Converter={StaticResource inverseBool}}" />
|
IsVisible="{Binding ShowCardCode, Converter={StaticResource inverseBool}}"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding Cipher.Card.Code, Mode=OneWay}"
|
Text="{Binding Cipher.Card.Code, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding ShowCardCode}" />
|
IsVisible="{Binding ShowCardCode}"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowCardCodeIcon}"
|
Text="{Binding ShowCardCodeIcon}"
|
||||||
@@ -366,7 +405,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
|
AutomationId="ShowValueButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -376,124 +416,156 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopySecurityCode}" />
|
AutomationProperties.Name="{u:I18n CopySecurityCode}"
|
||||||
|
AutomationId="CopyValueButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Card.Code, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Card.Code, Converter={StaticResource stringHasValue}}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding IsIdentity}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsIdentity}" Spacing="0" Padding="0">
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Identity.FullName, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Identity.FullName, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n IdentityName}"
|
Text="{u:I18n IdentityName}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.FullName, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.FullName, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Identity.FullName, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Identity.FullName, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Identity.Username, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Identity.Username, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Username}"
|
Text="{u:I18n Username}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.Username, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.Username, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Identity.Username, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Identity.Username, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Identity.Company, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Identity.Company, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Company}"
|
Text="{u:I18n Company}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.Company, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.Company, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Identity.Company, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Identity.Company, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Identity.SSN, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Identity.SSN, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n SSN}"
|
Text="{u:I18n SSN}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.SSN, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.SSN, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Identity.SSN, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Identity.SSN, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Identity.PassportNumber, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Identity.PassportNumber, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PassportNumber}"
|
Text="{u:I18n PassportNumber}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.PassportNumber, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.PassportNumber, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Identity.PassportNumber, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Identity.PassportNumber, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Identity.LicenseNumber, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Identity.LicenseNumber, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n LicenseNumber}"
|
Text="{u:I18n LicenseNumber}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.LicenseNumber, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.LicenseNumber, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Identity.LicenseNumber, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Identity.LicenseNumber, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Identity.Email, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Identity.Email, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Email}"
|
Text="{u:I18n Email}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.Email, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.Email, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Identity.Email, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Identity.Email, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row"
|
<StackLayout StyleClass="box-row"
|
||||||
IsVisible="{Binding Cipher.Identity.Phone, Converter={StaticResource stringHasValue}}">
|
IsVisible="{Binding Cipher.Identity.Phone, Converter={StaticResource stringHasValue}}"
|
||||||
|
AutomationId="ItemRow" >
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Phone}"
|
Text="{u:I18n Phone}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.Phone, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.Phone, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="ItemValue" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator"
|
<BoxView StyleClass="box-row-separator"
|
||||||
IsVisible="{Binding Cipher.Identity.Phone, Converter={StaticResource stringHasValue}}" />
|
IsVisible="{Binding Cipher.Identity.Phone, Converter={StaticResource stringHasValue}}" />
|
||||||
<StackLayout StyleClass="box-row" IsVisible="{Binding ShowIdentityAddress}">
|
<StackLayout StyleClass="box-row" IsVisible="{Binding ShowIdentityAddress}"
|
||||||
|
AutomationId="ItemRow">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Address}"
|
Text="{u:I18n Address}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label"
|
||||||
|
AutomationId="ItemName" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.Address1, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.Address1, Mode=OneWay}"
|
||||||
IsVisible="{Binding Cipher.Identity.Address1, Converter={StaticResource stringHasValue}}"
|
IsVisible="{Binding Cipher.Identity.Address1, Converter={StaticResource stringHasValue}}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="IdentityAddressOneLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.Address2, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.Address2, Mode=OneWay}"
|
||||||
IsVisible="{Binding Cipher.Identity.Address2, Converter={StaticResource stringHasValue}}"
|
IsVisible="{Binding Cipher.Identity.Address2, Converter={StaticResource stringHasValue}}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="IdentityAddressTwoLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.Address3, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.Address3, Mode=OneWay}"
|
||||||
IsVisible="{Binding Cipher.Identity.Address3, Converter={StaticResource stringHasValue}}"
|
IsVisible="{Binding Cipher.Identity.Address3, Converter={StaticResource stringHasValue}}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="IdentityAddressThreeLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.FullAddressPart2, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.FullAddressPart2, Mode=OneWay}"
|
||||||
IsVisible="{Binding Cipher.Identity.FullAddressPart2, Converter={StaticResource stringHasValue}}"
|
IsVisible="{Binding Cipher.Identity.FullAddressPart2, Converter={StaticResource stringHasValue}}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="IdentityFullAddressPartTwoLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Cipher.Identity.Country, Mode=OneWay}"
|
Text="{Binding Cipher.Identity.Country, Mode=OneWay}"
|
||||||
IsVisible="{Binding Cipher.Identity.Country, Converter={StaticResource stringHasValue}}"
|
IsVisible="{Binding Cipher.Identity.Country, Converter={StaticResource stringHasValue}}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="IdentityCountryLabel" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding ShowIdentityAddress}" />
|
<BoxView StyleClass="box-row-separator" IsVisible="{Binding ShowIdentityAddress}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -503,11 +575,11 @@
|
|||||||
<Label Text="{u:I18n URIs, Header=True}"
|
<Label Text="{u:I18n URIs, Header=True}"
|
||||||
StyleClass="box-header, box-header-platform" />
|
StyleClass="box-header, box-header-platform" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<controls:RepeaterView ItemsSource="{Binding Cipher.Login.Uris}">
|
<controls:RepeaterView ItemsSource="{Binding Cipher.Login.Uris}" AutomationId="CipherUriContainer">
|
||||||
<controls:RepeaterView.ItemTemplate>
|
<controls:RepeaterView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:LoginUriView">
|
<DataTemplate x:DataType="views:LoginUriView">
|
||||||
<StackLayout Spacing="0" Padding="0">
|
<StackLayout Spacing="0" Padding="0">
|
||||||
<Grid StyleClass="box-row">
|
<Grid StyleClass="box-row" AutomationId="UriRow">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@@ -533,7 +605,8 @@
|
|||||||
Text="{Binding HostOrUri, Mode=OneWay}"
|
Text="{Binding HostOrUri, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0" />
|
Grid.Column="0"
|
||||||
|
AutomationId="UriValue" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.ShareSquare}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.ShareSquare}}"
|
||||||
@@ -544,7 +617,8 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
IsVisible="{Binding CanLaunch, Mode=OneWay}"
|
IsVisible="{Binding CanLaunch, Mode=OneWay}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Launch}" />
|
AutomationProperties.Name="{u:I18n Launch}"
|
||||||
|
AutomationId="LaunchUriButton" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -554,7 +628,8 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Copy}" />
|
AutomationProperties.Name="{u:I18n Copy}"
|
||||||
|
AutomationId="CopyUriButton" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -568,14 +643,15 @@
|
|||||||
<Label Text="{u:I18n Notes, Header=True}"
|
<Label Text="{u:I18n Notes, Header=True}"
|
||||||
StyleClass="box-header, box-header-platform" />
|
StyleClass="box-header, box-header-platform" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row" AutomationId="NotesRow">
|
||||||
<controls:SelectableLabel
|
<controls:SelectableLabel
|
||||||
Text="{Binding Cipher.Notes, Mode=OneWay}"
|
Text="{Binding Cipher.Notes, Mode=OneWay}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value"
|
||||||
|
AutomationId="CipherNotesLabel" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding Cipher.HasFields}">
|
<StackLayout StyleClass="box" IsVisible="{Binding Cipher.HasFields}" AutomationId="CustomFieldsContainer">
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
<Label Text="{u:I18n CustomFields, Header=True}"
|
<Label Text="{u:I18n CustomFields, Header=True}"
|
||||||
StyleClass="box-header, box-header-platform" />
|
StyleClass="box-header, box-header-platform" />
|
||||||
@@ -590,21 +666,23 @@
|
|||||||
<Label Text="{u:I18n Attachments, Header=True}"
|
<Label Text="{u:I18n Attachments, Header=True}"
|
||||||
StyleClass="box-header, box-header-platform" />
|
StyleClass="box-header, box-header-platform" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<controls:RepeaterView ItemsSource="{Binding Cipher.Attachments}">
|
<controls:RepeaterView ItemsSource="{Binding Cipher.Attachments}" AutomationId="CipherAttachmentsContainer">
|
||||||
<controls:RepeaterView.ItemTemplate>
|
<controls:RepeaterView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:AttachmentView">
|
<DataTemplate x:DataType="views:AttachmentView">
|
||||||
<StackLayout Spacing="0" Padding="0">
|
<StackLayout Spacing="0" Padding="0">
|
||||||
<StackLayout Orientation="Horizontal" StyleClass="box-row" Spacing="10">
|
<StackLayout Orientation="Horizontal" StyleClass="box-row" Spacing="10" AutomationId="CipherAttachment">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding FileName, Mode=OneWay}"
|
Text="{Binding FileName, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationId="CipherAttachmentFileNameLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding SizeName, Mode=OneWay}"
|
Text="{Binding SizeName, Mode=OneWay}"
|
||||||
StyleClass="box-sub-label"
|
StyleClass="box-sub-label"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalTextAlignment="Center" />
|
VerticalTextAlignment="Center"
|
||||||
|
AutomationId="CipherAttachmentFileSizeLabel" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Download}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Download}}"
|
||||||
@@ -612,7 +690,8 @@
|
|||||||
CommandParameter="{Binding .}"
|
CommandParameter="{Binding .}"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Download}" />
|
AutomationProperties.Name="{u:I18n Download}"
|
||||||
|
AutomationId="CipherAttachmentDownloadButton" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -622,17 +701,20 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-bottom">
|
<StackLayout StyleClass="box-bottom">
|
||||||
<Label FormattedText="{Binding UpdatedText}"
|
<Label FormattedText="{Binding UpdatedText}"
|
||||||
StyleClass="box-footer-label" />
|
StyleClass="box-footer-label"
|
||||||
|
AutomationId="CipherUpdatedDateLabel" />
|
||||||
<Label FormattedText="{Binding PasswordUpdatedText}"
|
<Label FormattedText="{Binding PasswordUpdatedText}"
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
IsVisible="{Binding Cipher.PasswordRevisionDisplayDate, Converter={StaticResource notNull}}">
|
IsVisible="{Binding Cipher.PasswordRevisionDisplayDate, Converter={StaticResource notNull}}"
|
||||||
|
AutomationId="CipherUpdatedPasswordDateLabel">
|
||||||
<Label.GestureRecognizers>
|
<Label.GestureRecognizers>
|
||||||
<TapGestureRecognizer Tapped="PasswordHistory_Tapped" />
|
<TapGestureRecognizer Tapped="PasswordHistory_Tapped" />
|
||||||
</Label.GestureRecognizers>
|
</Label.GestureRecognizers>
|
||||||
</Label>
|
</Label>
|
||||||
<Label FormattedText="{Binding PasswordHistoryText}"
|
<Label FormattedText="{Binding PasswordHistoryText}"
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
IsVisible="{Binding Cipher.HasPasswordHistory}">
|
IsVisible="{Binding Cipher.HasPasswordHistory}"
|
||||||
|
AutomationId="CipherPasswordHistoryLabel">
|
||||||
<Label.GestureRecognizers>
|
<Label.GestureRecognizers>
|
||||||
<TapGestureRecognizer Tapped="PasswordHistory_Tapped" />
|
<TapGestureRecognizer Tapped="PasswordHistory_Tapped" />
|
||||||
</Label.GestureRecognizers>
|
</Label.GestureRecognizers>
|
||||||
@@ -662,6 +744,7 @@
|
|||||||
AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize"
|
AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n EditItem}"
|
AutomationProperties.Name="{u:I18n EditItem}"
|
||||||
|
AutomationId="CipherEditButton"
|
||||||
IsVisible="{Binding CanEdit}">
|
IsVisible="{Binding CanEdit}">
|
||||||
<Button.Effects>
|
<Button.Effects>
|
||||||
<effects:FabShadowEffect />
|
<effects:FabShadowEffect />
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}" />
|
AutomationProperties.Name="{u:I18n Account}"
|
||||||
|
AutomationId="AccountIconButton" />
|
||||||
<ToolbarItem IconImageSource="search.png" Clicked="Search_Clicked"
|
<ToolbarItem IconImageSource="search.png" Clicked="Search_Clicked"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Search}" />
|
AutomationProperties.Name="{u:I18n Search}" />
|
||||||
|
|||||||
@@ -41,7 +41,8 @@
|
|||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
TextChanged="SearchBar_TextChanged"
|
TextChanged="SearchBar_TextChanged"
|
||||||
SearchButtonPressed="SearchBar_SearchButtonPressed"
|
SearchButtonPressed="SearchBar_SearchButtonPressed"
|
||||||
Placeholder="{Binding PageTitle}" />
|
Placeholder="{Binding PageTitle}"
|
||||||
|
AutomationId="SearchBar" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform"
|
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform"
|
||||||
x:Name="_separator" x:Key="separator" />
|
x:Name="_separator" x:Key="separator" />
|
||||||
@@ -91,7 +92,8 @@
|
|||||||
Source="empty_items_state" />
|
Source="empty_items_state" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ThereAreNoItemsThatMatchTheSearch}"
|
Text="{u:I18n ThereAreNoItemsThatMatchTheSearch}"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="NoSearchResultsLabel" />
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n AddAnItem}"
|
Text="{u:I18n AddAnItem}"
|
||||||
Command="{Binding AddCipherCommand}"
|
Command="{Binding AddCipherCommand}"
|
||||||
@@ -104,7 +106,8 @@
|
|||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform"
|
StyleClass="list, list-platform"
|
||||||
ExtraDataForLogging="Ciphers Page">
|
ExtraDataForLogging="Ciphers Page"
|
||||||
|
AutomationId="CipherList">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:CipherView">
|
<DataTemplate x:DataType="views:CipherView">
|
||||||
<controls:CipherViewCell
|
<controls:CipherViewCell
|
||||||
|
|||||||
@@ -208,6 +208,11 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
|
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
|
||||||
{
|
{
|
||||||
|
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (selection == AppResources.AutofillAndSave)
|
if (selection == AppResources.AutofillAndSave)
|
||||||
{
|
{
|
||||||
var uris = cipher.Login?.Uris?.ToList();
|
var uris = cipher.Login?.Uris?.ToList();
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}" />
|
AutomationProperties.Name="{u:I18n Account}"
|
||||||
|
AutomationId="AccountIconButton" />
|
||||||
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
|
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Search}" />
|
AutomationProperties.Name="{u:I18n Search}" />
|
||||||
@@ -59,13 +60,14 @@
|
|||||||
<controls:AuthenticatorViewCell
|
<controls:AuthenticatorViewCell
|
||||||
Cipher="{Binding Cipher}"
|
Cipher="{Binding Cipher}"
|
||||||
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
|
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
|
||||||
TotpSec="{Binding TotpSec}"/>
|
TotpSec="{Binding TotpSec}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate x:Key="groupTemplate"
|
<DataTemplate x:Key="groupTemplate"
|
||||||
x:DataType="pages:GroupingsPageListItem">
|
x:DataType="pages:GroupingsPageListItem">
|
||||||
<controls:ExtendedStackLayout Orientation="Horizontal"
|
<controls:ExtendedStackLayout Orientation="Horizontal"
|
||||||
StyleClass="list-row, list-row-platform">
|
StyleClass="list-row, list-row-platform"
|
||||||
|
AutomationId="{Binding AutomationId}">
|
||||||
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
@@ -79,12 +81,14 @@
|
|||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
StyleClass="list-title"/>
|
StyleClass="list-title"
|
||||||
|
AutomationId="ItemNameLabel" />
|
||||||
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
StyleClass="list-sub"/>
|
StyleClass="list-sub"
|
||||||
|
AutomationId="ItemCountLabel" />
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
@@ -95,7 +99,8 @@
|
|||||||
Spacing="0"
|
Spacing="0"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
StyleClass="list-row-header-container, list-row-header-container-platform">
|
StyleClass="list-row-header-container, list-row-header-container-platform"
|
||||||
|
AutomationId="{Binding AutomationId}">
|
||||||
<BoxView
|
<BoxView
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
|
||||||
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
<StackLayout StyleClass="list-row-header, list-row-header-platform">
|
||||||
@@ -104,7 +109,8 @@
|
|||||||
StyleClass="list-header, list-header-platform" />
|
StyleClass="list-header, list-header-platform" />
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding ItemCount}"
|
Text="{Binding ItemCount}"
|
||||||
StyleClass="list-header-sub" />
|
StyleClass="list-header-sub"
|
||||||
|
AutomationId="SectionItemCount" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -147,7 +153,8 @@
|
|||||||
IsVisible="{Binding ShowNoData}">
|
IsVisible="{Binding ShowNoData}">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding NoDataText}"
|
Text="{Binding NoDataText}"
|
||||||
HorizontalTextAlignment="Center"></Label>
|
HorizontalTextAlignment="Center"
|
||||||
|
AutomationId="NoDataDisplayed"></Label>
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n AddAnItem}"
|
Text="{u:I18n AddAnItem}"
|
||||||
Clicked="AddButton_Clicked"
|
Clicked="AddButton_Clicked"
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Bit.App.Pages
|
using Bit.App.Utilities.Automation;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GroupingsPageHeaderListItem : IGroupingsPageListItem
|
public class GroupingsPageHeaderListItem : IGroupingsPageListItem
|
||||||
{
|
{
|
||||||
@@ -10,5 +12,12 @@
|
|||||||
|
|
||||||
public string Title { get; }
|
public string Title { get; }
|
||||||
public string ItemCount { get; set; }
|
public string ItemCount { get; set; }
|
||||||
|
public string AutomationId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return AutomationIdsHelper.AddSuffixFor(AutomationIdsHelper.ToEnglishTitleCase(Title), SuffixType.Header);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Bit.App.Utilities.Automation;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@@ -32,5 +33,6 @@ namespace Bit.App.Pages
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string NameShort => string.IsNullOrWhiteSpace(Name) || Name.Length == 0 ? "-" : Name[0].ToString();
|
public string NameShort => string.IsNullOrWhiteSpace(Name) || Name.Length == 0 ? "-" : Name[0].ToString();
|
||||||
public string ItemCount { get; set; }
|
public string ItemCount { get; set; }
|
||||||
|
public string AutomationId => AutomationIdsHelper.AddSuffixFor(NameShort, SuffixType.ListGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities.Automation;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
@@ -115,5 +116,38 @@ namespace Bit.App.Pages
|
|||||||
return _icon;
|
return _icon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string AutomationId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Type != null)
|
||||||
|
{
|
||||||
|
return AutomationIdsHelper.AddSuffixFor(System.Enum.GetName(typeof(CipherType), Type.Value), SuffixType.Filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsTrash)
|
||||||
|
{
|
||||||
|
return AutomationIdsHelper.AddSuffixFor("Trash", SuffixType.Filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Folder != null)
|
||||||
|
{
|
||||||
|
return AutomationIdsHelper.AddSuffixFor("Folder", SuffixType.Filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Collection != null)
|
||||||
|
{
|
||||||
|
return AutomationIdsHelper.AddSuffixFor("Collection", SuffixType.Filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsTotpCode)
|
||||||
|
{
|
||||||
|
return AutomationIdsHelper.AddSuffixFor("TOTP", SuffixType.ListItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,13 +330,16 @@ namespace Bit.App.Pages
|
|||||||
items.AddRange(itemGroup);
|
items.AddRange(itemGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
// because of update to XF v5.0.0.2401
|
{
|
||||||
GroupedItems.Clear();
|
// 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.ReplaceRange(items);
|
GroupedItems.Clear();
|
||||||
|
}
|
||||||
|
GroupedItems.ReplaceRange(items);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -356,21 +359,24 @@ namespace Bit.App.Pages
|
|||||||
items.AddRange(itemGroup);
|
items.AddRange(itemGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupedItems.Any())
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
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);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// 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.Clear();
|
||||||
}
|
}
|
||||||
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
});
|
||||||
GroupedItems.AddRange(items);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GroupedItems.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -378,9 +384,12 @@ namespace Bit.App.Pages
|
|||||||
_doingLoad = false;
|
_doingLoad = false;
|
||||||
Loaded = true;
|
Loaded = true;
|
||||||
Loading = false;
|
Loading = false;
|
||||||
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
ShowList = !ShowNoData;
|
{
|
||||||
DisableRefreshing();
|
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
|
||||||
|
ShowList = !ShowNoData;
|
||||||
|
DisableRefreshing();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
263
src/App/Resources/AppResources.Designer.cs
generated
263
src/App/Resources/AppResources.Designer.cs
generated
@@ -418,6 +418,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Admin approval requested.
|
||||||
|
/// </summary>
|
||||||
|
public static string AdminApprovalRequested {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AdminApprovalRequested", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to All.
|
/// Looks up a localized string similar to All.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -562,6 +571,24 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Approve with master password.
|
||||||
|
/// </summary>
|
||||||
|
public static string ApproveWithMasterPassword {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ApproveWithMasterPassword", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Approve with my other device.
|
||||||
|
/// </summary>
|
||||||
|
public static string ApproveWithMyOtherDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ApproveWithMyOtherDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to April.
|
/// Looks up a localized string similar to April.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -796,15 +823,6 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks up a localized string similar to Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android"..
|
|
||||||
/// </summary>
|
|
||||||
public static string AutofillBlockedUrisDescription {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("AutofillBlockedUrisDescription", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Do you want to auto-fill or view this item?.
|
/// Looks up a localized string similar to Do you want to auto-fill or view this item?.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -940,6 +958,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Auto-fill will not be offered for these URIs..
|
||||||
|
/// </summary>
|
||||||
|
public static string AutoFillWillNotBeOfferedForTheseURIs {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AutoFillWillNotBeOfferedForTheseURIs", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Auto-fill with Bitwarden.
|
/// Looks up a localized string similar to Auto-fill with Bitwarden.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1228,6 +1255,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Block auto-fill.
|
||||||
|
/// </summary>
|
||||||
|
public static string BlockAutoFill {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("BlockAutoFill", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Brand.
|
/// Looks up a localized string similar to Brand.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1264,6 +1300,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Cannot edit multiple URIs at once.
|
||||||
|
/// </summary>
|
||||||
|
public static string CannotEditMultipleURIsAtOnce {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CannotEditMultipleURIsAtOnce", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Cannot open the app "{0}"..
|
/// Looks up a localized string similar to Cannot open the app "{0}"..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2146,6 +2191,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Edit URI.
|
||||||
|
/// </summary>
|
||||||
|
public static string EditURI {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EditURI", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Email.
|
/// Looks up a localized string similar to Email.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2299,6 +2353,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Enter URI.
|
||||||
|
/// </summary>
|
||||||
|
public static string EnterURI {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EnterURI", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Enter the 6 digit verification code from your authenticator app..
|
/// Looks up a localized string similar to Enter the 6 digit verification code from your authenticator app..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2947,6 +3010,24 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Format: {0}.
|
||||||
|
/// </summary>
|
||||||
|
public static string FormatX {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FormatX", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Format: {0}. Separate multiple URIs with a comma..
|
||||||
|
/// </summary>
|
||||||
|
public static string FormatXSeparateMultipleURIsWithAComma {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FormatXSeparateMultipleURIsWithAComma", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Forwarded email alias.
|
/// Looks up a localized string similar to Forwarded email alias.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3262,6 +3343,24 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Invalid API key.
|
||||||
|
/// </summary>
|
||||||
|
public static string InvalidAPIKey {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InvalidAPIKey", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Invalid API token.
|
||||||
|
/// </summary>
|
||||||
|
public static string InvalidAPIToken {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InvalidAPIToken", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Invalid email address..
|
/// Looks up a localized string similar to Invalid email address..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3271,6 +3370,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Invalid format. Use https://, http://, or androidapp://.
|
||||||
|
/// </summary>
|
||||||
|
public static string InvalidFormatUseHttpsHttpOrAndroidApp {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InvalidFormatUseHttpsHttpOrAndroidApp", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Invalid master password. Try again..
|
/// Looks up a localized string similar to Invalid master password. Try again..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3289,6 +3397,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Invalid URI.
|
||||||
|
/// </summary>
|
||||||
|
public static string InvalidURI {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("InvalidURI", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Invalid verification code.
|
/// Looks up a localized string similar to Invalid verification code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3568,6 +3685,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Logged in!.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoggedIn {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoggedIn", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Logged in as {0} on {1}..
|
/// Looks up a localized string similar to Logged in as {0} on {1}..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3586,6 +3712,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Logging in as {0}.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoggingInAsX {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoggingInAsX", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Logging in as {0} on {1}.
|
/// Looks up a localized string similar to Logging in as {0} on {1}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -4200,6 +4335,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to New blocked URI.
|
||||||
|
/// </summary>
|
||||||
|
public static string NewBlockedURI {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NewBlockedURI", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to New custom field.
|
/// Looks up a localized string similar to New custom field.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -5174,6 +5318,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Remember this device.
|
||||||
|
/// </summary>
|
||||||
|
public static string RememberThisDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RememberThisDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Remove.
|
/// Looks up a localized string similar to Remove.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -5246,6 +5399,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Request admin approval.
|
||||||
|
/// </summary>
|
||||||
|
public static string RequestAdminApproval {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RequestAdminApproval", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Request one-time password.
|
/// Looks up a localized string similar to Request one-time password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6101,6 +6263,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to There are no blocked URIs.
|
||||||
|
/// </summary>
|
||||||
|
public static string ThereAreNoBlockedURIs {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ThereAreNoBlockedURIs", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to There are no items in your vault that match "{0}".
|
/// Looks up a localized string similar to There are no items in your vault that match "{0}".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6119,6 +6290,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to The URI {0} is already blocked.
|
||||||
|
/// </summary>
|
||||||
|
public static string TheURIXIsAlreadyBlocked {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TheURIXIsAlreadyBlocked", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to 30 days.
|
/// Looks up a localized string similar to 30 days.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6236,6 +6416,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Trouble logging in?.
|
||||||
|
/// </summary>
|
||||||
|
public static string TroubleLoggingIn {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TroubleLoggingIn", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Try again.
|
/// Looks up a localized string similar to Try again.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6245,6 +6434,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Turn off using a public device.
|
||||||
|
/// </summary>
|
||||||
|
public static string TurnOffUsingPublicDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TurnOffUsingPublicDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to 20 seconds.
|
/// Looks up a localized string similar to 20 seconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6435,7 +6633,7 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.
|
/// Looks up a localized string similar to Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve {
|
public static string UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve {
|
||||||
get {
|
get {
|
||||||
@@ -6569,6 +6767,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to URI removed.
|
||||||
|
/// </summary>
|
||||||
|
public static string URIRemoved {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("URIRemoved", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to URIs.
|
/// Looks up a localized string similar to URIs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6578,6 +6785,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to URI saved.
|
||||||
|
/// </summary>
|
||||||
|
public static string URISaved {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("URISaved", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to US.
|
/// Looks up a localized string similar to US.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6758,6 +6974,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Vault timeout action changed to log out.
|
||||||
|
/// </summary>
|
||||||
|
public static string VaultTimeoutActionChangedToLogOut {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("VaultTimeoutActionChangedToLogOut", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
|
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -7127,6 +7352,24 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Your request has been sent to your admin..
|
||||||
|
/// </summary>
|
||||||
|
public static string YourRequestHasBeenSentToYourAdmin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("YourRequestHasBeenSentToYourAdmin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You will be notified once approved. .
|
||||||
|
/// </summary>
|
||||||
|
public static string YouWillBeNotifiedOnceApproved {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("YouWillBeNotifiedOnceApproved", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device's USB port, then touch its button..
|
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device's USB port, then touch its button..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2634,6 +2634,12 @@ Wil u na die rekening omskakel?</value>
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2635,6 +2635,12 @@
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2633,6 +2633,12 @@ Bu hesaba keçmək istəyirsiniz?</value>
|
|||||||
<value>Ana parolu təkrar soruş köməyi</value>
|
<value>Ana parolu təkrar soruş köməyi</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Yetərsiz yaddaşa görə kilid açma uğursuz ola bilər. Həll etmək üçün KDF yaddaş tənzimləmələrinizi azaldın</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Yararsız API açarı</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Yararsız API tokeni</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2636,4 +2636,10 @@
|
|||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Па прычыне недахопу памяці можа адбыцца збой разблакіроўкі. Паменшыце налады памяці KDF, каб вырашыць гэту праблему</value>
|
<value>Па прычыне недахопу памяці можа адбыцца збой разблакіроўкі. Паменшыце налады памяці KDF, каб вырашыць гэту праблему</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Памылковы ключ API</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Памылковы токен API</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2636,4 +2636,10 @@ select Add TOTP to store the key safely</value>
|
|||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Отключването може да бъде неуспешно заради недостатъчно памет. Намалете настройките на паметта за KDF, за да разрешите проблема.</value>
|
<value>Отключването може да бъде неуспешно заради недостатъчно памет. Намалете настройките на паметта за KDF, за да разрешите проблема.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Неправилен ключ за ППИ</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Неправилен идентификатор за ППИ</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2635,6 +2635,12 @@ Do you want to switch to this account?</value>
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2633,6 +2633,12 @@ Skeniranje će biti izvršeno automatski.</value>
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2636,4 +2636,10 @@ Voleu canviar a aquest compte?</value>
|
|||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>El desbloqueig pot fallar a causa de memòria insuficient. Disminueix la configuració de memòria KDF per resoldre-ho</value>
|
<value>El desbloqueig pot fallar a causa de memòria insuficient. Disminueix la configuració de memòria KDF per resoldre-ho</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Clau API no vàlida</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Token API no vàlid</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2635,4 +2635,10 @@ Chcete se přepnout na tento účet?</value>
|
|||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Odemknutí může selhat z důvodu nedostatku paměti. Pro vyřešení snižte nastavení paměti KDF.</value>
|
<value>Odemknutí může selhat z důvodu nedostatku paměti. Pro vyřešení snižte nastavení paměti KDF.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Neplatný klíč API</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Neplatný token API</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2635,6 +2635,12 @@ Do you want to switch to this account?</value>
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2636,4 +2636,10 @@ Vil du skifte til denne konto?</value>
|
|||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Oplåsning kan fejle grundet utilstrækkelig hukommelse. Reducér KDF-hukommelsesindstillinger for at afhjælpe</value>
|
<value>Oplåsning kan fejle grundet utilstrækkelig hukommelse. Reducér KDF-hukommelsesindstillinger for at afhjælpe</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Ugyldig API-nøgle</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Ugyldigt API-token</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2635,4 +2635,10 @@ Möchtest du zu diesem Konto wechseln?</value>
|
|||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Das Entsperren kann aufgrund von unzureichendem Arbeitsspeicher fehlschlagen. Verringere deine KDF-Speichereinstellungen, um das Problem zu beheben.</value>
|
<value>Das Entsperren kann aufgrund von unzureichendem Arbeitsspeicher fehlschlagen. Verringere deine KDF-Speichereinstellungen, um das Problem zu beheben.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Ungültiger API-Schlüssel</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Ungültiger API-Token</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2610,16 +2610,16 @@
|
|||||||
<value>Δεν υπάρχουν στοιχεία που να ταιριάζουν με την αναζήτηση</value>
|
<value>Δεν υπάρχουν στοιχεία που να ταιριάζουν με την αναζήτηση</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="US" xml:space="preserve">
|
<data name="US" xml:space="preserve">
|
||||||
<value>US</value>
|
<value>ΗΠΑ</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EU" xml:space="preserve">
|
<data name="EU" xml:space="preserve">
|
||||||
<value>EU</value>
|
<value>ΕΕ</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SelfHosted" xml:space="preserve">
|
<data name="SelfHosted" xml:space="preserve">
|
||||||
<value>Self-hosted</value>
|
<value>Αυτο-φιλοξενούμενο</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DataRegion" xml:space="preserve">
|
<data name="DataRegion" xml:space="preserve">
|
||||||
<value>Data region</value>
|
<value>Περιοχή δεδομένων</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Region" xml:space="preserve">
|
<data name="Region" xml:space="preserve">
|
||||||
<value>Περιοχή</value>
|
<value>Περιοχή</value>
|
||||||
@@ -2634,6 +2634,12 @@
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2634,6 +2634,12 @@ Do you want to switch to this account?</value>
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2648,6 +2648,12 @@ Do you want to switch to this account?</value>
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -775,10 +775,10 @@
|
|||||||
<value>Activado</value>
|
<value>Activado</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Off" xml:space="preserve">
|
<data name="Off" xml:space="preserve">
|
||||||
<value>Apagado</value>
|
<value>No</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="On" xml:space="preserve">
|
<data name="On" xml:space="preserve">
|
||||||
<value>Activado</value>
|
<value>Sí</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Status" xml:space="preserve">
|
<data name="Status" xml:space="preserve">
|
||||||
<value>Estado</value>
|
<value>Estado</value>
|
||||||
@@ -2632,9 +2632,15 @@ seleccione Agregar TOTP para almacenar la clave de forma segura</value>
|
|||||||
<value>Contraseña maestra actual</value>
|
<value>Contraseña maestra actual</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
|
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
|
||||||
<value>Master password re-prompt help</value>
|
<value>Ayuda de volver a pedir contraseña maestra</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>El desbloqueo puede fallar por falta de memoria. Disminuye los ajustes de memoria KDF para resolver</value>
|
<value>El desbloqueo puede fallar por falta de memoria. Disminuye los ajustes de memoria KDF para resolver</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Clave API no válida</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Token de API no válido</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2496,7 +2496,7 @@ Soovid selle konto peale lülituda?</value>
|
|||||||
<value>Tuleta ülemparooli vihjega meelde</value>
|
<value>Tuleta ülemparooli vihjega meelde</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoggingInAsXOnY" xml:space="preserve">
|
<data name="LoggingInAsXOnY" xml:space="preserve">
|
||||||
<value>Logging in as {0} on {1}</value>
|
<value>Sisselogimas kui {0} lehel {1}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NotYou" xml:space="preserve">
|
<data name="NotYou" xml:space="preserve">
|
||||||
<value>Pole sina?</value>
|
<value>Pole sina?</value>
|
||||||
@@ -2610,19 +2610,19 @@ Soovid selle konto peale lülituda?</value>
|
|||||||
<value>Otsingusõnale ei vasta kirjeid</value>
|
<value>Otsingusõnale ei vasta kirjeid</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="US" xml:space="preserve">
|
<data name="US" xml:space="preserve">
|
||||||
<value>US</value>
|
<value>USA</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EU" xml:space="preserve">
|
<data name="EU" xml:space="preserve">
|
||||||
<value>EU</value>
|
<value>EL</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SelfHosted" xml:space="preserve">
|
<data name="SelfHosted" xml:space="preserve">
|
||||||
<value>Self-hosted</value>
|
<value>Enda majutatud</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DataRegion" xml:space="preserve">
|
<data name="DataRegion" xml:space="preserve">
|
||||||
<value>Data region</value>
|
<value>Andmete salvestamise piirkond</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Region" xml:space="preserve">
|
<data name="Region" xml:space="preserve">
|
||||||
<value>Region</value>
|
<value>Piirkond</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdateWeakMasterPasswordWarning" xml:space="preserve">
|
<data name="UpdateWeakMasterPasswordWarning" xml:space="preserve">
|
||||||
<value>Sinu ülemparool ei vasta ühele või rohkemale organisatsiooni poolt seatud poliitikale. Hoidlale ligipääsemiseks pead oma ülemaprooli uuendama. Jätkamisel logitakse sind praegusest sessioonist välja, mistõttu pead uuesti sisse logima. Teistes seadmetes olevad aktiivsed sessioonid aeguvad umbes ühe tunni jooksul.</value>
|
<value>Sinu ülemparool ei vasta ühele või rohkemale organisatsiooni poolt seatud poliitikale. Hoidlale ligipääsemiseks pead oma ülemaprooli uuendama. Jätkamisel logitakse sind praegusest sessioonist välja, mistõttu pead uuesti sisse logima. Teistes seadmetes olevad aktiivsed sessioonid aeguvad umbes ühe tunni jooksul.</value>
|
||||||
@@ -2634,6 +2634,12 @@ Soovid selle konto peale lülituda?</value>
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Vigane API võti</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Vigane API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2633,6 +2633,12 @@ Kontu honetara aldatu nahi duzu?</value>
|
|||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>Invalid API key</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>Invalid API token</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -2497,7 +2497,7 @@
|
|||||||
<value>دریافت یادآور کلمه عبور اصلی</value>
|
<value>دریافت یادآور کلمه عبور اصلی</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoggingInAsXOnY" xml:space="preserve">
|
<data name="LoggingInAsXOnY" xml:space="preserve">
|
||||||
<value>Logging in as {0} on {1}</value>
|
<value>درحال وارد شدن به عنوان {0} در {1}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NotYou" xml:space="preserve">
|
<data name="NotYou" xml:space="preserve">
|
||||||
<value>شما نیستید؟</value>
|
<value>شما نیستید؟</value>
|
||||||
@@ -2611,19 +2611,19 @@
|
|||||||
<value>هیچ موردی وجود ندارد که با جستجو مطابقت داشته باشد</value>
|
<value>هیچ موردی وجود ندارد که با جستجو مطابقت داشته باشد</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="US" xml:space="preserve">
|
<data name="US" xml:space="preserve">
|
||||||
<value>US</value>
|
<value>امریکا</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EU" xml:space="preserve">
|
<data name="EU" xml:space="preserve">
|
||||||
<value>EU</value>
|
<value>اروپا</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SelfHosted" xml:space="preserve">
|
<data name="SelfHosted" xml:space="preserve">
|
||||||
<value>Self-hosted</value>
|
<value>خود میزبان</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DataRegion" xml:space="preserve">
|
<data name="DataRegion" xml:space="preserve">
|
||||||
<value>Data region</value>
|
<value>منطقه داده</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Region" xml:space="preserve">
|
<data name="Region" xml:space="preserve">
|
||||||
<value>Region</value>
|
<value>منطقه</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdateWeakMasterPasswordWarning" xml:space="preserve">
|
<data name="UpdateWeakMasterPasswordWarning" xml:space="preserve">
|
||||||
<value>کلمه عبور اصلی شما با یک یا چند سیاست سازمانتان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را بهروز کنید. در صورت ادامه، شما از نشست فعلی خود خارج میشوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند.</value>
|
<value>کلمه عبور اصلی شما با یک یا چند سیاست سازمانتان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را بهروز کنید. در صورت ادامه، شما از نشست فعلی خود خارج میشوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند.</value>
|
||||||
@@ -2632,9 +2632,15 @@
|
|||||||
<value>کلمه عبور اصلی فعلی</value>
|
<value>کلمه عبور اصلی فعلی</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
|
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
|
||||||
<value>Master password re-prompt help</value>
|
<value>راهنمای درخواست مجدد کلمه عبور اصلی</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
|
||||||
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve</value>
|
<value>باز کردن قفل ممکن است به دلیل حافظه ناکافی انجام شود. تنظیمات حافظه KDF خود را کاهش دهید تا حل شود.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIKey" xml:space="preserve">
|
||||||
|
<value>کلید API نامعتبر</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidAPIToken" xml:space="preserve">
|
||||||
|
<value>کلید توکن API نامعتبر</value>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user