mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
3 Commits
bugfix/SG-
...
v2022.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099d7e22e2 | ||
|
|
c86d4e7984 | ||
|
|
6164106c84 |
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
|
|
||||||
## Before you submit
|
## Before you submit
|
||||||
- Please check for formatting errors (`dotnet format --verify-no-changes`) (required)
|
- [ ] I have checked for formatting errors (`dotnet tool run dotnet-format --check`) (required)
|
||||||
- Please add **unit tests** where it makes sense to do so (encouraged but not required)
|
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
||||||
- If this change requires a **documentation update** - notify the documentation team
|
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||||
- If this change has particular **deployment requirements** - notify the DevOps team
|
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||||
|
|||||||
45
.github/workflows/build.yml
vendored
45
.github/workflows/build.yml
vendored
@@ -441,17 +441,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
env:
|
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||||
KEYVAULT: bitwarden-prod-kv
|
with:
|
||||||
SECRETS: |
|
keyvault: "bitwarden-prod-kv"
|
||||||
appcenter-ios-token
|
secrets: "appcenter-ios-token"
|
||||||
run: |
|
|
||||||
for i in ${SECRETS//,/ }
|
|
||||||
do
|
|
||||||
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
|
|
||||||
echo "::add-mask::$VALUE"
|
|
||||||
echo "::set-output name=$i::$VALUE"
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Decrypt secrets
|
- name: Decrypt secrets
|
||||||
env:
|
env:
|
||||||
@@ -642,17 +635,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
env:
|
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||||
KEYVAULT: bitwarden-prod-kv
|
with:
|
||||||
SECRETS: |
|
keyvault: "bitwarden-prod-kv"
|
||||||
crowdin-api-token
|
secrets: "crowdin-api-token"
|
||||||
run: |
|
|
||||||
for i in ${SECRETS//,/ }
|
|
||||||
do
|
|
||||||
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
|
|
||||||
echo "::add-mask::$VALUE"
|
|
||||||
echo "::set-output name=$i::$VALUE"
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Upload Sources
|
- name: Upload Sources
|
||||||
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
|
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
|
||||||
@@ -709,18 +695,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
|
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
with:
|
||||||
KEYVAULT: bitwarden-prod-kv
|
keyvault: "bitwarden-prod-kv"
|
||||||
SECRETS: |
|
secrets: "devops-alerts-slack-webhook-url"
|
||||||
devops-alerts-slack-webhook-url
|
|
||||||
run: |
|
|
||||||
for i in ${SECRETS//,/ }
|
|
||||||
do
|
|
||||||
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
|
|
||||||
echo "::add-mask::$VALUE"
|
|
||||||
echo "::set-output name=$i::$VALUE"
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Notify Slack on failure
|
- name: Notify Slack on failure
|
||||||
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
|
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
|
||||||
|
|||||||
12
.github/workflows/crowdin-pull.yml
vendored
12
.github/workflows/crowdin-pull.yml
vendored
@@ -24,10 +24,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
|
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-prod-kv"
|
keyvault: "bitwarden-prod-kv"
|
||||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
secrets: "crowdin-api-token"
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: crowdin/github-action@12143a68c213f3c6d9913c9e5023224f7231face
|
uses: crowdin/github-action@12143a68c213f3c6d9913c9e5023224f7231face
|
||||||
@@ -40,12 +40,10 @@ jobs:
|
|||||||
upload_sources: false
|
upload_sources: false
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
github_user_name: "bitwarden-devops-bot"
|
github_user_name: "github-actions"
|
||||||
github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
github_user_email: "<>"
|
||||||
commit_message: "Autosync the updated translations"
|
commit_message: "Autosync the updated translations"
|
||||||
localization_branch_name: crowdin-auto-sync
|
localization_branch_name: crowdin-auto-sync
|
||||||
create_pull_request: true
|
create_pull_request: true
|
||||||
pull_request_title: "Autosync Crowdin Translations"
|
pull_request_title: "Autosync Crowdin Translations"
|
||||||
pull_request_body: "Autosync the updated translations"
|
pull_request_body: "Autosync the updated translations"
|
||||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
|
||||||
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
|
||||||
|
|||||||
40
.github/workflows/version-auto-bump.yml
vendored
40
.github/workflows/version-auto-bump.yml
vendored
@@ -2,38 +2,39 @@
|
|||||||
name: Version Auto Bump
|
name: Version Auto Bump
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
release:
|
||||||
tags:
|
types: [published]
|
||||||
- v**
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
name: "Setup"
|
name: "Setup"
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-20.04
|
||||||
outputs:
|
outputs:
|
||||||
version_number: ${{ steps.version.outputs.new-version }}
|
version_number: ${{ steps.version.outputs.new-version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Branch
|
- name: Checkout Branch
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||||
|
|
||||||
- name: Calculate bumped version
|
- name: Get version to bump
|
||||||
id: version
|
id: version
|
||||||
env:
|
env:
|
||||||
RELEASE_TAG: ${{ github.ref }}
|
RELEASE_TAG: ${{ github.event.release.tag }}
|
||||||
run: |
|
run: |
|
||||||
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
|
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]\.)([0-9])/\1/')
|
||||||
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
|
CURR_VER=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]\.)([0-9])/\2/')
|
||||||
echo "Current Major: $CURR_MAJOR"
|
echo $CURR_VER
|
||||||
echo "Current Patch: $CURR_PATCH"
|
|
||||||
|
((CURR_VER++))
|
||||||
|
NEW_VER=$CURR_MAJOR$CURR_VER
|
||||||
|
|
||||||
|
echo $NEW_VER
|
||||||
|
|
||||||
NEW_PATCH=$((CURR_PATCH+1))
|
|
||||||
NEW_VER=$CURR_MAJOR.$NEW_PATCH
|
|
||||||
echo "New Version: $NEW_VER"
|
|
||||||
echo "::set-output name=new-version::$NEW_VER"
|
echo "::set-output name=new-version::$NEW_VER"
|
||||||
|
|
||||||
trigger_version_bump:
|
trigger_version_bump:
|
||||||
name: "Trigger version bump workflow"
|
name: "Trigger version bump workflow"
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-20.04
|
||||||
needs:
|
needs:
|
||||||
- setup
|
- setup
|
||||||
steps:
|
steps:
|
||||||
@@ -44,10 +45,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
|
env:
|
||||||
with:
|
KEYVAULT: bitwarden-prod-kv
|
||||||
keyvault: "bitwarden-prod-kv"
|
SECRET: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
run: |
|
||||||
|
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $SECRET --query value --output tsv)
|
||||||
|
echo "::add-mask::$VALUE"
|
||||||
|
echo "::set-output name=$SECRET::$VALUE"
|
||||||
|
|
||||||
- name: Call GitHub API to trigger workflow bump
|
- name: Call GitHub API to trigger workflow bump
|
||||||
env:
|
env:
|
||||||
|
|||||||
24
.github/workflows/version-bump.yml
vendored
24
.github/workflows/version-bump.yml
vendored
@@ -16,26 +16,6 @@ jobs:
|
|||||||
- name: Checkout Branch
|
- name: Checkout Branch
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||||
|
|
||||||
- name: Login to Azure - Prod Subscription
|
|
||||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
|
||||||
with:
|
|
||||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
|
||||||
|
|
||||||
- name: Retrieve secrets
|
|
||||||
id: retrieve-secrets
|
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
|
|
||||||
with:
|
|
||||||
keyvault: "bitwarden-prod-kv"
|
|
||||||
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
|
|
||||||
|
|
||||||
- name: Import GPG key
|
|
||||||
uses: crazy-max/ghaction-import-gpg@c8bb57c57e8df1be8c73ff3d59deab1dbc00e0d1
|
|
||||||
with:
|
|
||||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
|
||||||
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
|
||||||
git_user_signingkey: true
|
|
||||||
git_commit_gpgsign: true
|
|
||||||
|
|
||||||
- name: Create Version Branch
|
- name: Create Version Branch
|
||||||
run: |
|
run: |
|
||||||
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||||
@@ -72,8 +52,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup git
|
- name: Setup git
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "bitwarden-devops-bot"
|
git config --local user.name "github-actions[bot]"
|
||||||
|
|
||||||
- name: Check if version changed
|
- name: Check if version changed
|
||||||
id: version-changed
|
id: version-changed
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ namespace Bit.Droid.Accessibility
|
|||||||
|
|
||||||
public static string GetUri(AccessibilityNodeInfo root)
|
public static string GetUri(AccessibilityNodeInfo root)
|
||||||
{
|
{
|
||||||
var uri = string.Concat(Core.Constants.AndroidAppProtocol, root.PackageName);
|
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
|
||||||
if (SupportedBrowsers.ContainsKey(root.PackageName))
|
if (SupportedBrowsers.ContainsKey(root.PackageName))
|
||||||
{
|
{
|
||||||
var browser = SupportedBrowsers[root.PackageName];
|
var browser = SupportedBrowsers[root.PackageName];
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ using Bit.Core.Utilities;
|
|||||||
|
|
||||||
namespace Bit.Droid.Accessibility
|
namespace Bit.Droid.Accessibility
|
||||||
{
|
{
|
||||||
[Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden", Exported = true)]
|
[Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")]
|
||||||
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
||||||
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
||||||
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
|
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||||
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
||||||
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
||||||
<TargetFrameworkVersion>v12.1</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
|
||||||
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
||||||
<NuGetPackageImportStamp>
|
<NuGetPackageImportStamp>
|
||||||
</NuGetPackageImportStamp>
|
</NuGetPackageImportStamp>
|
||||||
@@ -75,24 +75,24 @@
|
|||||||
<Version>2.1.0.4</Version>
|
<Version>2.1.0.4</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Portable.BouncyCastle">
|
<PackageReference Include="Portable.BouncyCastle">
|
||||||
<Version>1.9.0</Version>
|
<Version>1.8.10</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1" />
|
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.13" />
|
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.9" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.16" />
|
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.11" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.9.0" />
|
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.10" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.14" />
|
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1" />
|
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.7.3</Version>
|
<Version>1.7.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||||
<Version>123.0.8</Version>
|
<Version>122.0.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.6.1.1" />
|
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.4.0.4" />
|
||||||
<PackageReference Include="Xamarin.Google.Dagger" Version="2.41.0.2" />
|
<PackageReference Include="Xamarin.Google.Dagger" Version="2.37.0" />
|
||||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
|
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
|
||||||
<Version>118.0.1.2</Version>
|
<Version>117.0.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -152,10 +152,6 @@
|
|||||||
<Compile Include="Utilities\IntentExtensions.cs" />
|
<Compile Include="Utilities\IntentExtensions.cs" />
|
||||||
<Compile Include="Renderers\CustomPageRenderer.cs" />
|
<Compile Include="Renderers\CustomPageRenderer.cs" />
|
||||||
<Compile Include="Effects\NoEmojiKeyboardEffect.cs" />
|
<Compile Include="Effects\NoEmojiKeyboardEffect.cs" />
|
||||||
<Compile Include="Receivers\NotificationDismissReceiver.cs" />
|
|
||||||
<Compile Include="Services\FileService.cs" />
|
|
||||||
<Compile Include="Services\AutofillHandler.cs" />
|
|
||||||
<Compile Include="Constants.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
||||||
@@ -180,7 +176,6 @@
|
|||||||
<AndroidResource Include="Resources\drawable\cog_settings.xml" />
|
<AndroidResource Include="Resources\drawable\cog_settings.xml" />
|
||||||
<AndroidResource Include="Resources\drawable\icon.xml" />
|
<AndroidResource Include="Resources\drawable\icon.xml" />
|
||||||
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
|
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
|
||||||
<AndroidResource Include="Resources\drawable\ic_launcher_monochrome.xml" />
|
|
||||||
<AndroidResource Include="Resources\drawable\ic_warning.xml" />
|
<AndroidResource Include="Resources\drawable\ic_warning.xml" />
|
||||||
<AndroidResource Include="Resources\drawable\id.xml" />
|
<AndroidResource Include="Resources\drawable\id.xml" />
|
||||||
<AndroidResource Include="Resources\drawable\info.xml" />
|
<AndroidResource Include="Resources\drawable\info.xml" />
|
||||||
@@ -218,13 +213,6 @@
|
|||||||
<AndroidResource Include="Resources\values\colors.xml" />
|
<AndroidResource Include="Resources\values\colors.xml" />
|
||||||
<AndroidResource Include="Resources\values\manifest.xml" />
|
<AndroidResource Include="Resources\values\manifest.xml" />
|
||||||
<AndroidResource Include="Resources\values-v30\manifest.xml" />
|
<AndroidResource Include="Resources\values-v30\manifest.xml" />
|
||||||
<AndroidResource Include="Resources\drawable-v26\splash_screen_round.xml" />
|
|
||||||
<AndroidResource Include="Resources\drawable\logo_rounded.xml" />
|
|
||||||
<AndroidResource Include="Resources\drawable-night-v26\splash_screen_round.xml" />
|
|
||||||
<AndroidResource Include="Resources\drawable\ic_notification.xml">
|
|
||||||
<SubType></SubType>
|
|
||||||
<Generator></Generator>
|
|
||||||
</AndroidResource>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
|
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
|
||||||
@@ -292,8 +280,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Resources\values-v30\" />
|
<Folder Include="Resources\values-v30\" />
|
||||||
<Folder Include="Resources\drawable-v26\" />
|
|
||||||
<Folder Include="Resources\drawable-night-v26\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
||||||
@@ -19,7 +19,6 @@ using AndroidX.AutoFill.Inline;
|
|||||||
using AndroidX.AutoFill.Inline.V1;
|
using AndroidX.AutoFill.Inline.V1;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using SaveFlags = Android.Service.Autofill.SaveFlags;
|
using SaveFlags = Android.Service.Autofill.SaveFlags;
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
@@ -271,7 +270,8 @@ namespace Bit.Droid.Autofill
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
intent.PutExtra("autofillFrameworkUri", uri);
|
intent.PutExtra("autofillFrameworkUri", uri);
|
||||||
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
|
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
||||||
|
PendingIntentFlags.CancelCurrent);
|
||||||
|
|
||||||
var overlayPresentation = BuildOverlayPresentation(
|
var overlayPresentation = BuildOverlayPresentation(
|
||||||
AppResources.AutofillWithBitwarden,
|
AppResources.AutofillWithBitwarden,
|
||||||
@@ -324,7 +324,7 @@ namespace Bit.Droid.Autofill
|
|||||||
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
|
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
|
||||||
// "my vault" presentation) so we're including an empty one here
|
// "my vault" presentation) so we're including an empty one here
|
||||||
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
|
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
|
||||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
|
PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent);
|
||||||
}
|
}
|
||||||
var slice = CreateInlinePresentationSlice(
|
var slice = CreateInlinePresentationSlice(
|
||||||
inlinePresentationSpec,
|
inlinePresentationSpec,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ using Bit.Core.Utilities;
|
|||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden", Exported = true)]
|
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden")]
|
||||||
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
|
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
|
||||||
[MetaData("android.autofill", Resource = "@xml/autofillservice")]
|
[MetaData("android.autofill", Resource = "@xml/autofillservice")]
|
||||||
[Register("com.x8bit.bitwarden.Autofill.AutofillService")]
|
[Register("com.x8bit.bitwarden.Autofill.AutofillService")]
|
||||||
@@ -134,7 +134,7 @@ namespace Bit.Droid.Autofill
|
|||||||
{
|
{
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
intent.PutExtra("autofillFrameworkName", parser.Uri
|
intent.PutExtra("autofillFrameworkName", parser.Uri
|
||||||
.Replace(Core.Constants.AndroidAppProtocol, string.Empty)
|
.Replace(Constants.AndroidAppProtocol, string.Empty)
|
||||||
.Replace("https://", string.Empty)
|
.Replace("https://", string.Empty)
|
||||||
.Replace("http://", string.Empty));
|
.Replace("http://", string.Empty));
|
||||||
intent.PutExtra("autofillFrameworkUri", parser.Uri);
|
intent.PutExtra("autofillFrameworkUri", parser.Uri);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace Bit.Droid.Autofill
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_uri = string.Concat(Core.Constants.AndroidAppProtocol, PackageName);
|
_uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
|
||||||
}
|
}
|
||||||
return _uri;
|
return _uri;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Bit.Droid
|
|
||||||
{
|
|
||||||
public static class Constants
|
|
||||||
{
|
|
||||||
public const string PACKAGE_NAME = "com.x8bit.bitwarden";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,14 +5,12 @@ 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.Content.Res;
|
|
||||||
using Android.Nfc;
|
using Android.Nfc;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Views;
|
using AndroidX.Core.Content;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
@@ -20,11 +18,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Receivers;
|
using Bit.Droid.Receivers;
|
||||||
using Bit.Droid.Utilities;
|
using Bit.Droid.Utilities;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Xamarin.Essentials;
|
|
||||||
using ZXing.Net.Mobile.Android;
|
using ZXing.Net.Mobile.Android;
|
||||||
using FileProvider = AndroidX.Core.Content.FileProvider;
|
|
||||||
|
|
||||||
namespace Bit.Droid
|
namespace Bit.Droid
|
||||||
{
|
{
|
||||||
@@ -36,14 +30,11 @@ namespace Bit.Droid
|
|||||||
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
||||||
{
|
{
|
||||||
private IDeviceActionService _deviceActionService;
|
private IDeviceActionService _deviceActionService;
|
||||||
private IFileService _fileService;
|
|
||||||
private IMessagingService _messagingService;
|
private IMessagingService _messagingService;
|
||||||
private IBroadcasterService _broadcasterService;
|
private IBroadcasterService _broadcasterService;
|
||||||
private IStateService _stateService;
|
private IStateService _stateService;
|
||||||
private IAppIdService _appIdService;
|
private IAppIdService _appIdService;
|
||||||
private IEventService _eventService;
|
private IEventService _eventService;
|
||||||
private IPushNotificationListenerService _pushNotificationListenerService;
|
|
||||||
private ILogger _logger;
|
|
||||||
private PendingIntent _eventUploadPendingIntent;
|
private PendingIntent _eventUploadPendingIntent;
|
||||||
private AppOptions _appOptions;
|
private AppOptions _appOptions;
|
||||||
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
|
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
|
||||||
@@ -54,20 +45,17 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
|
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
|
||||||
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
|
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
|
||||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false));
|
PendingIntentFlags.UpdateCurrent);
|
||||||
|
|
||||||
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
|
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
|
||||||
StrictMode.SetThreadPolicy(policy);
|
StrictMode.SetThreadPolicy(policy);
|
||||||
|
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_fileService = ServiceContainer.Resolve<IFileService>();
|
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
|
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
|
||||||
|
|
||||||
TabLayoutResource = Resource.Layout.Tabbar;
|
TabLayoutResource = Resource.Layout.Tabbar;
|
||||||
ToolbarResource = Resource.Layout.Toolbar;
|
ToolbarResource = Resource.Layout.Toolbar;
|
||||||
@@ -82,7 +70,7 @@ namespace Bit.Droid
|
|||||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||||
});
|
});
|
||||||
|
|
||||||
_logger.InitAsync();
|
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||||
|
|
||||||
var toplayout = Window?.DecorView?.RootView;
|
var toplayout = Window?.DecorView?.RootView;
|
||||||
if (toplayout != null)
|
if (toplayout != null)
|
||||||
@@ -93,9 +81,8 @@ namespace Bit.Droid
|
|||||||
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
||||||
Xamarin.Forms.Forms.Init(this, savedInstanceState);
|
Xamarin.Forms.Forms.Init(this, savedInstanceState);
|
||||||
_appOptions = GetOptions();
|
_appOptions = GetOptions();
|
||||||
CreateNotificationChannel();
|
|
||||||
LoadApplication(new App.App(_appOptions));
|
LoadApplication(new App.App(_appOptions));
|
||||||
DisableAndroidFontScale();
|
|
||||||
|
|
||||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
_broadcasterService.Subscribe(_activityKey, (message) =>
|
||||||
{
|
{
|
||||||
@@ -151,15 +138,6 @@ namespace Bit.Droid
|
|||||||
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
|
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
.GetResult();
|
.GetResult();
|
||||||
|
|
||||||
if (Intent?.GetStringExtra(Core.Constants.NotificationData) is string notificationDataJson)
|
|
||||||
{
|
|
||||||
var notificationType = JToken.Parse(notificationDataJson).SelectToken(Core.Constants.NotificationDataType);
|
|
||||||
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
|
|
||||||
{
|
|
||||||
_pushNotificationListenerService.OnNotificationTapped(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewIntent(Intent intent)
|
protected override void OnNewIntent(Intent intent)
|
||||||
@@ -213,13 +191,13 @@ namespace Bit.Droid
|
|||||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
|
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
|
||||||
[GeneratedEnum] Permission[] grantResults)
|
[GeneratedEnum] Permission[] grantResults)
|
||||||
{
|
{
|
||||||
if (requestCode == Core.Constants.SelectFilePermissionRequestCode)
|
if (requestCode == Constants.SelectFilePermissionRequestCode)
|
||||||
{
|
{
|
||||||
if (grantResults.Any(r => r != Permission.Granted))
|
if (grantResults.Any(r => r != Permission.Granted))
|
||||||
{
|
{
|
||||||
_messagingService.Send("selectFileCameraPermissionDenied");
|
_messagingService.Send("selectFileCameraPermissionDenied");
|
||||||
}
|
}
|
||||||
await _fileService.SelectFileAsync();
|
await _deviceActionService.SelectFileAsync();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -232,7 +210,7 @@ namespace Bit.Droid
|
|||||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||||
{
|
{
|
||||||
if (resultCode == Result.Ok &&
|
if (resultCode == Result.Ok &&
|
||||||
(requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
|
(requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode))
|
||||||
{
|
{
|
||||||
Android.Net.Uri uri = null;
|
Android.Net.Uri uri = null;
|
||||||
string fileName = null;
|
string fileName = null;
|
||||||
@@ -254,7 +232,7 @@ namespace Bit.Droid
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestCode == Core.Constants.SaveFileRequestCode)
|
if (requestCode == Constants.SaveFileRequestCode)
|
||||||
{
|
{
|
||||||
_messagingService.Send("selectSaveFileResult",
|
_messagingService.Send("selectSaveFileResult",
|
||||||
new Tuple<string, string>(uri.ToString(), fileName));
|
new Tuple<string, string>(uri.ToString(), fileName));
|
||||||
@@ -295,7 +273,7 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
var intent = new Intent(this, Class);
|
var intent = new Intent(this, Class);
|
||||||
intent.AddFlags(ActivityFlags.SingleTop);
|
intent.AddFlags(ActivityFlags.SingleTop);
|
||||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(0, true));
|
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
||||||
// register for all NDEF tags starting with http och https
|
// register for all NDEF tags starting with http och https
|
||||||
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
||||||
ndef.AddDataScheme("http");
|
ndef.AddDataScheme("http");
|
||||||
@@ -423,38 +401,5 @@ namespace Bit.Droid
|
|||||||
alarmManager.Cancel(_eventUploadPendingIntent);
|
alarmManager.Cancel(_eventUploadPendingIntent);
|
||||||
await _eventService.UploadEventsAsync();
|
await _eventService.UploadEventsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateNotificationChannel()
|
|
||||||
{
|
|
||||||
#if !FDROID
|
|
||||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
|
||||||
{
|
|
||||||
// Notification channels are new in API 26 (and not a part of the
|
|
||||||
// support library). There is no need to create a notification
|
|
||||||
// channel on older versions of Android.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var channel = new NotificationChannel(Core.Constants.AndroidNotificationChannelId, AppResources.AllNotifications, NotificationImportance.Default);
|
|
||||||
if(GetSystemService(NotificationService) is NotificationManager notificationManager)
|
|
||||||
{
|
|
||||||
notificationManager.CreateNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisableAndroidFontScale()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//As we are using NamedSizes the xamarin will change the font size. So we are disabling the Android scaling.
|
|
||||||
Resources.Configuration.FontScale = 1f;
|
|
||||||
BaseContext.Resources.DisplayMetrics.ScaledDensity = Resources.Configuration.FontScale * (float)DeviceDisplay.MainDisplayInfo.Density;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Exception(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,8 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
RegisterLocalServices();
|
RegisterLocalServices();
|
||||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Core.Constants.ClearCiphersCacheKey,
|
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
|
||||||
Core.Constants.AndroidAllClearCipherCacheKeys);
|
Constants.AndroidAllClearCipherCacheKeys);
|
||||||
InitializeAppSetup();
|
|
||||||
|
|
||||||
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
|
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
|
||||||
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
|
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
|
||||||
@@ -72,9 +71,8 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
ServiceContainer.Resolve<IAuthService>("authService"),
|
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||||
ServiceContainer.Resolve<ILogger>("logger"),
|
ServiceContainer.Resolve<ILogger>("logger"));
|
||||||
ServiceContainer.Resolve<IMessagingService>("messagingService"));
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||||
@@ -139,9 +137,8 @@ namespace Bit.Droid
|
|||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var clipboardService = new ClipboardService(stateService);
|
var clipboardService = new ClipboardService(stateService);
|
||||||
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
|
||||||
var fileService = new FileService(stateService, broadcasterService);
|
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
||||||
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService, new LazyResolve<IEventService>());
|
|
||||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||||
messagingService, broadcasterService);
|
messagingService, broadcasterService);
|
||||||
var biometricService = new BiometricService();
|
var biometricService = new BiometricService();
|
||||||
@@ -160,8 +157,6 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
|
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
|
||||||
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
|
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
|
||||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||||
ServiceContainer.Register<IFileService>(fileService);
|
|
||||||
ServiceContainer.Register<IAutofillHandler>(autofillHandler);
|
|
||||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||||
@@ -198,12 +193,5 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeAppSetup()
|
|
||||||
{
|
|
||||||
var appSetup = new AppSetup();
|
|
||||||
appSetup.InitializeServicesLastChance();
|
|
||||||
ServiceContainer.Register<IAppSetup>("appSetup", appSetup);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?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="2022.10.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.9.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
|
||||||
<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" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#if !FDROID
|
#if !FDROID
|
||||||
using System;
|
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Firebase.Messaging;
|
using Firebase.Messaging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -18,41 +16,34 @@ namespace Bit.Droid.Push
|
|||||||
{
|
{
|
||||||
public async override void OnNewToken(string token)
|
public async override void OnNewToken(string token)
|
||||||
{
|
{
|
||||||
try {
|
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||||
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
|
||||||
|
|
||||||
await stateService.SetPushRegisteredTokenAsync(token);
|
await stateService.SetPushRegisteredTokenAsync(token);
|
||||||
await pushNotificationService.RegisterAsync();
|
await pushNotificationService.RegisterAsync();
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Instance.Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async override void OnMessageReceived(RemoteMessage message)
|
public async override void OnMessageReceived(RemoteMessage message)
|
||||||
{
|
{
|
||||||
|
if (message?.Data == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (message?.Data == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = JObject.Parse(data);
|
var obj = JObject.Parse(data);
|
||||||
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
|
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
|
||||||
"pushNotificationListenerService");
|
"pushNotificationListenerService");
|
||||||
await listener.OnMessageAsync(obj, Device.Android);
|
await listener.OnMessageAsync(obj, Device.Android);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (JsonReaderException ex)
|
||||||
{
|
{
|
||||||
Logger.Instance.Exception(ex);
|
System.Diagnostics.Debug.WriteLine(ex.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
using Android.Content;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Models;
|
|
||||||
using Bit.App.Services;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using CoreConstants = Bit.Core.Constants;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Receivers
|
|
||||||
{
|
|
||||||
[BroadcastReceiver(Name = Constants.PACKAGE_NAME + "." + nameof(NotificationDismissReceiver), Exported = false)]
|
|
||||||
public class NotificationDismissReceiver : BroadcastReceiver
|
|
||||||
{
|
|
||||||
private readonly LazyResolve<IPushNotificationListenerService> _pushNotificationListenerService = new LazyResolve<IPushNotificationListenerService>();
|
|
||||||
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
|
|
||||||
|
|
||||||
public override void OnReceive(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (intent?.GetStringExtra(CoreConstants.NotificationData) is string notificationDataJson)
|
|
||||||
{
|
|
||||||
var notificationType = JToken.Parse(notificationDataJson).SelectToken(CoreConstants.NotificationDataType);
|
|
||||||
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
|
|
||||||
{
|
|
||||||
_pushNotificationListenerService.Value.OnNotificationDismissed(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Value.Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/darkgray"/>
|
|
||||||
<foreground android:drawable="@drawable/logo_rounded"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@drawable/logo_rounded"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<group
|
|
||||||
android:scaleX="0.099"
|
|
||||||
android:scaleY="0.099"
|
|
||||||
android:translateX="24.3"
|
|
||||||
android:translateY="24.3">
|
|
||||||
<path
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:pathData="M481.4,102.2c-3.7,-3.7 -8.1,-5.6 -13.1,-5.6L131.7,96.6c-5.1,0 -9.4,1.9 -13.1,5.6C114.9,105.9 113,110.2 113,115.3v224.4c0,16.7 3.3,33.4 9.8,49.8c6.5,16.5 14.6,31.1 24.3,43.8c9.6,12.8 21.1,25.2 34.5,37.2c13.3,12.1 25.7,22.1 37,30.1c11.3,8 23.1,15.5 35.4,22.6c12.3,7.1 21,11.9 26.2,14.5c5.2,2.5 9.3,4.5 12.4,5.8c2.3,1.2 4.9,1.8 7.6,1.8c2.7,0 5.3,-0.6 7.6,-1.8c3.1,-1.4 7.3,-3.3 12.4,-5.8c5.2,-2.5 13.9,-7.4 26.2,-14.5c12.3,-7.1 24.1,-14.7 35.4,-22.6c11.3,-8 23.6,-18 37,-30.1c13.3,-12.1 24.8,-24.5 34.5,-37.2c9.6,-12.8 17.7,-27.4 24.2,-43.8c6.5,-16.5 9.8,-33.1 9.8,-49.8L487.3,115.3C487,110.2 485.1,105.9 481.4,102.2zM438,341.8C438,423 300,493 300,493L300,144.6h138C438,144.6 438,260.6 438,341.8z" />
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<vector android:height="24dp" android:viewportHeight="420"
|
|
||||||
android:viewportWidth="420" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#FFFFFF" android:pathData="M350.43,40.516C347.563,37.65 344.153,36.178 340.281,36.178L79.487,36.178C75.538,36.178 72.206,37.65 69.338,40.516C66.472,43.384 65,46.716 65,50.665L65,224.527C65,237.466 67.557,250.405 72.593,263.112C77.629,275.895 83.904,287.207 91.42,297.046C98.857,306.964 107.768,316.571 118.149,325.869C128.455,335.242 138.063,342.99 146.817,349.19C155.573,355.387 164.715,361.198 174.243,366.699C183.773,372.2 190.514,375.919 194.543,377.933C198.572,379.871 201.749,381.421 204.151,382.426C205.932,383.357 207.948,383.821 210.04,383.821C212.131,383.821 214.145,383.357 215.929,382.426C218.329,381.344 221.584,379.871 225.534,377.933C229.563,375.997 236.304,372.2 245.832,366.699C255.365,361.198 264.506,355.311 273.262,349.19C282.017,342.99 291.545,335.242 301.928,325.869C312.232,316.493 321.142,306.886 328.657,297.046C336.096,287.129 342.372,275.819 347.407,263.112C352.444,250.328 355,237.466 355,224.527L355,50.665C354.768,46.716 353.296,43.384 350.43,40.516ZM316.804,226.154C316.804,289.067 209.883,343.302 209.883,343.302L209.883,73.368L316.804,73.368C316.804,73.368 316.804,163.242 316.804,226.154Z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<group android:scaleX="0.11454546"
|
|
||||||
android:scaleY="0.11454546"
|
|
||||||
android:translateX="31.663637"
|
|
||||||
android:translateY="27.54">
|
|
||||||
<path
|
|
||||||
android:pathData="M376.4,12.2c-3.7,-3.7 -8.1,-5.6 -13.1,-5.6H26.7c-5.1,0 -9.4,1.9 -13.1,5.6C9.9,15.9 8,20.2 8,25.3v224.4c0,16.7 3.3,33.4 9.8,49.8c6.5,16.5 14.6,31.1 24.3,43.8c9.6,12.8 21.1,25.2 34.5,37.2c13.3,12.1 25.7,22.1 37,30.1c11.3,8 23.1,15.5 35.4,22.6c12.3,7.1 21,11.9 26.2,14.5c5.2,2.5 9.3,4.5 12.4,5.8c2.3,1.2 4.9,1.8 7.6,1.8c2.7,0 5.3,-0.6 7.6,-1.8c3.1,-1.4 7.3,-3.3 12.4,-5.8c5.2,-2.5 13.9,-7.4 26.2,-14.5c12.3,-7.1 24.1,-14.7 35.4,-22.6c11.3,-8 23.6,-18 37,-30.1c13.3,-12.1 24.8,-24.5 34.5,-37.2c9.6,-12.8 17.7,-27.4 24.2,-43.8c6.5,-16.5 9.8,-33.1 9.8,-49.8V25.3C382,20.2 380.1,15.9 376.4,12.2zM333,251.8C333,333 195,403 195,403V54.6h138C333,54.6 333,170.6 333,251.8z"
|
|
||||||
android:fillColor="#FFFFFF"/>
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
<style name="LaunchTheme" parent="BaseTheme">
|
<style name="LaunchTheme" parent="BaseTheme">
|
||||||
<item name="android:windowBackground">@drawable/splash_screen_dark</item>
|
<item name="android:windowBackground">@drawable/splash_screen_dark</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="BaseTheme" parent="Theme.AppCompat">
|
<style name="BaseTheme" parent="Theme.AppCompat">
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
<style name="LaunchTheme" parent="BaseTheme">
|
<style name="LaunchTheme" parent="BaseTheme">
|
||||||
<item name="android:windowBackground">@drawable/splash_screen</item>
|
<item name="android:windowBackground">@drawable/splash_screen</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowSplashScreenBackground">@color/ic_launcher_background</item>
|
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="BaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
<style name="BaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
|||||||
@@ -6,5 +6,4 @@
|
|||||||
android:accessibilityFeedbackType="feedbackGeneric"
|
android:accessibilityFeedbackType="feedbackGeneric"
|
||||||
android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
|
android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
|
||||||
android:notificationTimeout="100"
|
android:notificationTimeout="100"
|
||||||
android:canRetrieveWindowContent="true"
|
android:canRetrieveWindowContent="true"/>
|
||||||
android:isAccessibilityTool="false"/>
|
|
||||||
@@ -1,21 +1,10 @@
|
|||||||
#if !FDROID
|
#if !FDROID
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.OS;
|
|
||||||
using AndroidX.Core.App;
|
using AndroidX.Core.App;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Models;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Droid.Receivers;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using static Xamarin.Essentials.Platform;
|
|
||||||
using Intent = Android.Content.Intent;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
{
|
{
|
||||||
@@ -34,11 +23,6 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public bool IsRegisteredForPush => NotificationManagerCompat.From(Android.App.Application.Context)?.AreNotificationsEnabled() ?? false;
|
public bool IsRegisteredForPush => NotificationManagerCompat.From(Android.App.Application.Context)?.AreNotificationsEnabled() ?? false;
|
||||||
|
|
||||||
public Task<bool> AreNotificationsSettingsEnabledAsync()
|
|
||||||
{
|
|
||||||
return Task.FromResult(IsRegisteredForPush);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GetTokenAsync()
|
public async Task<string> GetTokenAsync()
|
||||||
{
|
{
|
||||||
return await _stateService.GetPushCurrentTokenAsync();
|
return await _stateService.GetPushCurrentTokenAsync();
|
||||||
@@ -63,50 +47,6 @@ namespace Bit.Droid.Services
|
|||||||
// Do we ever need to unregister?
|
// Do we ever need to unregister?
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DismissLocalNotification(string notificationId)
|
|
||||||
{
|
|
||||||
if (int.TryParse(notificationId, out int intNotificationId))
|
|
||||||
{
|
|
||||||
var notificationManager = NotificationManagerCompat.From(Android.App.Application.Context);
|
|
||||||
notificationManager.Cancel(intNotificationId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendLocalNotification(string title, string message, BaseNotificationData data)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(data.Id))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("notificationId cannot be null or empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = Android.App.Application.Context;
|
|
||||||
var intent = new Intent(context, typeof(MainActivity));
|
|
||||||
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
|
|
||||||
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
|
|
||||||
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
|
|
||||||
|
|
||||||
var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
|
|
||||||
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
|
|
||||||
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
|
|
||||||
|
|
||||||
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId)
|
|
||||||
.SetContentIntent(pendingIntent)
|
|
||||||
.SetContentTitle(title)
|
|
||||||
.SetContentText(message)
|
|
||||||
.SetSmallIcon(Resource.Drawable.ic_notification)
|
|
||||||
.SetColor((int)Android.Graphics.Color.White)
|
|
||||||
.SetDeleteIntent(deletePendingIntent)
|
|
||||||
.SetAutoCancel(true);
|
|
||||||
|
|
||||||
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
|
|
||||||
{
|
|
||||||
builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000);
|
|
||||||
}
|
|
||||||
|
|
||||||
var notificationManager = NotificationManagerCompat.From(context);
|
|
||||||
notificationManager.Notify(int.Parse(data.Id), builder.Build());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,210 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Android.App;
|
|
||||||
using Android.App.Assist;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.OS;
|
|
||||||
using Android.Provider;
|
|
||||||
using Android.Views.Autofill;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.Droid.Autofill;
|
|
||||||
using Plugin.CurrentActivity;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
|
||||||
{
|
|
||||||
public class AutofillHandler : IAutofillHandler
|
|
||||||
{
|
|
||||||
private readonly IStateService _stateService;
|
|
||||||
private readonly IMessagingService _messagingService;
|
|
||||||
private readonly IClipboardService _clipboardService;
|
|
||||||
private readonly LazyResolve<IEventService> _eventService;
|
|
||||||
|
|
||||||
public AutofillHandler(IStateService stateService,
|
|
||||||
IMessagingService messagingService,
|
|
||||||
IClipboardService clipboardService,
|
|
||||||
LazyResolve<IEventService> eventService)
|
|
||||||
{
|
|
||||||
_stateService = stateService;
|
|
||||||
_messagingService = messagingService;
|
|
||||||
_clipboardService = clipboardService;
|
|
||||||
_eventService = eventService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AutofillServiceEnabled()
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
var afm = (AutofillManager)activity.GetSystemService(
|
|
||||||
Java.Lang.Class.FromType(typeof(AutofillManager)));
|
|
||||||
return afm.IsEnabled && afm.HasEnabledAutofillServices;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SupportsAutofillService()
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
|
||||||
var manager = activity.GetSystemService(type) as AutofillManager;
|
|
||||||
return manager.IsAutofillSupported;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Autofill(CipherView cipher)
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
if (activity == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
|
|
||||||
{
|
|
||||||
if (cipher == null)
|
|
||||||
{
|
|
||||||
activity.SetResult(Result.Canceled);
|
|
||||||
activity.Finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var structure = activity.Intent.GetParcelableExtra(
|
|
||||||
AutofillManager.ExtraAssistStructure) as AssistStructure;
|
|
||||||
if (structure == null)
|
|
||||||
{
|
|
||||||
activity.SetResult(Result.Canceled);
|
|
||||||
activity.Finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var parser = new Parser(structure, activity.ApplicationContext);
|
|
||||||
parser.Parse();
|
|
||||||
if ((!parser.FieldCollection?.Fields?.Any() ?? true) || string.IsNullOrWhiteSpace(parser.Uri))
|
|
||||||
{
|
|
||||||
activity.SetResult(Result.Canceled);
|
|
||||||
activity.Finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var task = CopyTotpAsync(cipher);
|
|
||||||
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher));
|
|
||||||
var replyIntent = new Intent();
|
|
||||||
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
|
|
||||||
activity.SetResult(Result.Ok, replyIntent);
|
|
||||||
activity.Finish();
|
|
||||||
var eventTask = _eventService.Value.CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var data = new Intent();
|
|
||||||
if (cipher?.Login == null)
|
|
||||||
{
|
|
||||||
data.PutExtra("canceled", "true");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var task = CopyTotpAsync(cipher);
|
|
||||||
data.PutExtra("uri", cipher.Login.Uri);
|
|
||||||
data.PutExtra("username", cipher.Login.Username);
|
|
||||||
data.PutExtra("password", cipher.Login.Password);
|
|
||||||
}
|
|
||||||
if (activity.Parent == null)
|
|
||||||
{
|
|
||||||
activity.SetResult(Result.Ok, data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
activity.Parent.SetResult(Result.Ok, data);
|
|
||||||
}
|
|
||||||
activity.Finish();
|
|
||||||
_messagingService.Send("finishMainActivity");
|
|
||||||
if (cipher != null)
|
|
||||||
{
|
|
||||||
var eventTask = _eventService.Value.CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseAutofill()
|
|
||||||
{
|
|
||||||
Autofill(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AutofillAccessibilityServiceRunning()
|
|
||||||
{
|
|
||||||
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
|
|
||||||
Settings.Secure.EnabledAccessibilityServices);
|
|
||||||
return Application.Context.PackageName != null &&
|
|
||||||
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AutofillAccessibilityOverlayPermitted()
|
|
||||||
{
|
|
||||||
return Accessibility.AccessibilityHelpers.OverlayPermitted();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void DisableAutofillService()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
|
||||||
var manager = activity.GetSystemService(type) as AutofillManager;
|
|
||||||
manager.DisableAutofillServices();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AutofillServicesEnabled()
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
|
|
||||||
{
|
|
||||||
// Android 5-6: Both accessibility & overlay are required or nothing happens
|
|
||||||
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
|
|
||||||
{
|
|
||||||
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
|
|
||||||
return AutofillAccessibilityServiceRunning();
|
|
||||||
}
|
|
||||||
// Android 8+: Either autofill or accessibility is required
|
|
||||||
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CopyTotpAsync(CipherView cipher)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
|
|
||||||
{
|
|
||||||
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
|
|
||||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
|
||||||
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
|
|
||||||
{
|
|
||||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
|
||||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
|
||||||
if (totp != null)
|
|
||||||
{
|
|
||||||
await _clipboardService.CopyTextAsync(totp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ using Android.Content;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Droid.Receivers;
|
using Bit.Droid.Receivers;
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ namespace Bit.Droid.Services
|
|||||||
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
|
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
|
||||||
0,
|
0,
|
||||||
new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
|
new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
|
||||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false)));
|
PendingIntentFlags.UpdateCurrent));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Android;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
|
using Android.App.Assist;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using Android.Nfc;
|
using Android.Nfc;
|
||||||
@@ -9,13 +14,20 @@ using Android.Provider;
|
|||||||
using Android.Text;
|
using Android.Text;
|
||||||
using Android.Text.Method;
|
using Android.Text.Method;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
|
using Android.Views.Autofill;
|
||||||
using Android.Views.InputMethods;
|
using Android.Views.InputMethods;
|
||||||
|
using Android.Webkit;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
|
using AndroidX.Core.App;
|
||||||
|
using AndroidX.Core.Content;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Droid.Autofill;
|
||||||
using Bit.Droid.Utilities;
|
using Bit.Droid.Utilities;
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
|
|
||||||
@@ -23,20 +35,38 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
public class DeviceActionService : IDeviceActionService
|
public class DeviceActionService : IDeviceActionService
|
||||||
{
|
{
|
||||||
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
private readonly Func<IEventService> _eventServiceFunc;
|
||||||
private AlertDialog _progressDialog;
|
private AlertDialog _progressDialog;
|
||||||
object _progressDialogLock = new object();
|
object _progressDialogLock = new object();
|
||||||
|
|
||||||
|
private bool _cameraPermissionsDenied;
|
||||||
private Toast _toast;
|
private Toast _toast;
|
||||||
private string _userAgent;
|
private string _userAgent;
|
||||||
|
|
||||||
public DeviceActionService(
|
public DeviceActionService(
|
||||||
|
IClipboardService clipboardService,
|
||||||
IStateService stateService,
|
IStateService stateService,
|
||||||
IMessagingService messagingService)
|
IMessagingService messagingService,
|
||||||
|
IBroadcasterService broadcasterService,
|
||||||
|
Func<IEventService> eventServiceFunc)
|
||||||
{
|
{
|
||||||
|
_clipboardService = clipboardService;
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
_messagingService = messagingService;
|
_messagingService = messagingService;
|
||||||
|
_broadcasterService = broadcasterService;
|
||||||
|
_eventServiceFunc = eventServiceFunc;
|
||||||
|
|
||||||
|
_broadcasterService.Subscribe(nameof(DeviceActionService), (message) =>
|
||||||
|
{
|
||||||
|
if (message.Command == "selectFileCameraPermissionDenied")
|
||||||
|
{
|
||||||
|
_cameraPermissionsDenied = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DeviceUserAgent
|
public string DeviceUserAgent
|
||||||
@@ -182,6 +212,184 @@ namespace Bit.Droid.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool OpenFile(byte[] fileData, string id, string fileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
var intent = BuildOpenFileIntent(fileData, fileName);
|
||||||
|
if (intent == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
activity.StartActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanOpenFile(string fileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
|
||||||
|
if (intent == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var activities = activity.PackageManager.QueryIntentActivities(intent,
|
||||||
|
PackageInfoFlags.MatchDefaultOnly);
|
||||||
|
return (activities?.Count ?? 0) > 0;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
|
||||||
|
{
|
||||||
|
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||||
|
if (extension == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
||||||
|
if (mimeType == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
var cachePath = activity.CacheDir;
|
||||||
|
var filePath = Path.Combine(cachePath.Path, fileName);
|
||||||
|
File.WriteAllBytes(filePath, fileData);
|
||||||
|
var file = new Java.IO.File(cachePath, fileName);
|
||||||
|
if (!file.IsFile)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var intent = new Intent(Intent.ActionView);
|
||||||
|
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
|
||||||
|
"com.x8bit.bitwarden.fileprovider", file);
|
||||||
|
intent.SetDataAndType(uri, mimeType);
|
||||||
|
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
|
||||||
|
if (contentUri != null)
|
||||||
|
{
|
||||||
|
var uri = Android.Net.Uri.Parse(contentUri);
|
||||||
|
var stream = activity.ContentResolver.OpenOutputStream(uri);
|
||||||
|
// Using java bufferedOutputStream due to this issue:
|
||||||
|
// https://github.com/xamarin/xamarin-android/issues/3498
|
||||||
|
var javaStream = new Java.IO.BufferedOutputStream(stream);
|
||||||
|
javaStream.Write(fileData);
|
||||||
|
javaStream.Flush();
|
||||||
|
javaStream.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompt for location to save file
|
||||||
|
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||||
|
if (extension == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
||||||
|
if (mimeType == null)
|
||||||
|
{
|
||||||
|
// Unable to identify so fall back to generic "any" type
|
||||||
|
mimeType = "*/*";
|
||||||
|
}
|
||||||
|
|
||||||
|
var intent = new Intent(Intent.ActionCreateDocument);
|
||||||
|
intent.SetType(mimeType);
|
||||||
|
intent.AddCategory(Intent.CategoryOpenable);
|
||||||
|
intent.PutExtra(Intent.ExtraTitle, fileName);
|
||||||
|
|
||||||
|
activity.StartActivityForResult(intent, Constants.SaveFileRequestCode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClearCacheAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
|
||||||
|
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
catch (Exception) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SelectFileAsync()
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
var hasStorageWritePermission = !_cameraPermissionsDenied &&
|
||||||
|
HasPermission(Manifest.Permission.WriteExternalStorage);
|
||||||
|
var additionalIntents = new List<IParcelable>();
|
||||||
|
if (activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
|
||||||
|
{
|
||||||
|
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
|
||||||
|
if (!_cameraPermissionsDenied && !hasStorageWritePermission)
|
||||||
|
{
|
||||||
|
AskPermission(Manifest.Permission.WriteExternalStorage);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
if (!_cameraPermissionsDenied && !hasCameraPermission)
|
||||||
|
{
|
||||||
|
AskPermission(Manifest.Permission.Camera);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
if (!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = new Java.IO.File(activity.FilesDir, "temp_camera_photo.jpg");
|
||||||
|
if (!file.Exists())
|
||||||
|
{
|
||||||
|
file.ParentFile.Mkdirs();
|
||||||
|
file.CreateNewFile();
|
||||||
|
}
|
||||||
|
var outputFileUri = FileProvider.GetUriForFile(activity,
|
||||||
|
"com.x8bit.bitwarden.fileprovider", file);
|
||||||
|
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
|
||||||
|
}
|
||||||
|
catch (Java.IO.IOException) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var docIntent = new Intent(Intent.ActionOpenDocument);
|
||||||
|
docIntent.AddCategory(Intent.CategoryOpenable);
|
||||||
|
docIntent.SetType("*/*");
|
||||||
|
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
|
||||||
|
if (additionalIntents.Count > 0)
|
||||||
|
{
|
||||||
|
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
|
||||||
|
}
|
||||||
|
activity.StartActivityForResult(chooserIntent, Constants.SelectFileRequestCode);
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
public Task<string> DisplayPromptAync(string title = null, string description = null,
|
public Task<string> DisplayPromptAync(string title = null, string description = null,
|
||||||
string text = null, string okButtonText = null, string cancelButtonText = null,
|
string text = null, string okButtonText = null, string cancelButtonText = null,
|
||||||
bool numericKeyboard = false, bool autofocus = true, bool password = false)
|
bool numericKeyboard = false, bool autofocus = true, bool password = false)
|
||||||
@@ -259,6 +467,34 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DisableAutofillService()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
||||||
|
var manager = activity.GetSystemService(type) as AutofillManager;
|
||||||
|
manager.DisableAutofillServices();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AutofillServicesEnabled()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
|
||||||
|
{
|
||||||
|
// Android 5-6: Both accessibility & overlay are required or nothing happens
|
||||||
|
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
|
||||||
|
{
|
||||||
|
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
|
||||||
|
return AutofillAccessibilityServiceRunning();
|
||||||
|
}
|
||||||
|
// Android 8+: Either autofill or accessibility is required
|
||||||
|
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
|
||||||
|
}
|
||||||
|
|
||||||
public string GetBuildNumber()
|
public string GetBuildNumber()
|
||||||
{
|
{
|
||||||
return Application.Context.ApplicationContext.PackageManager.GetPackageInfo(
|
return Application.Context.ApplicationContext.PackageManager.GetPackageInfo(
|
||||||
@@ -290,6 +526,25 @@ namespace Bit.Droid.Services
|
|||||||
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
|
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SupportsAutofillService()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
||||||
|
var manager = activity.GetSystemService(type) as AutofillManager;
|
||||||
|
return manager.IsAutofillSupported;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int SystemMajorVersion()
|
public int SystemMajorVersion()
|
||||||
{
|
{
|
||||||
return (int)Build.VERSION.SdkInt;
|
return (int)Build.VERSION.SdkInt;
|
||||||
@@ -380,6 +635,112 @@ namespace Bit.Droid.Services
|
|||||||
title, cancel, destruction, buttons);
|
title, cancel, destruction, buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Autofill(CipherView cipher)
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
if (activity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
|
||||||
|
{
|
||||||
|
if (cipher == null)
|
||||||
|
{
|
||||||
|
activity.SetResult(Result.Canceled);
|
||||||
|
activity.Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var structure = activity.Intent.GetParcelableExtra(
|
||||||
|
AutofillManager.ExtraAssistStructure) as AssistStructure;
|
||||||
|
if (structure == null)
|
||||||
|
{
|
||||||
|
activity.SetResult(Result.Canceled);
|
||||||
|
activity.Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var parser = new Parser(structure, activity.ApplicationContext);
|
||||||
|
parser.Parse();
|
||||||
|
if ((!parser.FieldCollection?.Fields?.Any() ?? true) || string.IsNullOrWhiteSpace(parser.Uri))
|
||||||
|
{
|
||||||
|
activity.SetResult(Result.Canceled);
|
||||||
|
activity.Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var task = CopyTotpAsync(cipher);
|
||||||
|
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher));
|
||||||
|
var replyIntent = new Intent();
|
||||||
|
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
|
||||||
|
activity.SetResult(Result.Ok, replyIntent);
|
||||||
|
activity.Finish();
|
||||||
|
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var data = new Intent();
|
||||||
|
if (cipher?.Login == null)
|
||||||
|
{
|
||||||
|
data.PutExtra("canceled", "true");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var task = CopyTotpAsync(cipher);
|
||||||
|
data.PutExtra("uri", cipher.Login.Uri);
|
||||||
|
data.PutExtra("username", cipher.Login.Username);
|
||||||
|
data.PutExtra("password", cipher.Login.Password);
|
||||||
|
}
|
||||||
|
if (activity.Parent == null)
|
||||||
|
{
|
||||||
|
activity.SetResult(Result.Ok, data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activity.Parent.SetResult(Result.Ok, data);
|
||||||
|
}
|
||||||
|
activity.Finish();
|
||||||
|
_messagingService.Send("finishMainActivity");
|
||||||
|
if (cipher != null)
|
||||||
|
{
|
||||||
|
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseAutofill()
|
||||||
|
{
|
||||||
|
Autofill(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Background()
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
|
||||||
|
{
|
||||||
|
activity.SetResult(Result.Canceled);
|
||||||
|
activity.Finish();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activity.MoveTaskToBack(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AutofillAccessibilityServiceRunning()
|
||||||
|
{
|
||||||
|
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
|
||||||
|
Settings.Secure.EnabledAccessibilityServices);
|
||||||
|
return Application.Context.PackageName != null &&
|
||||||
|
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AutofillAccessibilityOverlayPermitted()
|
||||||
|
{
|
||||||
|
return Accessibility.AccessibilityHelpers.OverlayPermitted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasAutofillService()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenAccessibilityOverlayPermissionSettings()
|
public void OpenAccessibilityOverlayPermissionSettings()
|
||||||
{
|
{
|
||||||
@@ -410,6 +771,25 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AutofillServiceEnabled()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
var afm = (AutofillManager)activity.GetSystemService(
|
||||||
|
Java.Lang.Class.FromType(typeof(AutofillManager)));
|
||||||
|
return afm.IsEnabled && afm.HasEnabledAutofillServices;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenAccessibilitySettings()
|
public void OpenAccessibilitySettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -468,6 +848,61 @@ namespace Bit.Droid.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool DeleteDir(Java.IO.File dir)
|
||||||
|
{
|
||||||
|
if (dir != null && dir.IsDirectory)
|
||||||
|
{
|
||||||
|
var children = dir.List();
|
||||||
|
for (int i = 0; i < children.Length; i++)
|
||||||
|
{
|
||||||
|
var success = DeleteDir(new Java.IO.File(dir, children[i]));
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dir.Delete();
|
||||||
|
}
|
||||||
|
else if (dir != null && dir.IsFile)
|
||||||
|
{
|
||||||
|
return dir.Delete();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasPermission(string permission)
|
||||||
|
{
|
||||||
|
return ContextCompat.CheckSelfPermission(
|
||||||
|
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AskPermission(string permission)
|
||||||
|
{
|
||||||
|
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
|
||||||
|
Constants.SelectFilePermissionRequestCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
|
||||||
|
{
|
||||||
|
var intents = new List<IParcelable>();
|
||||||
|
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
|
||||||
|
var captureIntent = new Intent(MediaStore.ActionImageCapture);
|
||||||
|
var listCam = pm.QueryIntentActivities(captureIntent, 0);
|
||||||
|
foreach (var res in listCam)
|
||||||
|
{
|
||||||
|
var packageName = res.ActivityInfo.PackageName;
|
||||||
|
var intent = new Intent(captureIntent);
|
||||||
|
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
|
||||||
|
intent.SetPackage(packageName);
|
||||||
|
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
|
||||||
|
intents.Add(intent);
|
||||||
|
}
|
||||||
|
return intents;
|
||||||
|
}
|
||||||
|
|
||||||
private Intent RateIntentForUrl(string url, Activity activity)
|
private Intent RateIntentForUrl(string url, Activity activity)
|
||||||
{
|
{
|
||||||
var intent = new Intent(Intent.ActionView, Android.Net.Uri.Parse($"{url}?id={activity.PackageName}"));
|
var intent = new Intent(Intent.ActionView, Android.Net.Uri.Parse($"{url}?id={activity.PackageName}"));
|
||||||
@@ -485,6 +920,24 @@ namespace Bit.Droid.Services
|
|||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CopyTotpAsync(CipherView cipher)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
|
||||||
|
{
|
||||||
|
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
|
||||||
|
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||||
|
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
|
||||||
|
{
|
||||||
|
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||||
|
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||||
|
if (totp != null)
|
||||||
|
{
|
||||||
|
await _clipboardService.CopyTextAsync(totp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public float GetSystemFontSizeScale()
|
public float GetSystemFontSizeScale()
|
||||||
{
|
{
|
||||||
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
||||||
@@ -511,20 +964,5 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
activity.RunOnUiThread(() => activity.Window.AddFlags(WindowManagerFlags.Secure));
|
activity.RunOnUiThread(() => activity.Window.AddFlags(WindowManagerFlags.Secure));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenAppSettings()
|
|
||||||
{
|
|
||||||
var intent = new Intent(Android.Provider.Settings.ActionApplicationDetailsSettings);
|
|
||||||
intent.AddFlags(ActivityFlags.NewTask);
|
|
||||||
var uri = Android.Net.Uri.FromParts("package", Application.Context.PackageName, null);
|
|
||||||
intent.SetData(uri);
|
|
||||||
Application.Context.StartActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseExtensionPopUp()
|
|
||||||
{
|
|
||||||
// only used by iOS
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,278 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Android;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Content.PM;
|
|
||||||
using Android.OS;
|
|
||||||
using Android.Provider;
|
|
||||||
using Android.Webkit;
|
|
||||||
using AndroidX.Core.App;
|
|
||||||
using AndroidX.Core.Content;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Plugin.CurrentActivity;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
|
||||||
{
|
|
||||||
public class FileService : IFileService
|
|
||||||
{
|
|
||||||
private readonly IStateService _stateService;
|
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
|
||||||
|
|
||||||
private bool _cameraPermissionsDenied;
|
|
||||||
|
|
||||||
public FileService(IStateService stateService, IBroadcasterService broadcasterService)
|
|
||||||
{
|
|
||||||
_stateService = stateService;
|
|
||||||
_broadcasterService = broadcasterService;
|
|
||||||
|
|
||||||
_broadcasterService.Subscribe(nameof(FileService), (message) =>
|
|
||||||
{
|
|
||||||
if (message.Command == "selectFileCameraPermissionDenied")
|
|
||||||
{
|
|
||||||
_cameraPermissionsDenied = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OpenFile(byte[] fileData, string id, string fileName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
var intent = BuildOpenFileIntent(fileData, fileName);
|
|
||||||
if (intent == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
activity.StartActivity(intent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanOpenFile(string fileName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
|
|
||||||
if (intent == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var activities = activity.PackageManager.QueryIntentActivities(intent,
|
|
||||||
PackageInfoFlags.MatchDefaultOnly);
|
|
||||||
return (activities?.Count ?? 0) > 0;
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
|
|
||||||
{
|
|
||||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
|
||||||
if (extension == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
|
||||||
if (mimeType == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
var cachePath = activity.CacheDir;
|
|
||||||
var filePath = Path.Combine(cachePath.Path, fileName);
|
|
||||||
File.WriteAllBytes(filePath, fileData);
|
|
||||||
var file = new Java.IO.File(cachePath, fileName);
|
|
||||||
if (!file.IsFile)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var intent = new Intent(Intent.ActionView);
|
|
||||||
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
|
|
||||||
"com.x8bit.bitwarden.fileprovider", file);
|
|
||||||
intent.SetDataAndType(uri, mimeType);
|
|
||||||
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
|
|
||||||
if (contentUri != null)
|
|
||||||
{
|
|
||||||
var uri = Android.Net.Uri.Parse(contentUri);
|
|
||||||
var stream = activity.ContentResolver.OpenOutputStream(uri);
|
|
||||||
// Using java bufferedOutputStream due to this issue:
|
|
||||||
// https://github.com/xamarin/xamarin-android/issues/3498
|
|
||||||
var javaStream = new Java.IO.BufferedOutputStream(stream);
|
|
||||||
javaStream.Write(fileData);
|
|
||||||
javaStream.Flush();
|
|
||||||
javaStream.Close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt for location to save file
|
|
||||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
|
||||||
if (extension == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
|
||||||
if (mimeType == null)
|
|
||||||
{
|
|
||||||
// Unable to identify so fall back to generic "any" type
|
|
||||||
mimeType = "*/*";
|
|
||||||
}
|
|
||||||
|
|
||||||
var intent = new Intent(Intent.ActionCreateDocument);
|
|
||||||
intent.SetType(mimeType);
|
|
||||||
intent.AddCategory(Intent.CategoryOpenable);
|
|
||||||
intent.PutExtra(Intent.ExtraTitle, fileName);
|
|
||||||
|
|
||||||
activity.StartActivityForResult(intent, Core.Constants.SaveFileRequestCode);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ClearCacheAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
|
|
||||||
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SelectFileAsync()
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
var hasStorageWritePermission = !_cameraPermissionsDenied &&
|
|
||||||
HasPermission(Manifest.Permission.WriteExternalStorage);
|
|
||||||
var additionalIntents = new List<IParcelable>();
|
|
||||||
if (activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
|
|
||||||
{
|
|
||||||
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
|
|
||||||
if (!_cameraPermissionsDenied && !hasStorageWritePermission)
|
|
||||||
{
|
|
||||||
AskPermission(Manifest.Permission.WriteExternalStorage);
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
if (!_cameraPermissionsDenied && !hasCameraPermission)
|
|
||||||
{
|
|
||||||
AskPermission(Manifest.Permission.Camera);
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
if (!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var file = new Java.IO.File(activity.FilesDir, "temp_camera_photo.jpg");
|
|
||||||
if (!file.Exists())
|
|
||||||
{
|
|
||||||
file.ParentFile.Mkdirs();
|
|
||||||
file.CreateNewFile();
|
|
||||||
}
|
|
||||||
var outputFileUri = FileProvider.GetUriForFile(activity,
|
|
||||||
"com.x8bit.bitwarden.fileprovider", file);
|
|
||||||
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
|
|
||||||
}
|
|
||||||
catch (Java.IO.IOException) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var docIntent = new Intent(Intent.ActionOpenDocument);
|
|
||||||
docIntent.AddCategory(Intent.CategoryOpenable);
|
|
||||||
docIntent.SetType("*/*");
|
|
||||||
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
|
|
||||||
if (additionalIntents.Count > 0)
|
|
||||||
{
|
|
||||||
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
|
|
||||||
}
|
|
||||||
activity.StartActivityForResult(chooserIntent, Core.Constants.SelectFileRequestCode);
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool DeleteDir(Java.IO.File dir)
|
|
||||||
{
|
|
||||||
if (dir is null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dir.IsDirectory)
|
|
||||||
{
|
|
||||||
var children = dir.List();
|
|
||||||
for (int i = 0; i < children.Length; i++)
|
|
||||||
{
|
|
||||||
var success = DeleteDir(new Java.IO.File(dir, children[i]));
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dir.Delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dir.IsFile)
|
|
||||||
{
|
|
||||||
return dir.Delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasPermission(string permission)
|
|
||||||
{
|
|
||||||
return ContextCompat.CheckSelfPermission(
|
|
||||||
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AskPermission(string permission)
|
|
||||||
{
|
|
||||||
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
|
|
||||||
Core.Constants.SelectFilePermissionRequestCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
|
|
||||||
{
|
|
||||||
var intents = new List<IParcelable>();
|
|
||||||
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
|
|
||||||
var captureIntent = new Intent(MediaStore.ActionImageCapture);
|
|
||||||
var listCam = pm.QueryIntentActivities(captureIntent, 0);
|
|
||||||
foreach (var res in listCam)
|
|
||||||
{
|
|
||||||
var packageName = res.ActivityInfo.PackageName;
|
|
||||||
var intent = new Intent(captureIntent);
|
|
||||||
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
|
|
||||||
intent.SetPackage(packageName);
|
|
||||||
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
|
|
||||||
intents.Add(intent);
|
|
||||||
}
|
|
||||||
return intents;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,7 @@ using Java.Lang;
|
|||||||
namespace Bit.Droid.Tile
|
namespace Bit.Droid.Tile
|
||||||
{
|
{
|
||||||
[Service(Permission = Manifest.Permission.BindQuickSettingsTile, Label = "@string/AutoFillTile",
|
[Service(Permission = Manifest.Permission.BindQuickSettingsTile, Label = "@string/AutoFillTile",
|
||||||
Icon = "@drawable/shield", Exported = true)]
|
Icon = "@drawable/shield")]
|
||||||
[IntentFilter(new string[] { ActionQsTile })]
|
[IntentFilter(new string[] { ActionQsTile })]
|
||||||
[Register("com.x8bit.bitwarden.AutofillTileService")]
|
[Register("com.x8bit.bitwarden.AutofillTileService")]
|
||||||
public class AutofillTileService : TileService
|
public class AutofillTileService : TileService
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ using Java.Lang;
|
|||||||
|
|
||||||
namespace Bit.Droid.Tile
|
namespace Bit.Droid.Tile
|
||||||
{
|
{
|
||||||
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Exported = true, Label = "@string/PasswordGenerator",
|
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/PasswordGenerator",
|
||||||
Icon = "@drawable/generate")]
|
Icon = "@drawable/generate")]
|
||||||
[IntentFilter(new string[] { ActionQsTile })]
|
[IntentFilter(new string[] { ActionQsTile })]
|
||||||
[Register("com.x8bit.bitwarden.GeneratorTileService")]
|
[Register("com.x8bit.bitwarden.GeneratorTileService")]
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ using Java.Lang;
|
|||||||
namespace Bit.Droid.Tile
|
namespace Bit.Droid.Tile
|
||||||
{
|
{
|
||||||
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/MyVault",
|
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/MyVault",
|
||||||
Icon = "@drawable/shield",
|
Icon = "@drawable/shield")]
|
||||||
Exported = true)]
|
|
||||||
[IntentFilter(new string[] { ActionQsTile })]
|
[IntentFilter(new string[] { ActionQsTile })]
|
||||||
[Register("com.x8bit.bitwarden.MyVaultTileService")]
|
[Register("com.x8bit.bitwarden.MyVaultTileService")]
|
||||||
public class MyVaultTileService : TileService
|
public class MyVaultTileService : TileService
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
|
||||||
using Android.Provider;
|
using Android.Provider;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
|
||||||
@@ -49,22 +47,5 @@ namespace Bit.Droid.Utilities
|
|||||||
await AppHelpers.SetPreconfiguredSettingsAsync(dict);
|
await AppHelpers.SetPreconfiguredSettingsAsync(dict);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PendingIntentFlags AddPendingIntentMutabilityFlag(PendingIntentFlags pendingIntentFlags, bool isMutable)
|
|
||||||
{
|
|
||||||
//Mutable flag was added on API level 31
|
|
||||||
if (isMutable && Build.VERSION.SdkInt >= BuildVersionCodes.S)
|
|
||||||
{
|
|
||||||
return pendingIntentFlags | PendingIntentFlags.Mutable;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Immutable flag was added on API level 23
|
|
||||||
if (!isMutable && Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
|
||||||
{
|
|
||||||
return pendingIntentFlags | PendingIntentFlags.Immutable;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pendingIntentFlags;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
[Activity(
|
[Activity(
|
||||||
NoHistory = true,
|
NoHistory = true,
|
||||||
LaunchMode = LaunchMode.SingleTop,
|
LaunchMode = LaunchMode.SingleTop)]
|
||||||
Exported = true)]
|
|
||||||
[IntentFilter(new[] { Android.Content.Intent.ActionView },
|
[IntentFilter(new[] { Android.Content.Intent.ActionView },
|
||||||
Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
|
Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
|
||||||
DataScheme = "bitwarden")]
|
DataScheme = "bitwarden")]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
|
||||||
namespace Bit.App.Abstractions
|
namespace Bit.App.Abstractions
|
||||||
{
|
{
|
||||||
@@ -7,36 +8,46 @@ namespace Bit.App.Abstractions
|
|||||||
{
|
{
|
||||||
string DeviceUserAgent { get; }
|
string DeviceUserAgent { get; }
|
||||||
DeviceType DeviceType { get; }
|
DeviceType DeviceType { get; }
|
||||||
int SystemMajorVersion();
|
|
||||||
string SystemModel();
|
|
||||||
string GetBuildNumber();
|
|
||||||
|
|
||||||
void Toast(string text, bool longDuration = false);
|
void Toast(string text, bool longDuration = false);
|
||||||
|
bool LaunchApp(string appName);
|
||||||
Task ShowLoadingAsync(string text);
|
Task ShowLoadingAsync(string text);
|
||||||
Task HideLoadingAsync();
|
Task HideLoadingAsync();
|
||||||
|
bool OpenFile(byte[] fileData, string id, string fileName);
|
||||||
|
bool SaveFile(byte[] fileData, string id, string fileName, string contentUri);
|
||||||
|
bool CanOpenFile(string fileName);
|
||||||
|
Task ClearCacheAsync();
|
||||||
|
Task SelectFileAsync();
|
||||||
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<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
|
void RateApp();
|
||||||
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
|
|
||||||
|
|
||||||
bool SupportsFaceBiometric();
|
bool SupportsFaceBiometric();
|
||||||
Task<bool> SupportsFaceBiometricAsync();
|
Task<bool> SupportsFaceBiometricAsync();
|
||||||
bool SupportsNfc();
|
bool SupportsNfc();
|
||||||
bool SupportsCamera();
|
bool SupportsCamera();
|
||||||
bool SupportsFido2();
|
bool SupportsAutofillService();
|
||||||
|
int SystemMajorVersion();
|
||||||
bool LaunchApp(string appName);
|
string SystemModel();
|
||||||
void RateApp();
|
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
|
||||||
|
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
|
||||||
|
void Autofill(CipherView cipher);
|
||||||
|
void CloseAutofill();
|
||||||
|
void Background();
|
||||||
|
bool AutofillAccessibilityServiceRunning();
|
||||||
|
bool AutofillAccessibilityOverlayPermitted();
|
||||||
|
bool HasAutofillService();
|
||||||
|
bool AutofillServiceEnabled();
|
||||||
|
void DisableAutofillService();
|
||||||
|
bool AutofillServicesEnabled();
|
||||||
|
string GetBuildNumber();
|
||||||
void OpenAccessibilitySettings();
|
void OpenAccessibilitySettings();
|
||||||
void OpenAccessibilityOverlayPermissionSettings();
|
void OpenAccessibilityOverlayPermissionSettings();
|
||||||
void OpenAutofillSettings();
|
void OpenAutofillSettings();
|
||||||
long GetActiveTime();
|
long GetActiveTime();
|
||||||
void CloseMainApp();
|
void CloseMainApp();
|
||||||
|
bool SupportsFido2();
|
||||||
float GetSystemFontSizeScale();
|
float GetSystemFontSizeScale();
|
||||||
Task OnAccountSwitchCompleteAsync();
|
Task OnAccountSwitchCompleteAsync();
|
||||||
Task SetScreenCaptureAllowedAsync();
|
Task SetScreenCaptureAllowedAsync();
|
||||||
void OpenAppSettings();
|
|
||||||
void CloseExtensionPopUp();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Models;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Bit.App.Abstractions
|
namespace Bit.App.Abstractions
|
||||||
@@ -10,8 +9,6 @@ namespace Bit.App.Abstractions
|
|||||||
Task OnRegisteredAsync(string token, string device);
|
Task OnRegisteredAsync(string token, string device);
|
||||||
void OnUnregistered(string device);
|
void OnUnregistered(string device);
|
||||||
void OnError(string message, string device);
|
void OnError(string message, string device);
|
||||||
Task OnNotificationTapped(BaseNotificationData data);
|
|
||||||
Task OnNotificationDismissed(BaseNotificationData data);
|
|
||||||
bool ShouldShowNotification();
|
bool ShouldShowNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.App.Models;
|
|
||||||
|
|
||||||
namespace Bit.App.Abstractions
|
namespace Bit.App.Abstractions
|
||||||
{
|
{
|
||||||
public interface IPushNotificationService
|
public interface IPushNotificationService
|
||||||
{
|
{
|
||||||
bool IsRegisteredForPush { get; }
|
bool IsRegisteredForPush { get; }
|
||||||
Task<bool> AreNotificationsSettingsEnabledAsync();
|
|
||||||
Task<string> GetTokenAsync();
|
Task<string> GetTokenAsync();
|
||||||
Task RegisterAsync();
|
Task RegisterAsync();
|
||||||
Task UnregisterAsync();
|
Task UnregisterAsync();
|
||||||
void SendLocalNotification(string title, string message, BaseNotificationData data);
|
|
||||||
void DismissLocalNotification(string notificationId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<RootNamespace>Bit.App</RootNamespace>
|
<RootNamespace>Bit.App</RootNamespace>
|
||||||
<AssemblyName>BitwardenApp</AssemblyName>
|
<AssemblyName>BitwardenApp</AssemblyName>
|
||||||
<Configurations>Debug;Release;FDroid</Configurations>
|
<Configurations>Debug;Release;FDroid</Configurations>
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.2" />
|
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
||||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.5" />
|
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.2" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
|
||||||
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2515" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -122,20 +122,11 @@
|
|||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Remove="Pages\Accounts\AccountsPopupPage.xaml.cs" />
|
<Compile Remove="Pages\Accounts\AccountsPopupPage.xaml.cs" />
|
||||||
<Compile Update="Pages\Accounts\LoginPasswordlessPage.xaml.cs">
|
|
||||||
<DependentUpon>LoginPasswordlessPage.xaml</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Resources\" />
|
<Folder Include="Resources\" />
|
||||||
<Folder Include="Behaviors\" />
|
<Folder Include="Behaviors\" />
|
||||||
<Folder Include="Lists\" />
|
|
||||||
<Folder Include="Lists\ItemLayouts\" />
|
|
||||||
<Folder Include="Lists\DataTemplateSelectors\" />
|
|
||||||
<Folder Include="Lists\ItemLayouts\CustomFields\" />
|
|
||||||
<Folder Include="Lists\ItemViewModels\" />
|
|
||||||
<Folder Include="Lists\ItemViewModels\CustomFields\" />
|
|
||||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||||
<Folder Include="Utilities\AccountManagement\" />
|
<Folder Include="Utilities\AccountManagement\" />
|
||||||
<Folder Include="Controls\DateTime\" />
|
<Folder Include="Controls\DateTime\" />
|
||||||
@@ -421,12 +412,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Behaviors\" />
|
<None Remove="Behaviors\" />
|
||||||
<None Remove="Xamarin.CommunityToolkit" />
|
<None Remove="Xamarin.CommunityToolkit" />
|
||||||
<None Remove="Lists\" />
|
|
||||||
<None Remove="Lists\DataTemplates\" />
|
|
||||||
<None Remove="Lists\DataTemplateSelectors\" />
|
|
||||||
<None Remove="Lists\DataTemplates\CustomFields\" />
|
|
||||||
<None Remove="Lists\ItemViewModels\" />
|
|
||||||
<None Remove="Lists\ItemViewModels\CustomFields\" />
|
|
||||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||||
<None Remove="Utilities\AccountManagement\" />
|
<None Remove="Utilities\AccountManagement\" />
|
||||||
<None Remove="Controls\DateTime\" />
|
<None Remove="Controls\DateTime\" />
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ using Bit.App.Resources;
|
|||||||
using Bit.App.Services;
|
using Bit.App.Services;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.App.Utilities.AccountManagement;
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Response;
|
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
@@ -27,14 +25,13 @@ namespace Bit.App
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
private readonly ISyncService _syncService;
|
private readonly ISyncService _syncService;
|
||||||
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
|
private readonly IStorageService _secureStorageService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IFileService _fileService;
|
|
||||||
private readonly IAccountsManager _accountsManager;
|
private readonly IAccountsManager _accountsManager;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
|
||||||
private static bool _isResumed;
|
private static bool _isResumed;
|
||||||
// this variable is static because the app is launching new activities on notification click, creating new instances of App.
|
|
||||||
private static bool _pendingCheckPasswordlessLoginRequests;
|
|
||||||
|
|
||||||
public App(AppOptions appOptions)
|
public App(AppOptions appOptions)
|
||||||
{
|
{
|
||||||
@@ -50,10 +47,10 @@ namespace Bit.App
|
|||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
||||||
_authService = ServiceContainer.Resolve<IAuthService>("authService");
|
_authService = ServiceContainer.Resolve<IAuthService>("authService");
|
||||||
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_fileService = ServiceContainer.Resolve<IFileService>();
|
|
||||||
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
|
||||||
|
|
||||||
_accountsManager.Init(() => Options, this);
|
_accountsManager.Init(() => Options, this);
|
||||||
|
|
||||||
@@ -143,10 +140,6 @@ namespace Bit.App
|
|||||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (message.Command == "passwordlessLoginRequest" || message.Command == "unlocked" || message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
|
||||||
{
|
|
||||||
CheckPasswordlessLoginRequestsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -155,80 +148,11 @@ namespace Bit.App
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckPasswordlessLoginRequestsAsync()
|
|
||||||
{
|
|
||||||
if (!_isResumed)
|
|
||||||
{
|
|
||||||
_pendingCheckPasswordlessLoginRequests = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pendingCheckPasswordlessLoginRequests = false;
|
|
||||||
if (await _vaultTimeoutService.IsLockedAsync())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
|
|
||||||
if (notification == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await CheckShouldSwitchActiveUserAsync(notification))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delay to wait for the vault page to appear
|
|
||||||
await Task.Delay(2000);
|
|
||||||
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id);
|
|
||||||
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
|
|
||||||
{
|
|
||||||
PubKey = loginRequestData.PublicKey,
|
|
||||||
Id = loginRequestData.Id,
|
|
||||||
IpAddress = loginRequestData.RequestIpAddress,
|
|
||||||
Email = await _stateService.GetEmailAsync(),
|
|
||||||
FingerprintPhrase = loginRequestData.RequestFingerprint,
|
|
||||||
RequestDate = loginRequestData.CreationDate,
|
|
||||||
DeviceType = loginRequestData.RequestDeviceType,
|
|
||||||
Origin = loginRequestData.Origin,
|
|
||||||
});
|
|
||||||
await _stateService.SetPasswordlessLoginNotificationAsync(null);
|
|
||||||
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
|
|
||||||
if (loginRequestData.CreationDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) > DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> CheckShouldSwitchActiveUserAsync(PasswordlessRequestNotification notification)
|
|
||||||
{
|
|
||||||
var activeUserId = await _stateService.GetActiveUserIdAsync();
|
|
||||||
if (notification.UserId == activeUserId)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
|
|
||||||
await Device.InvokeOnMainThreadAsync(async () =>
|
|
||||||
{
|
|
||||||
var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok);
|
|
||||||
if (result == AppResources.Ok)
|
|
||||||
{
|
|
||||||
await _stateService.SetActiveUserAsync(notification.UserId);
|
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppOptions Options { get; private set; }
|
public AppOptions Options { get; private set; }
|
||||||
|
|
||||||
protected async override void OnStart()
|
protected async override void OnStart()
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
|
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
|
||||||
_isResumed = true;
|
|
||||||
await ClearCacheIfNeededAsync();
|
await ClearCacheIfNeededAsync();
|
||||||
Prime();
|
Prime();
|
||||||
if (string.IsNullOrWhiteSpace(Options.Uri))
|
if (string.IsNullOrWhiteSpace(Options.Uri))
|
||||||
@@ -240,10 +164,6 @@ namespace Bit.App
|
|||||||
SyncIfNeeded();
|
SyncIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_pendingCheckPasswordlessLoginRequests)
|
|
||||||
{
|
|
||||||
CheckPasswordlessLoginRequestsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
@@ -276,10 +196,6 @@ namespace Bit.App
|
|||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
|
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
|
||||||
_isResumed = true;
|
_isResumed = true;
|
||||||
if (_pendingCheckPasswordlessLoginRequests)
|
|
||||||
{
|
|
||||||
CheckPasswordlessLoginRequestsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
ResumedAsync().FireAndForget();
|
ResumedAsync().FireAndForget();
|
||||||
@@ -329,7 +245,7 @@ namespace Bit.App
|
|||||||
var lastClear = await _stateService.GetLastFileCacheClearAsync();
|
var lastClear = await _stateService.GetLastFileCacheClearAsync();
|
||||||
if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
|
if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
|
||||||
{
|
{
|
||||||
var task = Task.Run(() => _fileService.ClearCacheAsync());
|
var task = Task.Run(() => _deviceActionService.ClearCacheAsync());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
AccountView = accountView;
|
AccountView = accountView;
|
||||||
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
|
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
|
||||||
?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email);
|
?.GetOrCreateAvatar(AccountView.Name, AccountView.Email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountView AccountView
|
public AccountView AccountView
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.IO;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -11,8 +10,7 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
public class AvatarImageSource : StreamImageSource
|
public class AvatarImageSource : StreamImageSource
|
||||||
{
|
{
|
||||||
private readonly string _text;
|
private string _data;
|
||||||
private readonly string _id;
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
@@ -23,21 +21,20 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
if (obj is AvatarImageSource avatar)
|
if (obj is AvatarImageSource avatar)
|
||||||
{
|
{
|
||||||
return avatar._id == _id && avatar._text == _text;
|
return avatar._data == _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.Equals(obj);
|
return base.Equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
|
public override int GetHashCode() => _data?.GetHashCode() ?? -1;
|
||||||
|
|
||||||
public AvatarImageSource(string userId = null, string name = null, string email = null)
|
public AvatarImageSource(string name = null, string email = null)
|
||||||
{
|
{
|
||||||
_id = userId;
|
_data = name;
|
||||||
_text = name;
|
if (string.IsNullOrWhiteSpace(_data))
|
||||||
if (string.IsNullOrWhiteSpace(_text))
|
|
||||||
{
|
{
|
||||||
_text = email;
|
_data = email;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,24 +52,24 @@ namespace Bit.App.Controls
|
|||||||
private Stream Draw()
|
private Stream Draw()
|
||||||
{
|
{
|
||||||
string chars;
|
string chars;
|
||||||
string upperCaseText = null;
|
string upperData = null;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_text))
|
if (string.IsNullOrEmpty(_data))
|
||||||
{
|
{
|
||||||
chars = "..";
|
chars = "..";
|
||||||
}
|
}
|
||||||
else if (_text?.Length > 1)
|
else if (_data?.Length > 1)
|
||||||
{
|
{
|
||||||
upperCaseText = _text.ToUpper();
|
upperData = _data.ToUpper();
|
||||||
chars = GetFirstLetters(upperCaseText, 2);
|
chars = GetFirstLetters(upperData, 2);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
chars = upperCaseText = _text.ToUpper();
|
chars = upperData = _data.ToUpper();
|
||||||
}
|
}
|
||||||
|
|
||||||
var bgColor = CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff");
|
var bgColor = StringToColor(upperData);
|
||||||
var textColor = CoreHelpers.TextColorFromBgColor(bgColor);
|
var textColor = Color.White;
|
||||||
var size = 50;
|
var size = 50;
|
||||||
|
|
||||||
using (var bitmap = new SKBitmap(size * 2,
|
using (var bitmap = new SKBitmap(size * 2,
|
||||||
@@ -88,7 +85,7 @@ namespace Bit.App.Controls
|
|||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill,
|
Style = SKPaintStyle.Fill,
|
||||||
StrokeJoin = SKStrokeJoin.Miter,
|
StrokeJoin = SKStrokeJoin.Miter,
|
||||||
Color = SKColor.Parse(bgColor)
|
Color = SKColor.Parse(bgColor.ToHex())
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
|
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
|
||||||
@@ -100,7 +97,7 @@ namespace Bit.App.Controls
|
|||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill,
|
Style = SKPaintStyle.Fill,
|
||||||
StrokeJoin = SKStrokeJoin.Miter,
|
StrokeJoin = SKStrokeJoin.Miter,
|
||||||
Color = SKColor.Parse(bgColor)
|
Color = SKColor.Parse(bgColor.ToHex())
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
||||||
@@ -111,7 +108,7 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
IsAntialias = true,
|
IsAntialias = true,
|
||||||
Style = SKPaintStyle.Fill,
|
Style = SKPaintStyle.Fill,
|
||||||
Color = SKColor.Parse(textColor),
|
Color = SKColor.Parse(textColor.ToHex()),
|
||||||
TextSize = textSize,
|
TextSize = textSize,
|
||||||
TextAlign = SKTextAlign.Center,
|
TextAlign = SKTextAlign.Center,
|
||||||
Typeface = typeface
|
Typeface = typeface
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
public interface IAvatarImageSourcePool
|
public interface IAvatarImageSourcePool
|
||||||
{
|
{
|
||||||
AvatarImageSource GetOrCreateAvatar(string userId, string name, string email);
|
AvatarImageSource GetOrCreateAvatar(string name, string email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AvatarImageSourcePool : IAvatarImageSourcePool
|
public class AvatarImageSourcePool : IAvatarImageSourcePool
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();
|
private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();
|
||||||
|
|
||||||
public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email)
|
public AvatarImageSource GetOrCreateAvatar(string name, string email)
|
||||||
{
|
{
|
||||||
var key = $"{userId}{name}{email}";
|
var key = $"{name}{email}";
|
||||||
if (!_cache.TryGetValue(key, out var avatar))
|
if (!_cache.TryGetValue(key, out var avatar))
|
||||||
{
|
{
|
||||||
avatar = new AvatarImageSource(userId, name, email);
|
avatar = new AvatarImageSource(name, email);
|
||||||
if (!_cache.TryAdd(key, avatar)
|
if (!_cache.TryAdd(key, avatar)
|
||||||
&&
|
&&
|
||||||
!_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add.
|
!_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add.
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Controls
|
namespace Bit.App.Controls
|
||||||
{
|
{
|
||||||
[Obsolete]
|
|
||||||
public class RepeaterView : StackLayout
|
public class RepeaterView : StackLayout
|
||||||
{
|
{
|
||||||
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
|
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
using Bit.App.Lists.ItemViewModels.CustomFields;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.DataTemplateSelectors
|
|
||||||
{
|
|
||||||
public class CustomFieldItemTemplateSelector : DataTemplateSelector
|
|
||||||
{
|
|
||||||
public DataTemplate TextTemplate { get; set; }
|
|
||||||
public DataTemplate BooleanTemplate { get; set; }
|
|
||||||
public DataTemplate LinkedTemplate { get; set; }
|
|
||||||
public DataTemplate HiddenTemplate { get; set; }
|
|
||||||
|
|
||||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
|
||||||
{
|
|
||||||
switch (item)
|
|
||||||
{
|
|
||||||
case BooleanCustomFieldItemViewModel _:
|
|
||||||
return BooleanTemplate;
|
|
||||||
case LinkedCustomFieldItemViewModel _:
|
|
||||||
return LinkedTemplate;
|
|
||||||
case HiddenCustomFieldItemViewModel _:
|
|
||||||
return HiddenTemplate;
|
|
||||||
default:
|
|
||||||
return TextTemplate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<StackLayout
|
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
|
||||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.BooleanCustomFieldItemLayout"
|
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
|
||||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
|
||||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
|
||||||
x:DataType="cfvm:BooleanCustomFieldItemViewModel"
|
|
||||||
Spacing="0" Padding="0">
|
|
||||||
<StackLayout.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
|
||||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
|
||||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
|
||||||
</ResourceDictionary>
|
|
||||||
</StackLayout.Resources>
|
|
||||||
<Grid
|
|
||||||
StyleClass="box-row"
|
|
||||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Label
|
|
||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="0"
|
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
|
||||||
<Label
|
|
||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
|
||||||
IsVisible="{Binding IsEditing}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
VerticalOptions="FillAndExpand"
|
|
||||||
VerticalTextAlignment="Center"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="0"
|
|
||||||
Grid.RowSpan="2" />
|
|
||||||
<controls:IconLabel
|
|
||||||
Text="{Binding BooleanValue, Mode=OneWay, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Checkbox}}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="0"
|
|
||||||
Margin="0, 5, 0, 0"
|
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
|
||||||
<Switch
|
|
||||||
IsToggled="{Binding BooleanValue}"
|
|
||||||
IsVisible="{Binding IsEditing}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="1"
|
|
||||||
Grid.RowSpan="2" />
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
|
||||||
Command="{Binding FieldOptionsCommand}"
|
|
||||||
IsVisible="{Binding IsEditing}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="2"
|
|
||||||
Grid.RowSpan="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
|
||||||
</Grid>
|
|
||||||
<BoxView StyleClass="box-row-separator" />
|
|
||||||
</StackLayout>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
|
||||||
{
|
|
||||||
public partial class BooleanCustomFieldItemLayout : StackLayout
|
|
||||||
{
|
|
||||||
public BooleanCustomFieldItemLayout()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
|
||||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.HiddenCustomFieldItemLayout"
|
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
|
||||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
|
||||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
|
||||||
x:DataType="cfvm:HiddenCustomFieldItemViewModel"
|
|
||||||
Spacing="0" Padding="0">
|
|
||||||
<StackLayout.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
|
||||||
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
|
|
||||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
|
||||||
</ResourceDictionary>
|
|
||||||
</StackLayout.Resources>
|
|
||||||
<Grid
|
|
||||||
StyleClass="box-row"
|
|
||||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Label
|
|
||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="0" />
|
|
||||||
<StackLayout
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="0"
|
|
||||||
IsVisible="{Binding IsEditing, Converter={StaticResource inverseBool}}">
|
|
||||||
<controls:MonoLabel
|
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
IsVisible="{Binding ShowHiddenValue}" />
|
|
||||||
<controls:MonoLabel
|
|
||||||
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
IsVisible="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}" />
|
|
||||||
</StackLayout>
|
|
||||||
<controls:MonoEntry
|
|
||||||
Text="{Binding Field.Value}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="0"
|
|
||||||
IsVisible="{Binding IsEditing}"
|
|
||||||
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}"
|
|
||||||
IsEnabled="{Binding ShowViewHidden}"
|
|
||||||
IsSpellCheckEnabled="False"
|
|
||||||
IsTextPredictionEnabled="False">
|
|
||||||
<Entry.Keyboard>
|
|
||||||
<Keyboard x:FactoryMethod="Create">
|
|
||||||
<x:Arguments>
|
|
||||||
<KeyboardFlags>None</KeyboardFlags>
|
|
||||||
</x:Arguments>
|
|
||||||
</Keyboard>
|
|
||||||
</Entry.Keyboard>
|
|
||||||
</controls:MonoEntry>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding ShowHiddenValue, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
|
||||||
Command="{Binding ToggleHiddenValueCommand}"
|
|
||||||
IsVisible="{Binding ShowViewHidden}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="1"
|
|
||||||
Grid.RowSpan="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
|
||||||
Command="{Binding CopyFieldCommand}"
|
|
||||||
IsVisible="{Binding ShowCopyButton}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="2"
|
|
||||||
Grid.RowSpan="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n Copy}" />
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
|
||||||
Command="{Binding FieldOptionsCommand}"
|
|
||||||
IsVisible="{Binding IsEditing}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="2"
|
|
||||||
Grid.RowSpan="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
|
||||||
</Grid>
|
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
|
||||||
</StackLayout>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
|
||||||
{
|
|
||||||
public partial class HiddenCustomFieldItemLayout : StackLayout
|
|
||||||
{
|
|
||||||
public HiddenCustomFieldItemLayout()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
|
||||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.LinkedCustomFieldItemLayout"
|
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
|
||||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
|
||||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
|
||||||
x:DataType="cfvm:LinkedCustomFieldItemViewModel"
|
|
||||||
Spacing="0" Padding="0">
|
|
||||||
<StackLayout.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
|
||||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
|
||||||
</ResourceDictionary>
|
|
||||||
</StackLayout.Resources>
|
|
||||||
<Grid
|
|
||||||
StyleClass="box-row"
|
|
||||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Label
|
|
||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="0" />
|
|
||||||
<controls:IconLabel
|
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="0"
|
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
|
||||||
<StackLayout
|
|
||||||
StyleClass="box-row, box-row-input"
|
|
||||||
IsVisible="{Binding IsEditing}">
|
|
||||||
<Picker
|
|
||||||
x:Name="_linkedFieldOptionPicker"
|
|
||||||
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
|
||||||
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
|
||||||
ItemDisplayBinding="{Binding Key}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
</StackLayout>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
|
||||||
Command="{Binding FieldOptionsCommand}"
|
|
||||||
IsVisible="{Binding IsEditing}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="1"
|
|
||||||
Grid.RowSpan="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
|
||||||
</Grid>
|
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
|
||||||
</StackLayout>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
|
||||||
{
|
|
||||||
public partial class LinkedCustomFieldItemLayout : StackLayout
|
|
||||||
{
|
|
||||||
public LinkedCustomFieldItemLayout()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
|
||||||
x:Class="Bit.App.Lists.ItemLayouts.CustomFields.TextCustomFieldItemLayout"
|
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
|
||||||
xmlns:cfvm="clr-namespace:Bit.App.Lists.ItemViewModels.CustomFields"
|
|
||||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
|
||||||
x:DataType="cfvm:TextCustomFieldItemViewModel"
|
|
||||||
Spacing="0" Padding="0">
|
|
||||||
<StackLayout.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
|
||||||
<u:BooleanToBoxRowInputPaddingConverter x:Key="booleanToBoxRowInputPaddingConverter" />
|
|
||||||
</ResourceDictionary>
|
|
||||||
</StackLayout.Resources>
|
|
||||||
<Grid
|
|
||||||
StyleClass="box-row"
|
|
||||||
Padding="{Binding IsEditing, Converter={StaticResource booleanToBoxRowInputPaddingConverter}}">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Label
|
|
||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="0" />
|
|
||||||
<Label
|
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="0"
|
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
|
||||||
<Entry
|
|
||||||
Text="{Binding Field.Value}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="0"
|
|
||||||
IsVisible="{Binding IsEditing}" />
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
|
||||||
Command="{Binding CopyFieldCommand}"
|
|
||||||
IsVisible="{Binding ShowCopyButton}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="1"
|
|
||||||
Grid.RowSpan="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n Copy}" />
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
|
||||||
Command="{Binding FieldOptionsCommand}"
|
|
||||||
IsVisible="{Binding IsEditing}"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="1"
|
|
||||||
Grid.RowSpan="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n Options}" />
|
|
||||||
</Grid>
|
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
|
||||||
</StackLayout>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemLayouts.CustomFields
|
|
||||||
{
|
|
||||||
public partial class TextCustomFieldItemLayout : StackLayout
|
|
||||||
{
|
|
||||||
public TextCustomFieldItemLayout()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using System.Windows.Input;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
|
||||||
{
|
|
||||||
public abstract class BaseCustomFieldItemViewModel : ExtendedViewModel, ICustomFieldItemViewModel
|
|
||||||
{
|
|
||||||
protected FieldView _field;
|
|
||||||
protected bool _isEditing;
|
|
||||||
private string[] _additionalFieldProperties = new string[]
|
|
||||||
{
|
|
||||||
nameof(ValueText),
|
|
||||||
nameof(ShowCopyButton)
|
|
||||||
};
|
|
||||||
|
|
||||||
public BaseCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand)
|
|
||||||
{
|
|
||||||
_field = field;
|
|
||||||
_isEditing = isEditing;
|
|
||||||
FieldOptionsCommand = new Command(() => fieldOptionsCommand?.Execute(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public FieldView Field
|
|
||||||
{
|
|
||||||
get => _field;
|
|
||||||
set => SetProperty(ref _field, value,
|
|
||||||
additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(ValueText),
|
|
||||||
nameof(ShowCopyButton),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsEditing => _isEditing;
|
|
||||||
|
|
||||||
public virtual bool ShowCopyButton => false;
|
|
||||||
|
|
||||||
public virtual string ValueText => _field.Value;
|
|
||||||
|
|
||||||
public ICommand FieldOptionsCommand { get; }
|
|
||||||
|
|
||||||
public void TriggerFieldChanged()
|
|
||||||
{
|
|
||||||
TriggerPropertyChanged(nameof(Field), _additionalFieldProperties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using System.Windows.Input;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
|
||||||
{
|
|
||||||
public class BooleanCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
|
||||||
{
|
|
||||||
public BooleanCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand)
|
|
||||||
: base(field, isEditing, fieldOptionsCommand)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool BooleanValue
|
|
||||||
{
|
|
||||||
get => bool.TryParse(Field.Value, out var boolVal) && boolVal;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
Field.Value = value.ToString().ToLower();
|
|
||||||
TriggerPropertyChanged(nameof(BooleanValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
|
||||||
{
|
|
||||||
public interface ICustomFieldItemFactory
|
|
||||||
{
|
|
||||||
ICustomFieldItemViewModel CreateCustomFieldItem(FieldView field,
|
|
||||||
bool isEditing,
|
|
||||||
CipherView cipher,
|
|
||||||
IPasswordPromptable passwordPromptable,
|
|
||||||
ICommand copyFieldCommand,
|
|
||||||
ICommand fieldOptionsCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CustomFieldItemFactory : ICustomFieldItemFactory
|
|
||||||
{
|
|
||||||
readonly II18nService _i18nService;
|
|
||||||
readonly IEventService _eventService;
|
|
||||||
|
|
||||||
public CustomFieldItemFactory(II18nService i18nService, IEventService eventService)
|
|
||||||
{
|
|
||||||
_i18nService = i18nService;
|
|
||||||
_eventService = eventService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICustomFieldItemViewModel CreateCustomFieldItem(FieldView field,
|
|
||||||
bool isEditing,
|
|
||||||
CipherView cipher,
|
|
||||||
IPasswordPromptable passwordPromptable,
|
|
||||||
ICommand copyFieldCommand,
|
|
||||||
ICommand fieldOptionsCommand)
|
|
||||||
{
|
|
||||||
switch (field.Type)
|
|
||||||
{
|
|
||||||
case FieldType.Text:
|
|
||||||
return new TextCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, copyFieldCommand);
|
|
||||||
case FieldType.Boolean:
|
|
||||||
return new BooleanCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand);
|
|
||||||
case FieldType.Hidden:
|
|
||||||
return new HiddenCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, cipher, passwordPromptable, _eventService, copyFieldCommand);
|
|
||||||
case FieldType.Linked:
|
|
||||||
return new LinkedCustomFieldItemViewModel(field, isEditing, fieldOptionsCommand, cipher, _i18nService);
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException("There is no custom field item for field type " + field.Type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
|
||||||
{
|
|
||||||
public class HiddenCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
|
||||||
{
|
|
||||||
private readonly CipherView _cipher;
|
|
||||||
private readonly IPasswordPromptable _passwordPromptable;
|
|
||||||
private readonly IEventService _eventService;
|
|
||||||
private bool _showHiddenValue;
|
|
||||||
|
|
||||||
public HiddenCustomFieldItemViewModel(FieldView field,
|
|
||||||
bool isEditing,
|
|
||||||
ICommand fieldOptionsCommand,
|
|
||||||
CipherView cipher,
|
|
||||||
IPasswordPromptable passwordPromptable,
|
|
||||||
IEventService eventService,
|
|
||||||
ICommand copyFieldCommand)
|
|
||||||
: base(field, isEditing, fieldOptionsCommand)
|
|
||||||
{
|
|
||||||
_cipher = cipher;
|
|
||||||
_passwordPromptable = passwordPromptable;
|
|
||||||
_eventService = eventService;
|
|
||||||
|
|
||||||
CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field));
|
|
||||||
ToggleHiddenValueCommand = new AsyncCommand(ToggleHiddenValueAsync, (Func<bool>)null, ex =>
|
|
||||||
{
|
|
||||||
#if !FDROID
|
|
||||||
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand CopyFieldCommand { get; }
|
|
||||||
|
|
||||||
public ICommand ToggleHiddenValueCommand { get; set; }
|
|
||||||
|
|
||||||
public bool ShowHiddenValue
|
|
||||||
{
|
|
||||||
get => _showHiddenValue;
|
|
||||||
set => SetProperty(ref _showHiddenValue, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowViewHidden => _cipher.ViewPassword || (_isEditing && _field.NewField);
|
|
||||||
|
|
||||||
public override bool ShowCopyButton => !_isEditing && _cipher.ViewPassword && !string.IsNullOrWhiteSpace(Field.Value);
|
|
||||||
|
|
||||||
public async Task ToggleHiddenValueAsync()
|
|
||||||
{
|
|
||||||
if (!_isEditing && !await _passwordPromptable.PromptPasswordAsync())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowHiddenValue = !ShowHiddenValue;
|
|
||||||
if (ShowHiddenValue && (!_isEditing || _cipher?.Id != null))
|
|
||||||
{
|
|
||||||
await _eventService.CollectAsync(
|
|
||||||
Core.Enums.EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using Bit.Core.Models.View;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
|
||||||
{
|
|
||||||
public interface ICustomFieldItemViewModel
|
|
||||||
{
|
|
||||||
FieldView Field { get; set; }
|
|
||||||
|
|
||||||
bool ShowCopyButton { get; }
|
|
||||||
|
|
||||||
void TriggerFieldChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
|
||||||
{
|
|
||||||
public class LinkedCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
|
||||||
{
|
|
||||||
private readonly CipherView _cipher;
|
|
||||||
private readonly II18nService _i18nService;
|
|
||||||
private int _linkedFieldOptionSelectedIndex;
|
|
||||||
|
|
||||||
public LinkedCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand, CipherView cipher, II18nService i18nService)
|
|
||||||
: base(field, isEditing, fieldOptionsCommand)
|
|
||||||
{
|
|
||||||
_cipher = cipher;
|
|
||||||
_i18nService = i18nService;
|
|
||||||
|
|
||||||
LinkedFieldOptionSelectedIndex = Field.LinkedId.HasValue
|
|
||||||
? LinkedFieldOptions.FindIndex(lfo => lfo.Value == Field.LinkedId.Value)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
if (isEditing && Field.LinkedId is null)
|
|
||||||
{
|
|
||||||
field.LinkedId = LinkedFieldOptions[0].Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ValueText
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
|
||||||
return $"{BitwardenIcons.Link} {_i18nService.T(i18nKey)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int LinkedFieldOptionSelectedIndex
|
|
||||||
{
|
|
||||||
get => _linkedFieldOptionSelectedIndex;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref _linkedFieldOptionSelectedIndex, value))
|
|
||||||
{
|
|
||||||
LinkedFieldValueChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions
|
|
||||||
{
|
|
||||||
get => _cipher.LinkedFieldOptions
|
|
||||||
.Select(kvp => new KeyValuePair<string, LinkedIdType>(_i18nService.T(kvp.Key), kvp.Value))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LinkedFieldValueChanged()
|
|
||||||
{
|
|
||||||
if (Field != null && LinkedFieldOptionSelectedIndex > -1)
|
|
||||||
{
|
|
||||||
Field.LinkedId = LinkedFieldOptions.Find(lfo =>
|
|
||||||
lfo.Value == LinkedFieldOptions[LinkedFieldOptionSelectedIndex].Value).Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System.Windows.Input;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Lists.ItemViewModels.CustomFields
|
|
||||||
{
|
|
||||||
public class TextCustomFieldItemViewModel : BaseCustomFieldItemViewModel
|
|
||||||
{
|
|
||||||
public TextCustomFieldItemViewModel(FieldView field, bool isEditing, ICommand fieldOptionsCommand, ICommand copyFieldCommand)
|
|
||||||
: base(field, isEditing, fieldOptionsCommand)
|
|
||||||
{
|
|
||||||
CopyFieldCommand = new Command(() => copyFieldCommand?.Execute(Field));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool ShowCopyButton => !_isEditing && !string.IsNullOrWhiteSpace(Field.Value);
|
|
||||||
|
|
||||||
public ICommand CopyFieldCommand { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using System;
|
|
||||||
namespace Bit.App.Models
|
|
||||||
{
|
|
||||||
public abstract class BaseNotificationData
|
|
||||||
{
|
|
||||||
public abstract string Type { get; }
|
|
||||||
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PasswordlessNotificationData : BaseNotificationData
|
|
||||||
{
|
|
||||||
public const string TYPE = "passwordlessNotificationData";
|
|
||||||
|
|
||||||
public override string Type => TYPE;
|
|
||||||
|
|
||||||
public int TimeoutInMinutes { get; set; }
|
|
||||||
|
|
||||||
public string UserEmail { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<?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.LoginPasswordlessPage"
|
|
||||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
|
||||||
x:DataType="pages:LoginPasswordlessViewModel"
|
|
||||||
Title="{Binding PageTitle}">
|
|
||||||
|
|
||||||
<ContentPage.BindingContext>
|
|
||||||
<pages:LoginPasswordlessViewModel />
|
|
||||||
</ContentPage.BindingContext>
|
|
||||||
|
|
||||||
<ContentPage.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
|
|
||||||
x:Name="_closeItem" x:Key="closeItem" />
|
|
||||||
</ResourceDictionary>
|
|
||||||
</ContentPage.Resources>
|
|
||||||
<StackLayout
|
|
||||||
Padding="7, 0, 7, 20">
|
|
||||||
<ScrollView
|
|
||||||
VerticalOptions="FillAndExpand">
|
|
||||||
<StackLayout>
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n AreYouTryingToLogIn}"
|
|
||||||
FontSize="Title"
|
|
||||||
FontAttributes="Bold"
|
|
||||||
Margin="0,14,0,21"/>
|
|
||||||
<Label
|
|
||||||
Text="{Binding LogInAttemptByLabel}"
|
|
||||||
FontSize="Small"
|
|
||||||
Margin="0,0,0,24"/>
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n FingerprintPhrase}"
|
|
||||||
FontSize="Small"
|
|
||||||
FontAttributes="Bold"/>
|
|
||||||
<controls:MonoLabel
|
|
||||||
FormattedText="{Binding LoginRequest.FingerprintPhrase}"
|
|
||||||
FontSize="Medium"
|
|
||||||
TextColor="{DynamicResource FingerprintPhrase}"
|
|
||||||
Margin="0,0,0,27"/>
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n DeviceType}"
|
|
||||||
FontSize="Small"
|
|
||||||
FontAttributes="Bold"/>
|
|
||||||
<Label
|
|
||||||
Text="{Binding LoginRequest.DeviceType}"
|
|
||||||
FontSize="Small"
|
|
||||||
Margin="0,0,0,21"/>
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n IpAddress}"
|
|
||||||
IsVisible="{Binding ShowIpAddress}"
|
|
||||||
FontSize="Small"
|
|
||||||
FontAttributes="Bold"/>
|
|
||||||
<Label
|
|
||||||
Text="{Binding LoginRequest.IpAddress}"
|
|
||||||
IsVisible="{Binding ShowIpAddress}"
|
|
||||||
FontSize="Small"
|
|
||||||
Margin="0,0,0,21"/>
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n Time}"
|
|
||||||
FontSize="Small"
|
|
||||||
FontAttributes="Bold"/>
|
|
||||||
<Label
|
|
||||||
Text="{Binding TimeOfRequestText}"
|
|
||||||
FontSize="Small"
|
|
||||||
Margin="0,0,0,57"/>
|
|
||||||
</StackLayout>
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
Text="{u:I18n ConfirmLogIn}"
|
|
||||||
Command="{Binding AcceptRequestCommand}"
|
|
||||||
Margin="0,0,0,17"
|
|
||||||
StyleClass="btn-primary"/>
|
|
||||||
<Button
|
|
||||||
Text="{u:I18n DenyLogIn}"
|
|
||||||
Command="{Binding RejectRequestCommand}"
|
|
||||||
StyleClass="btn-secundary"/>
|
|
||||||
|
|
||||||
</StackLayout>
|
|
||||||
</pages:BaseContentPage>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
|
||||||
{
|
|
||||||
public partial class LoginPasswordlessPage : BaseContentPage
|
|
||||||
{
|
|
||||||
private LoginPasswordlessViewModel _vm;
|
|
||||||
|
|
||||||
public LoginPasswordlessPage(LoginPasswordlessDetails loginPasswordlessDetails)
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
_vm = BindingContext as LoginPasswordlessViewModel;
|
|
||||||
_vm.Page = this;
|
|
||||||
|
|
||||||
_vm.LoginRequest = loginPasswordlessDetails;
|
|
||||||
|
|
||||||
ToolbarItems.Add(_closeItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
|
||||||
{
|
|
||||||
if (DoOnce())
|
|
||||||
{
|
|
||||||
await Navigation.PopModalAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAppearing()
|
|
||||||
{
|
|
||||||
base.OnAppearing();
|
|
||||||
_vm.StartRequestTimeUpdater();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDisappearing()
|
|
||||||
{
|
|
||||||
base.OnDisappearing();
|
|
||||||
_vm.StopRequestTimeUpdater();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
|
||||||
{
|
|
||||||
public class LoginPasswordlessViewModel : BaseViewModel
|
|
||||||
{
|
|
||||||
private IDeviceActionService _deviceActionService;
|
|
||||||
private IAuthService _authService;
|
|
||||||
private IPlatformUtilsService _platformUtilsService;
|
|
||||||
private ILogger _logger;
|
|
||||||
private LoginPasswordlessDetails _resquest;
|
|
||||||
private CancellationTokenSource _requestTimeCts;
|
|
||||||
private Task _requestTimeTask;
|
|
||||||
|
|
||||||
private const int REQUEST_TIME_UPDATE_PERIOD_IN_MINUTES = 5;
|
|
||||||
|
|
||||||
public LoginPasswordlessViewModel()
|
|
||||||
{
|
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
|
||||||
_authService = ServiceContainer.Resolve<IAuthService>("authService");
|
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
|
||||||
|
|
||||||
PageTitle = AppResources.LogInRequested;
|
|
||||||
|
|
||||||
AcceptRequestCommand = new AsyncCommand(() => PasswordlessLoginAsync(true),
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
RejectRequestCommand = new AsyncCommand(() => PasswordlessLoginAsync(false),
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand AcceptRequestCommand { get; }
|
|
||||||
|
|
||||||
public ICommand RejectRequestCommand { get; }
|
|
||||||
|
|
||||||
public string LogInAttemptByLabel => LoginRequest != null ? string.Format(AppResources.LogInAttemptByXOnY, LoginRequest.Email, LoginRequest.Origin) : string.Empty;
|
|
||||||
|
|
||||||
public string TimeOfRequestText => CreateRequestDate(LoginRequest?.RequestDate);
|
|
||||||
|
|
||||||
public bool ShowIpAddress => !string.IsNullOrEmpty(LoginRequest?.IpAddress);
|
|
||||||
|
|
||||||
public LoginPasswordlessDetails LoginRequest
|
|
||||||
{
|
|
||||||
get => _resquest;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
SetProperty(ref _resquest, value, additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(LogInAttemptByLabel),
|
|
||||||
nameof(TimeOfRequestText),
|
|
||||||
nameof(ShowIpAddress),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopRequestTimeUpdater()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_requestTimeCts?.Cancel();
|
|
||||||
_requestTimeCts?.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartRequestTimeUpdater()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_requestTimeCts?.Cancel();
|
|
||||||
_requestTimeCts = new CancellationTokenSource();
|
|
||||||
_requestTimeTask = new TimerTask(_logger, UpdateRequestTime, _requestTimeCts).RunPeriodic(TimeSpan.FromMinutes(REQUEST_TIME_UPDATE_PERIOD_IN_MINUTES));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateRequestTime()
|
|
||||||
{
|
|
||||||
TriggerPropertyChanged(nameof(TimeOfRequestText));
|
|
||||||
if (DateTime.UtcNow > LoginRequest?.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes))
|
|
||||||
{
|
|
||||||
StopRequestTimeUpdater();
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
|
||||||
await Page.Navigation.PopModalAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task PasswordlessLoginAsync(bool approveRequest)
|
|
||||||
{
|
|
||||||
if (LoginRequest.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) <= DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
|
||||||
await Page.Navigation.PopModalAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
|
||||||
await _authService.PasswordlessLoginAsync(LoginRequest.Id, LoginRequest.PubKey, approveRequest);
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
await Page.Navigation.PopModalAsync();
|
|
||||||
_platformUtilsService.ShowToast("info", null, approveRequest ? AppResources.LogInAccepted : AppResources.LogInDenied);
|
|
||||||
|
|
||||||
StopRequestTimeUpdater();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string CreateRequestDate(DateTime? requestDate)
|
|
||||||
{
|
|
||||||
if (!requestDate.HasValue)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DateTime.UtcNow < requestDate.Value.ToUniversalTime().AddMinutes(REQUEST_TIME_UPDATE_PERIOD_IN_MINUTES))
|
|
||||||
{
|
|
||||||
return AppResources.JustNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Format(AppResources.XMinutesAgo, DateTime.UtcNow.Minute - requestDate.Value.ToUniversalTime().Minute);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleException(Exception ex)
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
|
||||||
{
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
|
|
||||||
}).FireAndForget();
|
|
||||||
_logger.Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LoginPasswordlessDetails
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
public string Key { get; set; }
|
|
||||||
|
|
||||||
public string PubKey { get; set; }
|
|
||||||
|
|
||||||
public string Origin { get; set; }
|
|
||||||
|
|
||||||
public string Email { get; set; }
|
|
||||||
|
|
||||||
public string FingerprintPhrase { get; set; }
|
|
||||||
|
|
||||||
public DateTime RequestDate { get; set; }
|
|
||||||
|
|
||||||
public string DeviceType { get; set; }
|
|
||||||
|
|
||||||
public string IpAddress { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -129,8 +129,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (useCurrentActiveAccount)
|
if (useCurrentActiveAccount)
|
||||||
{
|
{
|
||||||
return new AvatarImageSource(await _stateService.GetActiveUserIdAsync(),
|
return new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
||||||
await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
|
||||||
}
|
}
|
||||||
return new AvatarImageSource();
|
return new AvatarImageSource();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
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"
|
|
||||||
x:Class="Bit.App.Pages.GeneratorPage"
|
x:Class="Bit.App.Pages.GeneratorPage"
|
||||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
|
||||||
xmlns:enums="clr-namespace:Bit.Core.Enums;assembly=BitwardenCore"
|
|
||||||
x:DataType="pages:GeneratorPageViewModel"
|
x:DataType="pages:GeneratorPageViewModel"
|
||||||
Title="{Binding PageTitle}">
|
Title="{Binding PageTitle}">
|
||||||
<ContentPage.BindingContext>
|
<ContentPage.BindingContext>
|
||||||
@@ -19,9 +16,7 @@
|
|||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||||
<u:LocalizableEnumConverter x:Key="localizableEnum" />
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
|
||||||
<xct:EnumToBoolConverter x:Key="enumToBool"/>
|
|
||||||
<ToolbarItem Text="{u:I18n Cancel}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"
|
|
||||||
x:Name="_closeItem" x:Key="closeItem" />
|
x:Name="_closeItem" x:Key="closeItem" />
|
||||||
<ToolbarItem Text="{u:I18n Select}"
|
<ToolbarItem Text="{u:I18n Select}"
|
||||||
Clicked="Select_Clicked"
|
Clicked="Select_Clicked"
|
||||||
@@ -47,302 +42,60 @@
|
|||||||
in ContentView.-->
|
in ContentView.-->
|
||||||
<ContentView>
|
<ContentView>
|
||||||
<ScrollView Padding="0, 0, 0, 20">
|
<ScrollView Padding="0, 0, 0, 20">
|
||||||
<StackLayout Spacing="0"
|
<StackLayout Spacing="0" Padding="0">
|
||||||
Padding="10,0">
|
<StackLayout StyleClass="box">
|
||||||
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
||||||
Margin="0, 12, 0, 0"
|
Margin="0, 12, 0, 0"
|
||||||
Padding="10,0"
|
RowSpacing="0"
|
||||||
RowSpacing="0"
|
ColumnSpacing="0">
|
||||||
ColumnSpacing="0">
|
<Grid.RowDefinitions>
|
||||||
<Grid.RowDefinitions>
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="*" />
|
</Grid.RowDefinitions>
|
||||||
</Grid.RowDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto" />
|
</Grid.ColumnDefinitions>
|
||||||
</Grid.ColumnDefinitions>
|
<Frame Padding="10"
|
||||||
<Frame Padding="10"
|
Margin="0"
|
||||||
Margin="0"
|
HasShadow="False"
|
||||||
HasShadow="False"
|
BackgroundColor="Transparent"
|
||||||
BackgroundColor="Transparent"
|
BorderColor="Accent">
|
||||||
BorderColor="Accent">
|
<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" />
|
</Frame>
|
||||||
</Frame>
|
</Grid>
|
||||||
</Grid>
|
|
||||||
<Grid IsVisible="{Binding IsUsername, Converter={StaticResource inverseBool}}"
|
|
||||||
StyleClass="box-row"
|
|
||||||
RowDefinitions="Auto"
|
|
||||||
ColumnDefinitions="*,Auto,Auto">
|
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
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" />
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
|
||||||
Command="{Binding CopyCommand}"
|
|
||||||
Grid.Column="1"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n CopyPassword}" />
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
|
||||||
Command="{Binding RegenerateCommand}"
|
|
||||||
Grid.Column="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n GeneratePassword}" />
|
|
||||||
</Grid>
|
|
||||||
<Grid IsVisible="{Binding IsUsername}"
|
|
||||||
StyleClass="box-row"
|
|
||||||
RowDefinitions="Auto"
|
|
||||||
ColumnDefinitions="*,Auto,Auto">
|
|
||||||
<controls:MonoLabel
|
|
||||||
x:Name="lblUsername"
|
|
||||||
StyleClass="text-lg, text-html"
|
|
||||||
Text="{Binding ColoredUsername, Mode=OneWay}"
|
|
||||||
Margin="0, 20"
|
Margin="0, 20"
|
||||||
HorizontalOptions="Start" />
|
HorizontalTextAlignment="Center"
|
||||||
<controls:IconButton
|
HorizontalOptions="CenterAndExpand"
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
LineBreakMode="CharacterWrap" />
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
<Button Text="{u:I18n RegeneratePassword}"
|
||||||
Command="{Binding CopyCommand}"
|
StyleClass="btn-primary"
|
||||||
Grid.Column="1"
|
HorizontalOptions="FillAndExpand"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
Clicked="Regenerate_Clicked"></Button>
|
||||||
AutomationProperties.Name="{u:I18n CopyUsername}" />
|
<Button Text="{u:I18n CopyPassword}"
|
||||||
<controls:IconButton
|
HorizontalOptions="FillAndExpand"
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
Clicked="Copy_Clicked"></Button>
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
|
||||||
Command="{Binding RegenerateUsernameCommand}"
|
|
||||||
Grid.Column="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n GenerateUsername}" />
|
|
||||||
</Grid>
|
|
||||||
<BoxView StyleClass="box-row-separator"/>
|
|
||||||
<StackLayout StyleClass="box"
|
|
||||||
IsVisible="{Binding ShowTypePicker}"
|
|
||||||
Padding="0,10">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n WhatWouldYouLikeToGenerate}"
|
|
||||||
StyleClass="box-label" />
|
|
||||||
<Picker
|
|
||||||
x:Name="_typePicker"
|
|
||||||
ItemsSource="{Binding GeneratorTypeOptions, Mode=OneTime}"
|
|
||||||
SelectedItem="{Binding GeneratorTypeSelected}"
|
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label Text="{u:I18n Options, Header=True}"
|
<StackLayout StyleClass="box">
|
||||||
StyleClass="box-header, box-header-platform"
|
<StackLayout StyleClass="box-row-header">
|
||||||
Margin="0,10,0,0"/>
|
<Label Text="{u:I18n Options, Header=True}"
|
||||||
<!--USERNAME OPTIONS-->
|
StyleClass="box-header, box-header-platform" />
|
||||||
<StackLayout IsVisible="{Binding IsUsername}">
|
|
||||||
<StackLayout Orientation="Horizontal">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n UsernameType}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
VerticalOptions="Center"/>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.QuestionCircle}}"
|
|
||||||
Command="{Binding UsernameTypePromptHelpCommand}"
|
|
||||||
TextColor="{DynamicResource HyperlinkColor}"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n UsernamePromptHelpLink}"
|
|
||||||
VerticalOptions="Center"/>
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Picker
|
|
||||||
x:Name="_usernameTypePicker"
|
|
||||||
ItemsSource="{Binding UsernameTypeOptions, Mode=OneTime}"
|
|
||||||
SelectedItem="{Binding UsernameTypeSelected}"
|
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
<Label
|
|
||||||
StyleClass="box-footer-label"
|
|
||||||
Text="{Binding UsernameTypeDescriptionLabel}" />
|
|
||||||
<!--PLUS ADDRESSED EMAIL OPTIONS-->
|
|
||||||
<StackLayout StyleClass="box-row, box-row-input"
|
|
||||||
IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.PlusAddressedEmail}}">
|
|
||||||
<Label Text="{u:I18n EmailRequiredParenthesis}"
|
|
||||||
StyleClass="box-label" />
|
|
||||||
<Entry x:Name="_plusAddressedEmailEntry"
|
|
||||||
Text="{Binding PlusAddressedEmail}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
Text="{u:I18n EmailType}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Margin="0,10,0,0"/>
|
|
||||||
<Picker IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
x:Name="_plusAddressedEmailTypePicker"
|
|
||||||
ItemsSource="{Binding UsernameEmailTypeOptions, Mode=OneTime}"
|
|
||||||
SelectedItem="{Binding PlusAddressedEmailTypeSelected}"
|
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
Text="{u:I18n Website}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Margin="0,10,0,0" />
|
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
Text="{Binding EmailWebsite}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
<BoxView IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
StyleClass="box-row-separator"
|
|
||||||
Margin="0,10,0,0" />
|
|
||||||
</StackLayout>
|
|
||||||
<!--CATCH-ALL EMAIL OPTIONS-->
|
|
||||||
<StackLayout StyleClass="box-row, box-row-input"
|
|
||||||
IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.CatchAllEmail}}">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n DomainNameRequiredParenthesis}"
|
|
||||||
StyleClass="box-label" />
|
|
||||||
<Entry
|
|
||||||
x:Name="_catchAllEmailDomainNameEntry"
|
|
||||||
Text="{Binding CatchAllEmailDomain}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
Text="{u:I18n EmailType}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Margin="0,10,0,0"/>
|
|
||||||
<Picker IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
x:Name="_catchallEmailTypePicker"
|
|
||||||
ItemsSource="{Binding UsernameEmailTypeOptions, Mode=OneTime}"
|
|
||||||
SelectedItem="{Binding CatchAllEmailTypeSelected}"
|
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
Text="{u:I18n Website}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Margin="0,10,0,0" />
|
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
Text="{Binding EmailWebsite}"
|
|
||||||
StyleClass="box-value"/>
|
|
||||||
<BoxView IsVisible="{Binding ShowUsernameEmailType}"
|
|
||||||
StyleClass="box-row-separator"
|
|
||||||
Margin="0,10,0,0"/>
|
|
||||||
</StackLayout>
|
|
||||||
<!--FORWARDED EMAIL OPTIONS-->
|
|
||||||
<StackLayout StyleClass="box-row, box-row-input"
|
|
||||||
IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.ForwardedEmailAlias}}">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n Service}"
|
|
||||||
StyleClass="box-label" />
|
|
||||||
<Picker
|
|
||||||
x:Name="_serviceTypePicker"
|
|
||||||
ItemsSource="{Binding ForwardedEmailServiceTypeOptions, Mode=OneTime}"
|
|
||||||
SelectedItem="{Binding ForwardedEmailServiceSelected}"
|
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
<!--ANONADDY OPTIONS-->
|
|
||||||
<Grid IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
|
||||||
Grid.RowDefinitions="Auto,*"
|
|
||||||
Grid.ColumnDefinitions="*,Auto">
|
|
||||||
<Label
|
|
||||||
Margin="0,10,0,0"
|
|
||||||
Text="{u:I18n APIAccessToken}"
|
|
||||||
StyleClass="box-label"/>
|
|
||||||
<Entry
|
|
||||||
x:Name="_anonAddyApiAccessTokenEntry"
|
|
||||||
Text="{Binding AnonAddyApiAccessToken}"
|
|
||||||
IsPassword="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool}}"
|
|
||||||
Grid.Row="1"/>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding ShowAnonAddyHiddenValueIcon}"
|
|
||||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="1"/>
|
|
||||||
</Grid>
|
|
||||||
<Label IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
|
||||||
Text="{u:I18n DomainNameRequiredParenthesis}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Margin="0,10,0,0"/>
|
|
||||||
<Entry IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
|
||||||
x:Name="_anonAddyDomainNameEntry"
|
|
||||||
Text="{Binding AnonAddyDomainName}"
|
|
||||||
StyleClass="box-value"/>
|
|
||||||
<!--FIREFOX RELAY OPTIONS-->
|
|
||||||
<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 ShowFirefoxRelayHiddenValueIcon}"
|
|
||||||
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 ShowSimpleLoginHiddenValueIcon}"
|
|
||||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Grid.Column="1"/>
|
|
||||||
</Grid>
|
|
||||||
</StackLayout>
|
|
||||||
<!--RANDOM WORD OPTIONS-->
|
|
||||||
<Grid IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n Capitalize}"
|
|
||||||
StyleClass="box-label-regular"
|
|
||||||
HorizontalOptions="StartAndExpand" />
|
|
||||||
<Switch
|
|
||||||
IsToggled="{Binding CapitalizeRandomWordUsername}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
HorizontalOptions="End" />
|
|
||||||
</Grid>
|
|
||||||
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
|
||||||
StyleClass="box-row-separator" />
|
|
||||||
<Grid IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n IncludeNumber}"
|
|
||||||
StyleClass="box-label-regular"
|
|
||||||
HorizontalOptions="StartAndExpand" />
|
|
||||||
<Switch
|
|
||||||
IsToggled="{Binding IncludeNumberRandomWordUsername}"
|
|
||||||
StyleClass="box-value"
|
|
||||||
HorizontalOptions="End" />
|
|
||||||
</Grid>
|
|
||||||
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
|
||||||
StyleClass="box-row-separator" />
|
|
||||||
</StackLayout>
|
|
||||||
<!--PASSWORD OPTIONS-->
|
|
||||||
<StackLayout IsVisible="{Binding IsUsername, Converter={StaticResource inverseBool}}">
|
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordType}"
|
Text="{u:I18n Type}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label" />
|
||||||
<Picker
|
<Picker
|
||||||
x:Name="_passwordTypePicker"
|
x:Name="_typePicker"
|
||||||
ItemsSource="{Binding PasswordTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding PasswordTypeSelectedIndex}"
|
SelectedIndex="{Binding TypeSelectedIndex}"
|
||||||
StyleClass="box-value" />
|
StyleClass="box-value" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Spacing="0"
|
<StackLayout Spacing="0"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Models;
|
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Styles;
|
using Bit.App.Styles;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
@@ -19,20 +18,15 @@ namespace Bit.App.Pages
|
|||||||
private readonly Action<string> _selectAction;
|
private readonly Action<string> _selectAction;
|
||||||
private readonly TabsPage _tabsPage;
|
private readonly TabsPage _tabsPage;
|
||||||
|
|
||||||
public GeneratorPage(bool fromTabPage, Action<string> selectAction = null, TabsPage tabsPage = null, bool isUsernameGenerator = false, string emailWebsite = null, bool editMode = false, AppOptions appOptions = null)
|
public GeneratorPage(bool fromTabPage, Action<string> selectAction = null, TabsPage tabsPage = null)
|
||||||
{
|
{
|
||||||
_tabsPage = tabsPage;
|
_tabsPage = tabsPage;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_vm = BindingContext as GeneratorPageViewModel;
|
_vm = BindingContext as GeneratorPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_fromTabPage = fromTabPage;
|
_fromTabPage = fromTabPage;
|
||||||
_selectAction = selectAction;
|
_selectAction = selectAction;
|
||||||
_vm.ShowTypePicker = fromTabPage;
|
|
||||||
_vm.IsUsername = isUsernameGenerator;
|
|
||||||
_vm.EmailWebsite = emailWebsite;
|
|
||||||
_vm.EditMode = editMode;
|
|
||||||
_vm.IosExtension = appOptions?.IosExtension ?? false;
|
|
||||||
var isIos = Device.RuntimePlatform == Device.iOS;
|
var isIos = Device.RuntimePlatform == Device.iOS;
|
||||||
if (selectAction != null)
|
if (selectAction != null)
|
||||||
{
|
{
|
||||||
@@ -53,12 +47,10 @@ namespace Bit.App.Pages
|
|||||||
ToolbarItems.Add(_historyItem);
|
ToolbarItems.Add(_historyItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_typePicker.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
if (isIos)
|
||||||
_passwordTypePicker.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
{
|
||||||
_usernameTypePicker.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
_typePicker.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||||
_serviceTypePicker.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
}
|
||||||
_plusAddressedEmailTypePicker.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
|
||||||
_catchallEmailTypePicker.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
@@ -105,6 +97,16 @@ namespace Bit.App.Pages
|
|||||||
return base.OnBackButtonPressed();
|
return base.OnBackButtonPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void Regenerate_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
await _vm.RegenerateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Copy_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
await _vm.CopyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async void More_Clicked(object sender, EventArgs e)
|
private async void More_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!DoOnce())
|
if (!DoOnce())
|
||||||
@@ -122,7 +124,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private void Select_Clicked(object sender, EventArgs e)
|
private void Select_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_selectAction?.Invoke(_vm.IsUsername ? _vm.Username : _vm.Password);
|
_selectAction?.Invoke(_vm.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void History_Clicked(object sender, EventArgs e)
|
private async void History_Clicked(object sender, EventArgs e)
|
||||||
@@ -136,24 +138,19 @@ namespace Bit.App.Pages
|
|||||||
await _vm.SliderChangedAsync();
|
await _vm.SliderChangedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void Close_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task UpdateOnThemeChanged()
|
public override async Task UpdateOnThemeChanged()
|
||||||
{
|
{
|
||||||
await base.UpdateOnThemeChanged();
|
await base.UpdateOnThemeChanged();
|
||||||
|
|
||||||
await Device.InvokeOnMainThreadAsync(() =>
|
await Device.InvokeOnMainThreadAsync(() => _vm?.RedrawPassword());
|
||||||
{
|
|
||||||
if (_vm != null)
|
|
||||||
{
|
|
||||||
if (_vm.IsUsername)
|
|
||||||
{
|
|
||||||
_vm.RedrawUsername();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_vm.RedrawPassword();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
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.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@@ -20,17 +13,11 @@ namespace Bit.App.Pages
|
|||||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly IUsernameGenerationService _usernameGenerationService;
|
|
||||||
private readonly ITokenService _tokenService;
|
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
|
||||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
|
||||||
|
|
||||||
private PasswordGenerationOptions _options;
|
private PasswordGenerationOptions _options;
|
||||||
private UsernameGenerationOptions _usernameOptions;
|
|
||||||
private PasswordGeneratorPolicyOptions _enforcedPolicyOptions;
|
private PasswordGeneratorPolicyOptions _enforcedPolicyOptions;
|
||||||
private string _password;
|
private string _password;
|
||||||
private bool _isPassword;
|
private bool _isPassword;
|
||||||
private bool _isUsername;
|
|
||||||
private bool _uppercase;
|
private bool _uppercase;
|
||||||
private bool _lowercase;
|
private bool _lowercase;
|
||||||
private bool _number;
|
private bool _number;
|
||||||
@@ -43,72 +30,21 @@ namespace Bit.App.Pages
|
|||||||
private string _wordSeparator;
|
private string _wordSeparator;
|
||||||
private bool _capitalize;
|
private bool _capitalize;
|
||||||
private bool _includeNumber;
|
private bool _includeNumber;
|
||||||
private string _username;
|
private int _typeSelectedIndex;
|
||||||
private GeneratorType _generatorTypeSelected;
|
|
||||||
private int _passwordTypeSelectedIndex;
|
|
||||||
private bool _doneIniting;
|
private bool _doneIniting;
|
||||||
private bool _showTypePicker;
|
|
||||||
private string _emailWebsite;
|
|
||||||
private bool _showFirefoxRelayApiAccessToken;
|
|
||||||
private bool _showAnonAddyApiAccessToken;
|
|
||||||
private bool _showSimpleLoginApiKey;
|
|
||||||
private bool _editMode;
|
|
||||||
|
|
||||||
public GeneratorPageViewModel()
|
public GeneratorPageViewModel()
|
||||||
{
|
{
|
||||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
|
"passwordGenerationService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>();
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_usernameGenerationService = ServiceContainer.Resolve<IUsernameGenerationService>();
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
_tokenService = ServiceContainer.Resolve<ITokenService>();
|
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
|
|
||||||
|
|
||||||
PageTitle = AppResources.Generator;
|
PageTitle = AppResources.PasswordGenerator;
|
||||||
GeneratorTypeOptions = new List<GeneratorType> {
|
TypeOptions = new List<string> { AppResources.Password, AppResources.Passphrase };
|
||||||
GeneratorType.Password,
|
|
||||||
GeneratorType.Username
|
|
||||||
};
|
|
||||||
PasswordTypeOptions = new List<string> { AppResources.Password, AppResources.Passphrase };
|
|
||||||
|
|
||||||
UsernameTypeOptions = new List<UsernameType> {
|
|
||||||
UsernameType.PlusAddressedEmail,
|
|
||||||
UsernameType.CatchAllEmail,
|
|
||||||
UsernameType.ForwardedEmailAlias,
|
|
||||||
UsernameType.RandomWord
|
|
||||||
};
|
|
||||||
|
|
||||||
ForwardedEmailServiceTypeOptions = new List<ForwardedEmailServiceType> {
|
|
||||||
ForwardedEmailServiceType.AnonAddy,
|
|
||||||
ForwardedEmailServiceType.FirefoxRelay,
|
|
||||||
ForwardedEmailServiceType.SimpleLogin
|
|
||||||
};
|
|
||||||
|
|
||||||
UsernameEmailTypeOptions = new List<UsernameEmailType>
|
|
||||||
{
|
|
||||||
UsernameEmailType.Random,
|
|
||||||
UsernameEmailType.Website
|
|
||||||
};
|
|
||||||
|
|
||||||
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
|
|
||||||
RegenerateCommand = new AsyncCommand(RegenerateAsync, 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);
|
|
||||||
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
|
||||||
CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<GeneratorType> GeneratorTypeOptions { get; set; }
|
public List<string> TypeOptions { get; set; }
|
||||||
public List<string> PasswordTypeOptions { get; set; }
|
|
||||||
public List<UsernameType> UsernameTypeOptions { get; set; }
|
|
||||||
public List<ForwardedEmailServiceType> ForwardedEmailServiceTypeOptions { get; set; }
|
|
||||||
public List<UsernameEmailType> UsernameEmailTypeOptions { get; set; }
|
|
||||||
|
|
||||||
public Command UsernameTypePromptHelpCommand { get; set; }
|
|
||||||
public ICommand RegenerateCommand { get; set; }
|
|
||||||
public ICommand RegenerateUsernameCommand { get; set; }
|
|
||||||
public ICommand ToggleForwardedEmailHiddenValueCommand { get; set; }
|
|
||||||
public ICommand CopyCommand { get; set; }
|
|
||||||
public ICommand CloseCommand { get; set; }
|
|
||||||
|
|
||||||
public string Password
|
public string Password
|
||||||
{
|
{
|
||||||
@@ -120,18 +56,7 @@ namespace Bit.App.Pages
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Username
|
public string ColoredPassword => PasswordFormatter.FormatPassword(Password);
|
||||||
{
|
|
||||||
get => _username;
|
|
||||||
set => SetProperty(ref _username, value,
|
|
||||||
additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(ColoredUsername)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ColoredPassword => GeneratedValueFormatter.Format(Password);
|
|
||||||
public string ColoredUsername => GeneratedValueFormatter.Format(Username);
|
|
||||||
|
|
||||||
public bool IsPassword
|
public bool IsPassword
|
||||||
{
|
{
|
||||||
@@ -139,34 +64,6 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _isPassword, value);
|
set => SetProperty(ref _isPassword, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsUsername
|
|
||||||
{
|
|
||||||
get => _isUsername;
|
|
||||||
set => SetProperty(ref _isUsername, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IosExtension { get; set; }
|
|
||||||
|
|
||||||
public bool ShowTypePicker
|
|
||||||
{
|
|
||||||
get => _showTypePicker;
|
|
||||||
set => SetProperty(ref _showTypePicker, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EditMode
|
|
||||||
{
|
|
||||||
get => _editMode;
|
|
||||||
set => SetProperty(ref _editMode, value, additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(ShowUsernameEmailType)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowUsernameEmailType
|
|
||||||
{
|
|
||||||
get => !string.IsNullOrWhiteSpace(EmailWebsite);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Length
|
public int Length
|
||||||
{
|
{
|
||||||
get => _length;
|
get => _length;
|
||||||
@@ -338,20 +235,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PlusAddressedEmail
|
|
||||||
{
|
|
||||||
get => _usernameOptions.PlusAddressedEmail;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.PlusAddressedEmail != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.PlusAddressedEmail = value;
|
|
||||||
TriggerPropertyChanged(nameof(PlusAddressedEmail));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PasswordGeneratorPolicyOptions EnforcedPolicyOptions
|
public PasswordGeneratorPolicyOptions EnforcedPolicyOptions
|
||||||
{
|
{
|
||||||
get => _enforcedPolicyOptions;
|
get => _enforcedPolicyOptions;
|
||||||
@@ -364,277 +247,24 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public bool IsPolicyInEffect => _enforcedPolicyOptions.InEffect();
|
public bool IsPolicyInEffect => _enforcedPolicyOptions.InEffect();
|
||||||
|
|
||||||
public GeneratorType GeneratorTypeSelected
|
public int TypeSelectedIndex
|
||||||
{
|
{
|
||||||
get => _generatorTypeSelected;
|
get => _typeSelectedIndex;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _generatorTypeSelected, value))
|
if (SetProperty(ref _typeSelectedIndex, value))
|
||||||
{
|
|
||||||
IsUsername = value == GeneratorType.Username;
|
|
||||||
TriggerPropertyChanged(nameof(GeneratorTypeSelected));
|
|
||||||
SaveOptionsAsync().FireAndForget();
|
|
||||||
SaveUsernameOptionsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int PasswordTypeSelectedIndex
|
|
||||||
{
|
|
||||||
get => _passwordTypeSelectedIndex;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref _passwordTypeSelectedIndex, value))
|
|
||||||
{
|
{
|
||||||
IsPassword = value == 0;
|
IsPassword = value == 0;
|
||||||
TriggerPropertyChanged(nameof(PasswordTypeSelectedIndex));
|
var task = SaveOptionsAsync();
|
||||||
SaveOptionsAsync().FireAndForget();
|
|
||||||
SaveUsernameOptionsAsync().FireAndForget();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UsernameType UsernameTypeSelected
|
|
||||||
{
|
|
||||||
get => _usernameOptions.Type;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.Type != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.Type = value;
|
|
||||||
Username = Constants.DefaultUsernameGenerated;
|
|
||||||
TriggerPropertyChanged(nameof(UsernameTypeSelected), new string[] { nameof(UsernameTypeDescriptionLabel) });
|
|
||||||
SaveUsernameOptionsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string UsernameTypeDescriptionLabel => GetUsernameTypeLabelDescription(UsernameTypeSelected);
|
|
||||||
|
|
||||||
|
|
||||||
public ForwardedEmailServiceType ForwardedEmailServiceSelected
|
|
||||||
{
|
|
||||||
get => _usernameOptions.ServiceType;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.ServiceType != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.ServiceType = value;
|
|
||||||
Username = Constants.DefaultUsernameGenerated;
|
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CatchAllEmailDomain
|
|
||||||
{
|
|
||||||
get => _usernameOptions.CatchAllEmailDomain;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.CatchAllEmailDomain != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.CatchAllEmailDomain = value;
|
|
||||||
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string AnonAddyApiAccessToken
|
|
||||||
{
|
|
||||||
get => _usernameOptions.AnonAddyApiAccessToken;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.AnonAddyApiAccessToken != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.AnonAddyApiAccessToken = value;
|
|
||||||
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowAnonAddyApiAccessToken
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _showAnonAddyApiAccessToken;
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _showAnonAddyApiAccessToken, value,
|
|
||||||
additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(ShowAnonAddyHiddenValueIcon)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ShowAnonAddyHiddenValueIcon => _showAnonAddyApiAccessToken ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
|
||||||
|
|
||||||
public string AnonAddyDomainName
|
|
||||||
{
|
|
||||||
get => _usernameOptions.AnonAddyDomainName;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.AnonAddyDomainName != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.AnonAddyDomainName = value;
|
|
||||||
TriggerPropertyChanged(nameof(AnonAddyDomainName));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(ShowFirefoxRelayHiddenValueIcon)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ShowFirefoxRelayHiddenValueIcon => _showFirefoxRelayApiAccessToken ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
|
||||||
|
|
||||||
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,
|
|
||||||
additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(ShowSimpleLoginHiddenValueIcon)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ShowSimpleLoginHiddenValueIcon => _showSimpleLoginApiKey ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
|
||||||
|
|
||||||
public bool CapitalizeRandomWordUsername
|
|
||||||
{
|
|
||||||
get => _usernameOptions.CapitalizeRandomWordUsername;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.CapitalizeRandomWordUsername != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.CapitalizeRandomWordUsername = value;
|
|
||||||
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
|
|
||||||
SaveUsernameOptionsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IncludeNumberRandomWordUsername
|
|
||||||
{
|
|
||||||
get => _usernameOptions.IncludeNumberRandomWordUsername;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.IncludeNumberRandomWordUsername != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.IncludeNumberRandomWordUsername = value;
|
|
||||||
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
|
|
||||||
SaveUsernameOptionsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UsernameEmailType PlusAddressedEmailTypeSelected
|
|
||||||
{
|
|
||||||
get => _usernameOptions.PlusAddressedEmailType;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.PlusAddressedEmailType != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.PlusAddressedEmailType = value;
|
|
||||||
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
|
|
||||||
SaveUsernameOptionsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UsernameEmailType CatchAllEmailTypeSelected
|
|
||||||
{
|
|
||||||
get => _usernameOptions.CatchAllEmailType;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_usernameOptions.CatchAllEmailType != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.CatchAllEmailType = value;
|
|
||||||
TriggerPropertyChanged(nameof(CatchAllEmailTypeSelected));
|
|
||||||
SaveUsernameOptionsAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string EmailWebsite
|
|
||||||
{
|
|
||||||
get => _emailWebsite;
|
|
||||||
set => SetProperty(ref _emailWebsite, value, additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(ShowUsernameEmailType)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
(_options, EnforcedPolicyOptions) = await _passwordGenerationService.GetOptionsAsync();
|
(_options, EnforcedPolicyOptions) = await _passwordGenerationService.GetOptionsAsync();
|
||||||
LoadFromOptions();
|
LoadFromOptions();
|
||||||
|
await RegenerateAsync();
|
||||||
_usernameOptions = await _usernameGenerationService.GetOptionsAsync();
|
|
||||||
await _tokenService.PrepareTokenForDecodingAsync();
|
|
||||||
_usernameOptions.PlusAddressedEmail = _tokenService.GetEmail();
|
|
||||||
_usernameOptions.EmailWebsite = EmailWebsite;
|
|
||||||
_usernameOptions.CatchAllEmailType = _usernameOptions.PlusAddressedEmailType = string.IsNullOrWhiteSpace(EmailWebsite) || !EditMode ? UsernameEmailType.Random : UsernameEmailType.Website;
|
|
||||||
|
|
||||||
if (!IsUsername)
|
|
||||||
{
|
|
||||||
await RegenerateAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (UsernameTypeSelected != UsernameType.ForwardedEmailAlias)
|
|
||||||
{
|
|
||||||
await RegenerateUsernameAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Username = Constants.DefaultUsernameGenerated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TriggerUsernamePropertiesChanged();
|
|
||||||
|
|
||||||
_doneIniting = true;
|
_doneIniting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,11 +274,6 @@ namespace Bit.App.Pages
|
|||||||
await _passwordGenerationService.AddHistoryAsync(Password);
|
await _passwordGenerationService.AddHistoryAsync(Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RegenerateUsernameAsync()
|
|
||||||
{
|
|
||||||
Username = await _usernameGenerationService.GenerateAsync(_usernameOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RedrawPassword()
|
public void RedrawPassword()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_password))
|
if (!string.IsNullOrEmpty(_password))
|
||||||
@@ -657,14 +282,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RedrawUsername()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(_username))
|
|
||||||
{
|
|
||||||
TriggerPropertyChanged(nameof(ColoredUsername));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SaveOptionsAsync(bool regenerate = true)
|
public async Task SaveOptionsAsync(bool regenerate = true)
|
||||||
{
|
{
|
||||||
if (!_doneIniting)
|
if (!_doneIniting)
|
||||||
@@ -674,7 +291,6 @@ namespace Bit.App.Pages
|
|||||||
SetOptions();
|
SetOptions();
|
||||||
_passwordGenerationService.NormalizeOptions(_options, _enforcedPolicyOptions);
|
_passwordGenerationService.NormalizeOptions(_options, _enforcedPolicyOptions);
|
||||||
await _passwordGenerationService.SaveOptionsAsync(_options);
|
await _passwordGenerationService.SaveOptionsAsync(_options);
|
||||||
|
|
||||||
LoadFromOptions();
|
LoadFromOptions();
|
||||||
if (regenerate)
|
if (regenerate)
|
||||||
{
|
{
|
||||||
@@ -682,26 +298,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveUsernameOptionsAsync(bool regenerate = true)
|
|
||||||
{
|
|
||||||
if (!_doneIniting)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_usernameOptions.EmailWebsite = EmailWebsite;
|
|
||||||
await _usernameGenerationService.SaveOptionsAsync(_usernameOptions);
|
|
||||||
|
|
||||||
if (regenerate && UsernameTypeSelected != UsernameType.ForwardedEmailAlias)
|
|
||||||
{
|
|
||||||
await RegenerateUsernameAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Username = Constants.DefaultUsernameGenerated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SliderChangedAsync()
|
public async Task SliderChangedAsync()
|
||||||
{
|
{
|
||||||
await SaveOptionsAsync(false);
|
await SaveOptionsAsync(false);
|
||||||
@@ -721,40 +317,15 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task CopyAsync()
|
public async Task CopyAsync()
|
||||||
{
|
{
|
||||||
await _clipboardService.CopyTextAsync(IsUsername ? Username : Password);
|
await _clipboardService.CopyTextAsync(Password);
|
||||||
_platformUtilsService.ShowToastForCopiedValue(IsUsername ? AppResources.Username : AppResources.Password);
|
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
}
|
|
||||||
|
|
||||||
public void UsernameTypePromptHelp()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_platformUtilsService.LaunchUri("https://bitwarden.com/help/generator/#username-types");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Value.Exception(ex);
|
|
||||||
Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CloseAsync()
|
|
||||||
{
|
|
||||||
if (IosExtension)
|
|
||||||
{
|
|
||||||
_deviceActionService.CloseExtensionPopUp();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Page.Navigation.PopModalAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadFromOptions()
|
private void LoadFromOptions()
|
||||||
{
|
{
|
||||||
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault();
|
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault();
|
||||||
PasswordTypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
|
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
|
||||||
IsPassword = PasswordTypeSelectedIndex == 0;
|
IsPassword = TypeSelectedIndex == 0;
|
||||||
MinNumber = _options.MinNumber.GetValueOrDefault();
|
MinNumber = _options.MinNumber.GetValueOrDefault();
|
||||||
MinSpecial = _options.MinSpecial.GetValueOrDefault();
|
MinSpecial = _options.MinSpecial.GetValueOrDefault();
|
||||||
Special = _options.Special.GetValueOrDefault();
|
Special = _options.Special.GetValueOrDefault();
|
||||||
@@ -768,31 +339,10 @@ namespace Bit.App.Pages
|
|||||||
IncludeNumber = _options.IncludeNumber.GetValueOrDefault();
|
IncludeNumber = _options.IncludeNumber.GetValueOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TriggerUsernamePropertiesChanged()
|
|
||||||
{
|
|
||||||
TriggerPropertyChanged(nameof(CatchAllEmailTypeSelected));
|
|
||||||
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
|
|
||||||
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
|
|
||||||
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
|
|
||||||
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
|
|
||||||
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
|
|
||||||
TriggerPropertyChanged(nameof(AnonAddyDomainName));
|
|
||||||
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
|
||||||
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
|
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
|
||||||
TriggerPropertyChanged(nameof(UsernameTypeSelected));
|
|
||||||
TriggerPropertyChanged(nameof(PasswordTypeSelectedIndex));
|
|
||||||
TriggerPropertyChanged(nameof(GeneratorTypeSelected));
|
|
||||||
TriggerPropertyChanged(nameof(PlusAddressedEmail));
|
|
||||||
TriggerPropertyChanged(nameof(GeneratorTypeSelected));
|
|
||||||
TriggerPropertyChanged(nameof(UsernameTypeDescriptionLabel));
|
|
||||||
TriggerPropertyChanged(nameof(EmailWebsite));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetOptions()
|
private void SetOptions()
|
||||||
{
|
{
|
||||||
_options.AllowAmbiguousChar = AllowAmbiguousChars;
|
_options.AllowAmbiguousChar = AllowAmbiguousChars;
|
||||||
_options.Type = PasswordTypeSelectedIndex == 1 ? "passphrase" : "password";
|
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
|
||||||
_options.MinNumber = MinNumber;
|
_options.MinNumber = MinNumber;
|
||||||
_options.MinSpecial = MinSpecial;
|
_options.MinSpecial = MinSpecial;
|
||||||
_options.Special = Special;
|
_options.Special = Special;
|
||||||
@@ -805,51 +355,5 @@ namespace Bit.App.Pages
|
|||||||
_options.Capitalize = Capitalize;
|
_options.Capitalize = Capitalize;
|
||||||
_options.IncludeNumber = IncludeNumber;
|
_options.IncludeNumber = IncludeNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnSubmitException(Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Value.Exception(ex);
|
|
||||||
|
|
||||||
if (IsUsername && UsernameTypeSelected == UsernameType.ForwardedEmailAlias)
|
|
||||||
{
|
|
||||||
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(
|
|
||||||
AppResources.AnErrorHasOccurred, string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected), AppResources.Ok));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetUsernameTypeLabelDescription(UsernameType value)
|
|
||||||
{
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case UsernameType.PlusAddressedEmail:
|
|
||||||
return AppResources.PlusAddressedEmailDescription;
|
|
||||||
case UsernameType.CatchAllEmail:
|
|
||||||
return AppResources.CatchAllEmailDescription;
|
|
||||||
case UsernameType.ForwardedEmailAlias:
|
|
||||||
return AppResources.ForwardedEmailDescription;
|
|
||||||
default:
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace Bit.App.Pages
|
|||||||
public class SendAddEditPageViewModel : BaseViewModel
|
public class SendAddEditPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IFileService _fileService;
|
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
@@ -52,7 +51,6 @@ namespace Bit.App.Pages
|
|||||||
public SendAddEditPageViewModel()
|
public SendAddEditPageViewModel()
|
||||||
{
|
{
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_fileService = ServiceContainer.Resolve<IFileService>();
|
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
@@ -294,7 +292,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task ChooseFileAsync()
|
public async Task ChooseFileAsync()
|
||||||
{
|
{
|
||||||
await _fileService.SelectFileAsync();
|
await _deviceActionService.SelectFileAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearExpirationDate()
|
public void ClearExpirationDate()
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await LoadDataAsync();
|
await LoadDataAsync();
|
||||||
|
|
||||||
var uppercaseGroupNames = Device.RuntimePlatform == Device.iOS;
|
var uppercaseGroupNames = _deviceActionService.DeviceType == DeviceType.iOS;
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
groupedSends.Add(new SendGroupingsPageListGroup(
|
groupedSends.Add(new SendGroupingsPageListGroup(
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
<Button
|
<Button
|
||||||
Command="{Binding ToggleAccessibilityCommand}"
|
Clicked="ToggleAccessibility"
|
||||||
StyleClass="box-overlay"
|
StyleClass="box-overlay"
|
||||||
RelativeLayout.XConstraint="0"
|
RelativeLayout.XConstraint="0"
|
||||||
RelativeLayout.YConstraint="0"
|
RelativeLayout.YConstraint="0"
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ namespace Bit.App.Pages
|
|||||||
_vm.ToggleInlineAutofill();
|
_vm.ToggleInlineAutofill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleAccessibility(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
_vm.ToggleAccessibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ToggleDrawOver(object sender, EventArgs e)
|
private void ToggleDrawOver(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Services;
|
using Bit.App.Services;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class AutofillServicesPageViewModel : BaseViewModel
|
public class AutofillServicesPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly MobileI18nService _i18nService;
|
private readonly MobileI18nService _i18nService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
|
||||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
|
||||||
|
|
||||||
private bool _autofillServiceToggled;
|
private bool _autofillServiceToggled;
|
||||||
private bool _inlineAutofillToggled;
|
private bool _inlineAutofillToggled;
|
||||||
@@ -27,14 +22,9 @@ namespace Bit.App.Pages
|
|||||||
public AutofillServicesPageViewModel()
|
public AutofillServicesPageViewModel()
|
||||||
{
|
{
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
|
||||||
PageTitle = AppResources.AutofillServices;
|
PageTitle = AppResources.AutofillServices;
|
||||||
ToggleAccessibilityCommand = new AsyncCommand(ToggleAccessibilityAsync,
|
|
||||||
onException: ex => _logger.Value.Exception(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Autofill Service
|
#region Autofill Service
|
||||||
@@ -84,8 +74,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
#region Accessibility
|
#region Accessibility
|
||||||
|
|
||||||
public ICommand ToggleAccessibilityCommand { get; }
|
|
||||||
|
|
||||||
public string AccessibilityDescriptionLabel
|
public string AccessibilityDescriptionLabel
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -175,7 +163,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_autofillHandler.DisableAutofillService();
|
_deviceActionService.DisableAutofillService();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,18 +176,8 @@ namespace Bit.App.Pages
|
|||||||
InlineAutofillToggled = !InlineAutofillToggled;
|
InlineAutofillToggled = !InlineAutofillToggled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ToggleAccessibilityAsync()
|
public void ToggleAccessibility()
|
||||||
{
|
{
|
||||||
if (!_autofillHandler.AutofillAccessibilityServiceRunning())
|
|
||||||
{
|
|
||||||
var accept = await _platformUtilsService.ShowDialogAsync(AppResources.AccessibilityDisclosureText,
|
|
||||||
AppResources.AccessibilityServiceDisclosure, AppResources.Accept,
|
|
||||||
AppResources.Decline);
|
|
||||||
if (!accept)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_deviceActionService.OpenAccessibilitySettings();
|
_deviceActionService.OpenAccessibilitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,9 +193,9 @@ namespace Bit.App.Pages
|
|||||||
public void UpdateEnabled()
|
public void UpdateEnabled()
|
||||||
{
|
{
|
||||||
AutofillServiceToggled =
|
AutofillServiceToggled =
|
||||||
_autofillHandler.SupportsAutofillService() && _autofillHandler.AutofillServiceEnabled();
|
_deviceActionService.HasAutofillService() && _deviceActionService.AutofillServiceEnabled();
|
||||||
AccessibilityToggled = _autofillHandler.AutofillAccessibilityServiceRunning();
|
AccessibilityToggled = _deviceActionService.AutofillAccessibilityServiceRunning();
|
||||||
DrawOverToggled = _autofillHandler.AutofillAccessibilityOverlayPermitted();
|
DrawOverToggled = _deviceActionService.AutofillAccessibilityOverlayPermitted();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateInlineAutofillToggledAsync()
|
private async Task UpdateInlineAutofillToggledAsync()
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ namespace Bit.App.Pages
|
|||||||
public class ExportVaultPageViewModel : BaseViewModel
|
public class ExportVaultPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IFileService _fileService;
|
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly IExportService _exportService;
|
private readonly IExportService _exportService;
|
||||||
@@ -40,7 +39,6 @@ namespace Bit.App.Pages
|
|||||||
public ExportVaultPageViewModel()
|
public ExportVaultPageViewModel()
|
||||||
{
|
{
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_fileService = ServiceContainer.Resolve<IFileService>();
|
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
|
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
|
||||||
@@ -184,7 +182,7 @@ namespace Bit.App.Pages
|
|||||||
_defaultFilename = _exportService.GetFileName(null, fileFormat);
|
_defaultFilename = _exportService.GetFileName(null, fileFormat);
|
||||||
_exportResult = Encoding.UTF8.GetBytes(data);
|
_exportResult = Encoding.UTF8.GetBytes(data);
|
||||||
|
|
||||||
if (!_fileService.SaveFile(_exportResult, null, _defaultFilename, null))
|
if (!_deviceActionService.SaveFile(_exportResult, null, _defaultFilename, null))
|
||||||
{
|
{
|
||||||
ClearResult();
|
ClearResult();
|
||||||
await _platformUtilsService.ShowDialogAsync(_i18nService.T("ExportVaultFailure"));
|
await _platformUtilsService.ShowDialogAsync(_i18nService.T("ExportVaultFailure"));
|
||||||
@@ -222,7 +220,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async void SaveFileSelected(string contentUri, string filename)
|
public async void SaveFileSelected(string contentUri, string filename)
|
||||||
{
|
{
|
||||||
if (_fileService.SaveFile(_exportResult, null, filename ?? _defaultFilename, contentUri))
|
if (_deviceActionService.SaveFile(_exportResult, null, filename ?? _defaultFilename, contentUri))
|
||||||
{
|
{
|
||||||
ClearResult();
|
ClearResult();
|
||||||
_platformUtilsService.ShowToast("success", null, _i18nService.T("ExportVaultSuccess"));
|
_platformUtilsService.ShowToast("success", null, _i18nService.T("ExportVaultSuccess"));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
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;
|
||||||
@@ -10,12 +9,12 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public partial class OptionsPage : BaseContentPage
|
public partial class OptionsPage : BaseContentPage
|
||||||
{
|
{
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly OptionsPageViewModel _vm;
|
private readonly OptionsPageViewModel _vm;
|
||||||
|
|
||||||
public OptionsPage()
|
public OptionsPage()
|
||||||
{
|
{
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_vm = BindingContext as OptionsPageViewModel;
|
_vm = BindingContext as OptionsPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
@@ -26,7 +25,7 @@ namespace Bit.App.Pages
|
|||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
ToolbarItems.RemoveAt(0);
|
ToolbarItems.RemoveAt(0);
|
||||||
_vm.ShowAndroidAutofillSettings = _autofillHandler.SupportsAutofillService();
|
_vm.ShowAndroidAutofillSettings = _deviceActionService.SupportsAutofillService();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,14 +56,12 @@ namespace Bit.App.Pages
|
|||||||
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
||||||
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
||||||
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
||||||
new KeyValuePair<string, string>(ThemeManager.SolarizedDark, AppResources.SolarizedDark),
|
|
||||||
};
|
};
|
||||||
AutoDarkThemeOptions = new List<KeyValuePair<string, string>>
|
AutoDarkThemeOptions = new List<KeyValuePair<string, string>>
|
||||||
{
|
{
|
||||||
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
||||||
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
||||||
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
||||||
new KeyValuePair<string, string>(ThemeManager.SolarizedDark, AppResources.SolarizedDark),
|
|
||||||
};
|
};
|
||||||
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
|
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ namespace Bit.App.Pages
|
|||||||
private readonly ICryptoService _cryptoService;
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
|
||||||
private readonly IEnvironmentService _environmentService;
|
private readonly IEnvironmentService _environmentService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
@@ -31,7 +30,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly ILogger _loggerService;
|
private readonly ILogger _loggerService;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
|
||||||
private const int CustomVaultTimeoutValue = -100;
|
private const int CustomVaultTimeoutValue = -100;
|
||||||
|
|
||||||
private bool _supportsBiometric;
|
private bool _supportsBiometric;
|
||||||
@@ -43,7 +42,6 @@ namespace Bit.App.Pages
|
|||||||
private string _vaultTimeoutActionDisplayValue;
|
private string _vaultTimeoutActionDisplayValue;
|
||||||
private bool _showChangeMasterPassword;
|
private bool _showChangeMasterPassword;
|
||||||
private bool _reportLoggingEnabled;
|
private bool _reportLoggingEnabled;
|
||||||
private bool _approvePasswordlessLoginRequests;
|
|
||||||
|
|
||||||
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
||||||
new List<KeyValuePair<string, int?>>
|
new List<KeyValuePair<string, int?>>
|
||||||
@@ -75,7 +73,6 @@ namespace Bit.App.Pages
|
|||||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
|
||||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
@@ -86,7 +83,6 @@ namespace Bit.App.Pages
|
|||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
|
||||||
|
|
||||||
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
@@ -137,7 +133,6 @@ namespace Bit.App.Pages
|
|||||||
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
||||||
!await _keyConnectorService.GetUsesKeyConnector();
|
!await _keyConnectorService.GetUsesKeyConnector();
|
||||||
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||||
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
|
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,38 +326,6 @@ namespace Bit.App.Pages
|
|||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ApproveLoginRequestsAsync()
|
|
||||||
{
|
|
||||||
var options = new[]
|
|
||||||
{
|
|
||||||
CreateSelectableOption(AppResources.Yes, _approvePasswordlessLoginRequests),
|
|
||||||
CreateSelectableOption(AppResources.No, !_approvePasswordlessLoginRequests),
|
|
||||||
};
|
|
||||||
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.UseThisDeviceToApproveLoginRequestsMadeFromOtherDevices, AppResources.Cancel, null, options);
|
|
||||||
|
|
||||||
if (selection == null || selection == AppResources.Cancel)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_approvePasswordlessLoginRequests = CompareSelection(selection, AppResources.Yes);
|
|
||||||
await _stateService.SetApprovePasswordlessLoginsAsync(_approvePasswordlessLoginRequests);
|
|
||||||
|
|
||||||
BuildList();
|
|
||||||
|
|
||||||
if (!_approvePasswordlessLoginRequests || await _pushNotificationService.AreNotificationsSettingsEnabledAsync())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var openAppSettingsResult = await _platformUtilsService.ShowDialogAsync(AppResources.ReceivePushNotificationsForNewLoginRequests, title: string.Empty, confirmText: AppResources.Settings, cancelText: AppResources.NoThanks);
|
|
||||||
if (openAppSettingsResult)
|
|
||||||
{
|
|
||||||
_deviceActionService.OpenAppSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task VaultTimeoutActionAsync()
|
public async Task VaultTimeoutActionAsync()
|
||||||
{
|
{
|
||||||
var options = _vaultTimeoutActions.Select(o =>
|
var options = _vaultTimeoutActions.Select(o =>
|
||||||
@@ -456,7 +419,7 @@ namespace Bit.App.Pages
|
|||||||
else if (await _platformUtilsService.SupportsBiometricAsync())
|
else if (await _platformUtilsService.SupportsBiometricAsync())
|
||||||
{
|
{
|
||||||
_biometric = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
_biometric = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
Device.RuntimePlatform == Device.Android ? "." : null);
|
_deviceActionService.DeviceType == Core.Enums.DeviceType.Android ? "." : null);
|
||||||
}
|
}
|
||||||
if (_biometric == current)
|
if (_biometric == current)
|
||||||
{
|
{
|
||||||
@@ -487,7 +450,7 @@ namespace Bit.App.Pages
|
|||||||
autofillItems.Add(new SettingsPageListItem
|
autofillItems.Add(new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.AutofillServices,
|
Name = AppResources.AutofillServices,
|
||||||
SubLabel = _autofillHandler.AutofillServicesEnabled() ? AppResources.On : AppResources.Off,
|
SubLabel = _deviceActionService.AutofillServicesEnabled() ? AppResources.On : AppResources.Off,
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(Page as SettingsPage)))
|
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(Page as SettingsPage)))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -541,12 +504,6 @@ namespace Bit.App.Pages
|
|||||||
ExecuteAsync = () => UpdatePinAsync()
|
ExecuteAsync = () => UpdatePinAsync()
|
||||||
},
|
},
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
|
||||||
Name = AppResources.ApproveLoginRequests,
|
|
||||||
SubLabel = _approvePasswordlessLoginRequests ? AppResources.On : AppResources.Off,
|
|
||||||
ExecuteAsync = () => ApproveLoginRequestsAsync()
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
{
|
||||||
Name = AppResources.LockNow,
|
Name = AppResources.LockNow,
|
||||||
ExecuteAsync = () => LockAsync()
|
ExecuteAsync = () => LockAsync()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
<ToolbarItem Text="{u:I18n Save}" Command="{Binding SubmitAsyncCommand}" />
|
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" />
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void Save_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
await _vm.SubmitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void ChooseFile_Clicked(object sender, EventArgs e)
|
private async void ChooseFile_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
@@ -10,7 +8,6 @@ using Bit.Core.Exceptions;
|
|||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -18,13 +15,11 @@ namespace Bit.App.Pages
|
|||||||
public class AttachmentsPageViewModel : BaseViewModel
|
public class AttachmentsPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IFileService _fileService;
|
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
private readonly ICryptoService _cryptoService;
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly ILogger _logger;
|
|
||||||
private CipherView _cipher;
|
private CipherView _cipher;
|
||||||
private Cipher _cipherDomain;
|
private Cipher _cipherDomain;
|
||||||
private bool _hasAttachments;
|
private bool _hasAttachments;
|
||||||
@@ -35,16 +30,13 @@ namespace Bit.App.Pages
|
|||||||
public AttachmentsPageViewModel()
|
public AttachmentsPageViewModel()
|
||||||
{
|
{
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_fileService = ServiceContainer.Resolve<IFileService>();
|
|
||||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>();
|
|
||||||
Attachments = new ExtendedObservableCollection<AttachmentView>();
|
Attachments = new ExtendedObservableCollection<AttachmentView>();
|
||||||
DeleteAttachmentCommand = new Command<AttachmentView>(DeleteAsync);
|
DeleteAttachmentCommand = new Command<AttachmentView>(DeleteAsync);
|
||||||
SubmitAsyncCommand = new AsyncCommand(SubmitAsync, allowsMultipleExecutions: false);
|
|
||||||
PageTitle = AppResources.Attachments;
|
PageTitle = AppResources.Attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +59,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
public byte[] FileData { get; set; }
|
public byte[] FileData { get; set; }
|
||||||
public Command DeleteAttachmentCommand { get; set; }
|
public Command DeleteAttachmentCommand { get; set; }
|
||||||
public ICommand SubmitAsyncCommand { get; }
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
@@ -134,7 +125,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
_logger.Exception(e);
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
if (e?.Error != null)
|
if (e?.Error != null)
|
||||||
{
|
{
|
||||||
@@ -142,12 +132,6 @@ namespace Bit.App.Pages
|
|||||||
AppResources.AnErrorHasOccurred);
|
AppResources.AnErrorHasOccurred);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Exception(e);
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +142,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
|
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
|
||||||
}
|
}
|
||||||
await _fileService.SelectFileAsync();
|
await _deviceActionService.SelectFileAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void DeleteAsync(AttachmentView attachment)
|
private async void DeleteAsync(AttachmentView attachment)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
@@ -38,7 +37,6 @@ namespace Bit.App.Pages
|
|||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
@@ -234,7 +232,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (autofillResponse == AppResources.Yes || autofillResponse == AppResources.YesAndSave)
|
if (autofillResponse == AppResources.Yes || autofillResponse == AppResources.YesAndSave)
|
||||||
{
|
{
|
||||||
_autofillHandler.Autofill(cipher);
|
_deviceActionService.Autofill(cipher);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
private readonly IAuditService _auditService;
|
private readonly IAuditService _auditService;
|
||||||
protected readonly IDeviceActionService _deviceActionService;
|
protected readonly IDeviceActionService _deviceActionService;
|
||||||
protected readonly IFileService _fileService;
|
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
protected readonly IPlatformUtilsService _platformUtilsService;
|
protected readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private CipherView _cipher;
|
private CipherView _cipher;
|
||||||
@@ -23,7 +22,6 @@ namespace Bit.App.Pages
|
|||||||
public BaseCipherViewModel()
|
public BaseCipherViewModel()
|
||||||
{
|
{
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_fileService = ServiceContainer.Resolve<IFileService>();
|
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
|
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
@@ -75,3 +73,4 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
|
xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
|
||||||
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
|
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
|
||||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
xmlns:dts="clr-namespace:Bit.App.Lists.DataTemplateSelectors"
|
|
||||||
xmlns:il="clr-namespace:Bit.App.Lists.ItemLayouts.CustomFields"
|
|
||||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||||
x:DataType="pages:CipherAddEditPageViewModel"
|
x:DataType="pages:CipherAddEditPageViewModel"
|
||||||
x:Name="_page"
|
x:Name="_page"
|
||||||
@@ -55,25 +53,6 @@
|
|||||||
IsDestructive="True"
|
IsDestructive="True"
|
||||||
x:Name="_deleteItem"
|
x:Name="_deleteItem"
|
||||||
x:Key="deleteItem" />
|
x:Key="deleteItem" />
|
||||||
|
|
||||||
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
|
||||||
<il:TextCustomFieldItemLayout />
|
|
||||||
</DataTemplate>
|
|
||||||
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
|
||||||
<il:BooleanCustomFieldItemLayout />
|
|
||||||
</DataTemplate>
|
|
||||||
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
|
||||||
<il:HiddenCustomFieldItemLayout />
|
|
||||||
</DataTemplate>
|
|
||||||
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
|
||||||
<il:LinkedCustomFieldItemLayout />
|
|
||||||
</DataTemplate>
|
|
||||||
|
|
||||||
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
|
||||||
TextTemplate="{StaticResource TextCustomFieldDataTemplate}"
|
|
||||||
BooleanTemplate="{StaticResource BooleanCustomFieldDataTemplate}"
|
|
||||||
HiddenTemplate="{StaticResource HiddenCustomFieldDataTemplate}"
|
|
||||||
LinkedTemplate="{StaticResource LinkedCustomFieldDataTemplate}"/>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
@@ -128,26 +107,15 @@
|
|||||||
StyleClass="box-value" />
|
StyleClass="box-value" />
|
||||||
</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"
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
RowDefinitions="Auto,*"
|
<Label
|
||||||
ColumnDefinitions="*,Auto">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n Username}"
|
Text="{u:I18n Username}"
|
||||||
StyleClass="box-label"/>
|
StyleClass="box-label" />
|
||||||
<Entry
|
<Entry
|
||||||
x:Name="_loginUsernameEntry"
|
x:Name="_loginUsernameEntry"
|
||||||
Text="{Binding Cipher.Login.Username}"
|
Text="{Binding Cipher.Login.Username}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
Grid.Row="1"/>
|
</StackLayout>
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
|
||||||
Command="{Binding GenerateUsernameCommand}"
|
|
||||||
Grid.Column="1"
|
|
||||||
Grid.RowSpan="2"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n GenerateUsername}" />
|
|
||||||
</Grid>
|
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -668,10 +636,101 @@
|
|||||||
<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" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<controls:RepeaterView ItemsSource="{Binding Fields}">
|
||||||
Spacing="0"
|
<controls:RepeaterView.ItemTemplate>
|
||||||
BindableLayout.ItemsSource="{Binding Fields}"
|
<DataTemplate x:DataType="pages:CipherAddEditPageFieldViewModel">
|
||||||
BindableLayout.ItemTemplateSelector="{StaticResource CustomFieldItemTemplateSelector}" />
|
<StackLayout Spacing="0" Padding="0">
|
||||||
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label
|
||||||
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
|
IsVisible="{Binding IsBooleanType, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
|
IsVisible="{Binding IsBooleanType, Mode=OneWay}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
VerticalOptions="FillAndExpand"
|
||||||
|
VerticalTextAlignment="Center"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.RowSpan="2" />
|
||||||
|
<Entry
|
||||||
|
Text="{Binding Field.Value}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{Binding IsTextType}" />
|
||||||
|
<controls:MonoEntry
|
||||||
|
Text="{Binding Field.Value}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{Binding IsHiddenType}"
|
||||||
|
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}"
|
||||||
|
IsEnabled="{Binding ShowViewHidden}"
|
||||||
|
IsSpellCheckEnabled="False"
|
||||||
|
IsTextPredictionEnabled="False">
|
||||||
|
<Entry.Keyboard>
|
||||||
|
<Keyboard x:FactoryMethod="Create">
|
||||||
|
<x:Arguments>
|
||||||
|
<KeyboardFlags>None</KeyboardFlags>
|
||||||
|
</x:Arguments>
|
||||||
|
</Keyboard>
|
||||||
|
</Entry.Keyboard>
|
||||||
|
</controls:MonoEntry>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-input"
|
||||||
|
IsVisible="{Binding IsLinkedType}">
|
||||||
|
<Picker
|
||||||
|
x:Name="_linkedFieldOptionPicker"
|
||||||
|
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
||||||
|
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
||||||
|
ItemDisplayBinding="{Binding Key}"
|
||||||
|
StyleClass="box-value" />
|
||||||
|
</StackLayout>
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding BooleanValue}"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
IsVisible="{Binding IsBooleanType}" />
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowHiddenValueIcon}"
|
||||||
|
Command="{Binding ToggleHiddenValueCommand}"
|
||||||
|
IsVisible="{Binding ShowViewHidden}"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||||
|
Command="{Binding BindingContext.FieldOptionsCommand, Source={x:Reference _page}}"
|
||||||
|
CommandParameter="{Binding .}"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Options}" />
|
||||||
|
</Grid>
|
||||||
|
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsBooleanType}" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
</controls:RepeaterView.ItemTemplate>
|
||||||
|
</controls:RepeaterView>
|
||||||
<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"></Button>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace Bit.App.Pages
|
|||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
|
||||||
@@ -41,7 +40,6 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
|
|
||||||
@@ -352,8 +350,8 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Device.RuntimePlatform == Device.Android &&
|
else if (Device.RuntimePlatform == Device.Android &&
|
||||||
!_autofillHandler.AutofillAccessibilityServiceRunning() &&
|
!_deviceActionService.AutofillAccessibilityServiceRunning() &&
|
||||||
!_autofillHandler.AutofillServiceEnabled())
|
!_deviceActionService.AutofillServiceEnabled())
|
||||||
{
|
{
|
||||||
await DisplayAlert(AppResources.BitwardenAutofillService,
|
await DisplayAlert(AppResources.BitwardenAutofillService,
|
||||||
AppResources.BitwardenAutofillServiceAlert2, AppResources.Ok);
|
AppResources.BitwardenAutofillServiceAlert2, AppResources.Ok);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Lists.ItemViewModels.CustomFields;
|
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
@@ -26,9 +25,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IEventService _eventService;
|
private readonly IEventService _eventService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ICustomFieldItemFactory _customFieldItemFactory;
|
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
|
||||||
|
|
||||||
private bool _showNotesSeparator;
|
private bool _showNotesSeparator;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
@@ -77,21 +74,18 @@ namespace Bit.App.Pages
|
|||||||
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
|
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
|
||||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
_customFieldItemFactory = ServiceContainer.Resolve<ICustomFieldItemFactory>("customFieldItemFactory");
|
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
|
||||||
|
|
||||||
GeneratePasswordCommand = new Command(GeneratePassword);
|
GeneratePasswordCommand = new Command(GeneratePassword);
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
||||||
ToggleCardCodeCommand = new Command(ToggleCardCode);
|
ToggleCardCodeCommand = new Command(ToggleCardCode);
|
||||||
UriOptionsCommand = new Command<LoginUriView>(UriOptions);
|
UriOptionsCommand = new Command<LoginUriView>(UriOptions);
|
||||||
FieldOptionsCommand = new Command<ICustomFieldItemViewModel>(FieldOptions);
|
FieldOptionsCommand = new Command<CipherAddEditPageFieldViewModel>(FieldOptions);
|
||||||
PasswordPromptHelpCommand = new Command(PasswordPromptHelp);
|
PasswordPromptHelpCommand = new Command(PasswordPromptHelp);
|
||||||
CopyCommand = new AsyncCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
CopyCommand = new AsyncCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||||
GenerateUsernameCommand = new AsyncCommand(GenerateUsernameAsync, onException: ex => OnGenerateUsernameException(ex), allowsMultipleExecutions: false);
|
|
||||||
Uris = new ExtendedObservableCollection<LoginUriView>();
|
Uris = new ExtendedObservableCollection<LoginUriView>();
|
||||||
Fields = new ExtendedObservableCollection<ICustomFieldItemViewModel>();
|
Fields = new ExtendedObservableCollection<CipherAddEditPageFieldViewModel>();
|
||||||
Collections = new ExtendedObservableCollection<CollectionViewModel>();
|
Collections = new ExtendedObservableCollection<CollectionViewModel>();
|
||||||
AllowPersonal = true;
|
AllowPersonal = true;
|
||||||
|
|
||||||
@@ -151,7 +145,6 @@ namespace Bit.App.Pages
|
|||||||
public Command FieldOptionsCommand { get; set; }
|
public Command FieldOptionsCommand { get; set; }
|
||||||
public Command PasswordPromptHelpCommand { get; set; }
|
public Command PasswordPromptHelpCommand { get; set; }
|
||||||
public AsyncCommand CopyCommand { get; set; }
|
public AsyncCommand CopyCommand { get; set; }
|
||||||
public AsyncCommand GenerateUsernameCommand { get; set; }
|
|
||||||
public string CipherId { get; set; }
|
public string CipherId { get; set; }
|
||||||
public string OrganizationId { get; set; }
|
public string OrganizationId { get; set; }
|
||||||
public string FolderId { get; set; }
|
public string FolderId { get; set; }
|
||||||
@@ -166,7 +159,7 @@ namespace Bit.App.Pages
|
|||||||
public List<KeyValuePair<string, string>> FolderOptions { get; set; }
|
public List<KeyValuePair<string, string>> FolderOptions { get; set; }
|
||||||
public List<KeyValuePair<string, string>> OwnershipOptions { get; set; }
|
public List<KeyValuePair<string, string>> OwnershipOptions { get; set; }
|
||||||
public ExtendedObservableCollection<LoginUriView> Uris { get; set; }
|
public ExtendedObservableCollection<LoginUriView> Uris { get; set; }
|
||||||
public ExtendedObservableCollection<ICustomFieldItemViewModel> Fields { get; set; }
|
public ExtendedObservableCollection<CipherAddEditPageFieldViewModel> Fields { get; set; }
|
||||||
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
|
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
|
||||||
|
|
||||||
public int TypeSelectedIndex
|
public int TypeSelectedIndex
|
||||||
@@ -419,7 +412,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (Cipher.Fields != null)
|
if (Cipher.Fields != null)
|
||||||
{
|
{
|
||||||
Fields.ResetWithRange(Cipher.Fields?.Select(f => _customFieldItemFactory.CreateCustomFieldItem(f, true, Cipher, null, null, FieldOptionsCommand)));
|
Fields.ResetWithRange(Cipher.Fields?.Select(f => new CipherAddEditPageFieldViewModel(Cipher, f)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +503,7 @@ namespace Bit.App.Pages
|
|||||||
if (Page is CipherAddEditPage page && page.FromAutofillFramework)
|
if (Page is CipherAddEditPage page && page.FromAutofillFramework)
|
||||||
{
|
{
|
||||||
// Close and go back to app
|
// Close and go back to app
|
||||||
_autofillHandler.CloseAutofill();
|
_deviceActionService.CloseAutofill();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -599,32 +592,6 @@ namespace Bit.App.Pages
|
|||||||
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GenerateUsernameAsync()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(Cipher?.Login?.Username)
|
|
||||||
&& !await _platformUtilsService.ShowDialogAsync(AppResources.AreYouSureYouWantToOverwriteTheCurrentUsername, null, AppResources.Yes, AppResources.No))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var website = Cipher?.Login?.Uris?.FirstOrDefault()?.Host;
|
|
||||||
|
|
||||||
var page = new GeneratorPage(false, async (username) =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Cipher.Login.Username = username;
|
|
||||||
TriggerCipherChanged();
|
|
||||||
await Page.Navigation.PopModalAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
OnGenerateUsernameException(ex);
|
|
||||||
}
|
|
||||||
}, isUsernameGenerator: true, emailWebsite: website, editMode: true);
|
|
||||||
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void UriOptions(LoginUriView uri)
|
public async void UriOptions(LoginUriView uri)
|
||||||
{
|
{
|
||||||
if (!(Page as CipherAddEditPage).DoOnce())
|
if (!(Page as CipherAddEditPage).DoOnce())
|
||||||
@@ -663,7 +630,7 @@ namespace Bit.App.Pages
|
|||||||
Uris.Add(new LoginUriView());
|
Uris.Add(new LoginUriView());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void FieldOptions(ICustomFieldItemViewModel field)
|
public async void FieldOptions(CipherAddEditPageFieldViewModel field)
|
||||||
{
|
{
|
||||||
if (!(Page as CipherAddEditPage).DoOnce())
|
if (!(Page as CipherAddEditPage).DoOnce())
|
||||||
{
|
{
|
||||||
@@ -725,15 +692,15 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (Fields == null)
|
if (Fields == null)
|
||||||
{
|
{
|
||||||
Fields = new ExtendedObservableCollection<ICustomFieldItemViewModel>();
|
Fields = new ExtendedObservableCollection<CipherAddEditPageFieldViewModel>();
|
||||||
}
|
}
|
||||||
var type = fieldTypeOptions.FirstOrDefault(f => f.Value == typeSelection).Key;
|
var type = fieldTypeOptions.FirstOrDefault(f => f.Value == typeSelection).Key;
|
||||||
Fields.Add(_customFieldItemFactory.CreateCustomFieldItem(new FieldView
|
Fields.Add(new CipherAddEditPageFieldViewModel(Cipher, new FieldView
|
||||||
{
|
{
|
||||||
Type = type,
|
Type = type,
|
||||||
Name = string.IsNullOrWhiteSpace(name) ? null : name,
|
Name = string.IsNullOrWhiteSpace(name) ? null : name,
|
||||||
NewField = true,
|
NewField = true,
|
||||||
}, true, Cipher, null, null, FieldOptionsCommand));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -795,7 +762,7 @@ namespace Bit.App.Pages
|
|||||||
TriggerCipherChanged();
|
TriggerCipherChanged();
|
||||||
|
|
||||||
// Linked Custom Fields only apply to a specific item type
|
// Linked Custom Fields only apply to a specific item type
|
||||||
foreach (var field in Fields.OfType<LinkedCustomFieldItemViewModel>().ToList())
|
foreach (var field in Fields.Where(f => f.IsLinkedType).ToList())
|
||||||
{
|
{
|
||||||
Fields.Remove(field);
|
Fields.Remove(field);
|
||||||
}
|
}
|
||||||
@@ -871,11 +838,114 @@ namespace Bit.App.Pages
|
|||||||
_logger.Exception(ex);
|
_logger.Exception(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void OnGenerateUsernameException(Exception ex)
|
public class CipherAddEditPageFieldViewModel : ExtendedViewModel
|
||||||
|
{
|
||||||
|
private II18nService _i18nService;
|
||||||
|
private FieldView _field;
|
||||||
|
private CipherView _cipher;
|
||||||
|
private bool _showHiddenValue;
|
||||||
|
private bool _booleanValue;
|
||||||
|
private int _linkedFieldOptionSelectedIndex;
|
||||||
|
private string[] _additionalFieldProperties = new string[]
|
||||||
{
|
{
|
||||||
_logger.Exception(ex);
|
nameof(IsBooleanType),
|
||||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
nameof(IsHiddenType),
|
||||||
|
nameof(IsTextType),
|
||||||
|
nameof(IsLinkedType),
|
||||||
|
};
|
||||||
|
|
||||||
|
public CipherAddEditPageFieldViewModel(CipherView cipher, FieldView field)
|
||||||
|
{
|
||||||
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
|
_cipher = cipher;
|
||||||
|
Field = field;
|
||||||
|
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
|
||||||
|
BooleanValue = IsBooleanType && field.Value == "true";
|
||||||
|
LinkedFieldOptionSelectedIndex = !Field.LinkedId.HasValue ? 0 :
|
||||||
|
LinkedFieldOptions.FindIndex(lfo => lfo.Value == Field.LinkedId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldView Field
|
||||||
|
{
|
||||||
|
get => _field;
|
||||||
|
set => SetProperty(ref _field, value, additionalPropertyNames: _additionalFieldProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowHiddenValue
|
||||||
|
{
|
||||||
|
get => _showHiddenValue;
|
||||||
|
set => SetProperty(ref _showHiddenValue, value,
|
||||||
|
additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(ShowHiddenValueIcon)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BooleanValue
|
||||||
|
{
|
||||||
|
get => _booleanValue;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _booleanValue, value);
|
||||||
|
if (IsBooleanType)
|
||||||
|
{
|
||||||
|
Field.Value = value ? "true" : "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int LinkedFieldOptionSelectedIndex
|
||||||
|
{
|
||||||
|
get => _linkedFieldOptionSelectedIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _linkedFieldOptionSelectedIndex, value))
|
||||||
|
{
|
||||||
|
LinkedFieldValueChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions
|
||||||
|
{
|
||||||
|
get => _cipher.LinkedFieldOptions?
|
||||||
|
.Select(kvp => new KeyValuePair<string, LinkedIdType>(_i18nService.T(kvp.Key), kvp.Value))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command ToggleHiddenValueCommand { get; set; }
|
||||||
|
|
||||||
|
public string ShowHiddenValueIcon => _showHiddenValue ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public bool IsTextType => _field.Type == FieldType.Text;
|
||||||
|
public bool IsBooleanType => _field.Type == FieldType.Boolean;
|
||||||
|
public bool IsHiddenType => _field.Type == FieldType.Hidden;
|
||||||
|
public bool IsLinkedType => _field.Type == FieldType.Linked;
|
||||||
|
public bool ShowViewHidden => IsHiddenType && (_cipher.ViewPassword || _field.NewField);
|
||||||
|
|
||||||
|
public void ToggleHiddenValue()
|
||||||
|
{
|
||||||
|
ShowHiddenValue = !ShowHiddenValue;
|
||||||
|
if (ShowHiddenValue && _cipher?.Id != null)
|
||||||
|
{
|
||||||
|
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
|
var task = eventService.CollectAsync(EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TriggerFieldChanged()
|
||||||
|
{
|
||||||
|
TriggerPropertyChanged(nameof(Field), _additionalFieldProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LinkedFieldValueChanged()
|
||||||
|
{
|
||||||
|
if (Field != null && LinkedFieldOptionSelectedIndex > -1)
|
||||||
|
{
|
||||||
|
Field.LinkedId = LinkedFieldOptions.Find(lfo =>
|
||||||
|
lfo.Value == LinkedFieldOptions[LinkedFieldOptionSelectedIndex].Value).Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
|
xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
|
||||||
xmlns:dts="clr-namespace:Bit.App.Lists.DataTemplateSelectors"
|
|
||||||
xmlns:il="clr-namespace:Bit.App.Lists.ItemLayouts.CustomFields"
|
|
||||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||||
x:DataType="pages:CipherDetailsPageViewModel"
|
x:DataType="pages:CipherDetailsPageViewModel"
|
||||||
x:Name="_page"
|
x:Name="_page"
|
||||||
@@ -48,25 +46,6 @@
|
|||||||
<ToolbarItem Text="{u:I18n Clone}" Clicked="Clone_Clicked" Order="Secondary"
|
<ToolbarItem Text="{u:I18n Clone}" Clicked="Clone_Clicked" Order="Secondary"
|
||||||
x:Name="_cloneItem" x:Key="cloneItem" />
|
x:Name="_cloneItem" x:Key="cloneItem" />
|
||||||
|
|
||||||
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
|
||||||
<il:TextCustomFieldItemLayout />
|
|
||||||
</DataTemplate>
|
|
||||||
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
|
||||||
<il:BooleanCustomFieldItemLayout />
|
|
||||||
</DataTemplate>
|
|
||||||
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
|
||||||
<il:HiddenCustomFieldItemLayout />
|
|
||||||
</DataTemplate>
|
|
||||||
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
|
||||||
<il:LinkedCustomFieldItemLayout />
|
|
||||||
</DataTemplate>
|
|
||||||
|
|
||||||
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
|
||||||
TextTemplate="{StaticResource TextCustomFieldDataTemplate}"
|
|
||||||
BooleanTemplate="{StaticResource BooleanCustomFieldDataTemplate}"
|
|
||||||
HiddenTemplate="{StaticResource HiddenCustomFieldDataTemplate}"
|
|
||||||
LinkedTemplate="{StaticResource LinkedCustomFieldDataTemplate}"/>
|
|
||||||
|
|
||||||
<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">
|
||||||
@@ -580,10 +559,85 @@
|
|||||||
<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" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<controls:RepeaterView ItemsSource="{Binding Fields}">
|
||||||
Spacing="0"
|
<controls:RepeaterView.ItemTemplate>
|
||||||
BindableLayout.ItemsSource="{Binding Fields}"
|
<DataTemplate x:DataType="pages:CipherDetailsPageFieldViewModel">
|
||||||
BindableLayout.ItemTemplateSelector="{StaticResource CustomFieldItemTemplateSelector}" />
|
<StackLayout Spacing="0" Padding="0">
|
||||||
|
<Grid StyleClass="box-row">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label
|
||||||
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
|
StyleClass="box-label"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0" />
|
||||||
|
<Label
|
||||||
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{Binding IsTextType}" />
|
||||||
|
<controls:IconLabel
|
||||||
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{Binding IsLinkedType}" />
|
||||||
|
<controls:IconLabel
|
||||||
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="true"
|
||||||
|
AutomationProperties.Name="{Binding ValueAccessibilityText, Mode=OneWay}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
IsVisible="{Binding IsBooleanType}"
|
||||||
|
Margin="0, 5, 0, 0" />
|
||||||
|
<StackLayout IsVisible="{Binding IsHiddenType}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0">
|
||||||
|
<controls:MonoLabel
|
||||||
|
Text="{Binding ColoredHiddenValue, Mode=OneWay}"
|
||||||
|
StyleClass="box-value, text-html"
|
||||||
|
IsVisible="{Binding ShowHiddenValue}" />
|
||||||
|
<controls:MonoLabel
|
||||||
|
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
IsVisible="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}" />
|
||||||
|
</StackLayout>
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowHiddenValueIcon}"
|
||||||
|
Command="{Binding ToggleHiddenValueCommand}"
|
||||||
|
IsVisible="{Binding ShowViewHidden}"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
|
Command="{Binding BindingContext.CopyFieldCommand, Source={x:Reference _page}}"
|
||||||
|
CommandParameter="{Binding Field}"
|
||||||
|
IsVisible="{Binding ShowCopyButton}"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Copy}" />
|
||||||
|
</Grid>
|
||||||
|
<BoxView StyleClass="box-row-separator" />
|
||||||
|
</StackLayout>
|
||||||
|
</DataTemplate>
|
||||||
|
</controls:RepeaterView.ItemTemplate>
|
||||||
|
</controls:RepeaterView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAttachments}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowAttachments}">
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Lists.ItemViewModels.CustomFields;
|
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
@@ -19,7 +18,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class CipherDetailsPageViewModel : BaseCipherViewModel, IPasswordPromptable
|
public class CipherDetailsPageViewModel : BaseCipherViewModel
|
||||||
{
|
{
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
@@ -29,10 +28,9 @@ namespace Bit.App.Pages
|
|||||||
private readonly IEventService _eventService;
|
private readonly IEventService _eventService;
|
||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
private readonly ILocalizeService _localizeService;
|
private readonly ILocalizeService _localizeService;
|
||||||
private readonly ICustomFieldItemFactory _customFieldItemFactory;
|
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
|
|
||||||
private List<ICustomFieldItemViewModel> _fields;
|
private List<CipherDetailsPageFieldViewModel> _fields;
|
||||||
private bool _canAccessPremium;
|
private bool _canAccessPremium;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private bool _showCardNumber;
|
private bool _showCardNumber;
|
||||||
@@ -40,8 +38,8 @@ namespace Bit.App.Pages
|
|||||||
private string _totpCode;
|
private string _totpCode;
|
||||||
private string _totpCodeFormatted;
|
private string _totpCodeFormatted;
|
||||||
private string _totpSec;
|
private string _totpSec;
|
||||||
private double _totpInterval = Constants.TotpDefaultTimer;
|
|
||||||
private bool _totpLow;
|
private bool _totpLow;
|
||||||
|
private DateTime? _totpInterval = null;
|
||||||
private string _previousCipherId;
|
private string _previousCipherId;
|
||||||
private byte[] _attachmentData;
|
private byte[] _attachmentData;
|
||||||
private string _attachmentFilename;
|
private string _attachmentFilename;
|
||||||
@@ -60,7 +58,6 @@ namespace Bit.App.Pages
|
|||||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||||
_customFieldItemFactory = ServiceContainer.Resolve<ICustomFieldItemFactory>("customFieldItemFactory");
|
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
|
|
||||||
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||||
@@ -102,7 +99,7 @@ namespace Bit.App.Pages
|
|||||||
nameof(CanEdit),
|
nameof(CanEdit),
|
||||||
nameof(ShowUpgradePremiumTotpText)
|
nameof(ShowUpgradePremiumTotpText)
|
||||||
};
|
};
|
||||||
public List<ICustomFieldItemViewModel> Fields
|
public List<CipherDetailsPageFieldViewModel> Fields
|
||||||
{
|
{
|
||||||
get => _fields;
|
get => _fields;
|
||||||
set => SetProperty(ref _fields, value);
|
set => SetProperty(ref _fields, value);
|
||||||
@@ -144,7 +141,7 @@ namespace Bit.App.Pages
|
|||||||
public bool IsIdentity => Cipher?.Type == Core.Enums.CipherType.Identity;
|
public bool IsIdentity => Cipher?.Type == Core.Enums.CipherType.Identity;
|
||||||
public bool IsCard => Cipher?.Type == Core.Enums.CipherType.Card;
|
public bool IsCard => Cipher?.Type == Core.Enums.CipherType.Card;
|
||||||
public bool IsSecureNote => Cipher?.Type == Core.Enums.CipherType.SecureNote;
|
public bool IsSecureNote => Cipher?.Type == Core.Enums.CipherType.SecureNote;
|
||||||
public FormattedString ColoredPassword => GeneratedValueFormatter.Format(Cipher.Login.Password);
|
public FormattedString ColoredPassword => PasswordFormatter.FormatPassword(Cipher.Login.Password);
|
||||||
public FormattedString UpdatedText
|
public FormattedString UpdatedText
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -202,7 +199,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowUpgradePremiumTotpText => !CanAccessPremium && !Cipher.OrganizationUseTotp && ShowTotp;
|
public bool ShowUpgradePremiumTotpText => !CanAccessPremium && ShowTotp;
|
||||||
public bool ShowUris => IsLogin && Cipher.Login.HasUris;
|
public bool ShowUris => IsLogin && Cipher.Login.HasUris;
|
||||||
public bool ShowIdentityAddress => IsIdentity && (
|
public bool ShowIdentityAddress => IsIdentity && (
|
||||||
!string.IsNullOrWhiteSpace(Cipher.Identity.Address1) ||
|
!string.IsNullOrWhiteSpace(Cipher.Identity.Address1) ||
|
||||||
@@ -216,7 +213,7 @@ namespace Bit.App.Pages
|
|||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string TotpCodeFormatted
|
public string TotpCodeFormatted
|
||||||
{
|
{
|
||||||
get => ShowUpgradePremiumTotpText ? string.Empty : _totpCodeFormatted;
|
get => _canAccessPremium ? _totpCodeFormatted : string.Empty;
|
||||||
set => SetProperty(ref _totpCodeFormatted, value,
|
set => SetProperty(ref _totpCodeFormatted, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
@@ -241,7 +238,7 @@ namespace Bit.App.Pages
|
|||||||
Page.Resources["textTotp"] = ThemeManager.Resources()[value ? "text-danger" : "text-default"];
|
Page.Resources["textTotp"] = ThemeManager.Resources()[value ? "text-danger" : "text-default"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public double TotpProgress => string.IsNullOrEmpty(TotpSec) ? 0 : double.Parse(TotpSec) * 100 / _totpInterval;
|
public double TotpProgress => string.IsNullOrEmpty(TotpSec) ? 0 : double.Parse(TotpSec) * 100 / 30;
|
||||||
public bool IsDeleted => Cipher.IsDeleted;
|
public bool IsDeleted => Cipher.IsDeleted;
|
||||||
public bool CanEdit => !Cipher.IsDeleted;
|
public bool CanEdit => !Cipher.IsDeleted;
|
||||||
|
|
||||||
@@ -255,17 +252,13 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
Cipher = await cipher.DecryptAsync();
|
Cipher = await cipher.DecryptAsync();
|
||||||
CanAccessPremium = await _stateService.CanAccessPremiumAsync();
|
CanAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||||
|
Fields = Cipher.Fields?.Select(f => new CipherDetailsPageFieldViewModel(this, Cipher, f)).ToList();
|
||||||
Fields = Cipher.Fields?
|
|
||||||
.Select(f => _customFieldItemFactory.CreateCustomFieldItem(f, false, Cipher, this, CopyFieldCommand, null))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
|
if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
|
||||||
(Cipher.OrganizationUseTotp || CanAccessPremium))
|
(Cipher.OrganizationUseTotp || CanAccessPremium))
|
||||||
{
|
{
|
||||||
_totpTickHelper = new TotpHelper(Cipher);
|
_totpTickHelper = new TotpHelper(Cipher);
|
||||||
_totpTickCancellationToken?.Cancel();
|
_totpTickCancellationToken?.Cancel();
|
||||||
_totpInterval = _totpTickHelper.Interval;
|
|
||||||
_totpTickCancellationToken = new CancellationTokenSource();
|
_totpTickCancellationToken = new CancellationTokenSource();
|
||||||
_totpTickTask = new TimerTask(_logger, StartCiphersTotpTick, _totpTickCancellationToken).RunPeriodic();
|
_totpTickTask = new TimerTask(_logger, StartCiphersTotpTick, _totpTickCancellationToken).RunPeriodic();
|
||||||
}
|
}
|
||||||
@@ -285,7 +278,6 @@ namespace Bit.App.Pages
|
|||||||
await _totpTickHelper.GenerateNewTotpValues();
|
await _totpTickHelper.GenerateNewTotpValues();
|
||||||
TotpSec = _totpTickHelper.TotpSec;
|
TotpSec = _totpTickHelper.TotpSec;
|
||||||
TotpCodeFormatted = _totpTickHelper.TotpCodeFormatted;
|
TotpCodeFormatted = _totpTickHelper.TotpCodeFormatted;
|
||||||
_totpInterval = _totpTickHelper.Interval;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -431,6 +423,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null)
|
if (Cipher == null || Cipher.Type != Core.Enums.CipherType.Login || Cipher.Login.Totp == null)
|
||||||
{
|
{
|
||||||
|
_totpInterval = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
|
_totpCode = await _totpService.GetCodeAsync(Cipher.Login.Totp);
|
||||||
@@ -450,6 +443,7 @@ namespace Bit.App.Pages
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
TotpCodeFormatted = null;
|
TotpCodeFormatted = null;
|
||||||
|
_totpInterval = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,7 +487,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
var canOpenFile = true;
|
var canOpenFile = true;
|
||||||
if (!_fileService.CanOpenFile(attachment.FileName))
|
if (!_deviceActionService.CanOpenFile(attachment.FileName))
|
||||||
{
|
{
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
@@ -562,7 +556,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async void OpenAttachment(byte[] data, AttachmentView attachment)
|
public async void OpenAttachment(byte[] data, AttachmentView attachment)
|
||||||
{
|
{
|
||||||
if (!_fileService.OpenFile(data, attachment.Id, attachment.FileName))
|
if (!_deviceActionService.OpenFile(data, attachment.Id, attachment.FileName))
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToOpenFile);
|
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToOpenFile);
|
||||||
return;
|
return;
|
||||||
@@ -573,7 +567,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_attachmentData = data;
|
_attachmentData = data;
|
||||||
_attachmentFilename = attachment.FileName;
|
_attachmentFilename = attachment.FileName;
|
||||||
if (!_fileService.SaveFile(_attachmentData, null, _attachmentFilename, null))
|
if (!_deviceActionService.SaveFile(_attachmentData, null, _attachmentFilename, null))
|
||||||
{
|
{
|
||||||
ClearAttachmentData();
|
ClearAttachmentData();
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToSaveAttachment);
|
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToSaveAttachment);
|
||||||
@@ -582,7 +576,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async void SaveFileSelected(string contentUri, string filename)
|
public async void SaveFileSelected(string contentUri, string filename)
|
||||||
{
|
{
|
||||||
if (_fileService.SaveFile(_attachmentData, null, filename ?? _attachmentFilename, contentUri))
|
if (_deviceActionService.SaveFile(_attachmentData, null, filename ?? _attachmentFilename, contentUri))
|
||||||
{
|
{
|
||||||
ClearAttachmentData();
|
ClearAttachmentData();
|
||||||
_platformUtilsService.ShowToast("success", null, AppResources.SaveAttachmentSuccess);
|
_platformUtilsService.ShowToast("success", null, AppResources.SaveAttachmentSuccess);
|
||||||
@@ -671,7 +665,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> PromptPasswordAsync()
|
internal async Task<bool> PromptPasswordAsync()
|
||||||
{
|
{
|
||||||
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
|
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
|
||||||
{
|
{
|
||||||
@@ -681,4 +675,110 @@ namespace Bit.App.Pages
|
|||||||
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
|
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CipherDetailsPageFieldViewModel : ExtendedViewModel
|
||||||
|
{
|
||||||
|
private II18nService _i18nService;
|
||||||
|
private CipherDetailsPageViewModel _vm;
|
||||||
|
private FieldView _field;
|
||||||
|
private CipherView _cipher;
|
||||||
|
private bool _showHiddenValue;
|
||||||
|
|
||||||
|
public CipherDetailsPageFieldViewModel(CipherDetailsPageViewModel vm, CipherView cipher, FieldView field)
|
||||||
|
{
|
||||||
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
|
_vm = vm;
|
||||||
|
_cipher = cipher;
|
||||||
|
Field = field;
|
||||||
|
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldView Field
|
||||||
|
{
|
||||||
|
get => _field;
|
||||||
|
set => SetProperty(ref _field, value,
|
||||||
|
additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(ValueText),
|
||||||
|
nameof(ValueAccessibilityText),
|
||||||
|
nameof(IsBooleanType),
|
||||||
|
nameof(IsHiddenType),
|
||||||
|
nameof(IsTextType),
|
||||||
|
nameof(ShowCopyButton),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowHiddenValue
|
||||||
|
{
|
||||||
|
get => _showHiddenValue;
|
||||||
|
set => SetProperty(ref _showHiddenValue, value,
|
||||||
|
additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(ShowHiddenValueIcon)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ValueText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsBooleanType)
|
||||||
|
{
|
||||||
|
return _field.BoolValue ? BitwardenIcons.CheckSquare : BitwardenIcons.Square;
|
||||||
|
}
|
||||||
|
else if (IsLinkedType)
|
||||||
|
{
|
||||||
|
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
||||||
|
return BitwardenIcons.Link + _i18nService.T(i18nKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _field.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ValueAccessibilityText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsBooleanType)
|
||||||
|
{
|
||||||
|
return _field.BoolValue ? AppResources.Enabled : AppResources.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValueText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormattedString ColoredHiddenValue => PasswordFormatter.FormatPassword(_field.Value);
|
||||||
|
|
||||||
|
public Command ToggleHiddenValueCommand { get; set; }
|
||||||
|
|
||||||
|
public string ShowHiddenValueIcon => _showHiddenValue ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public bool IsTextType => _field.Type == Core.Enums.FieldType.Text;
|
||||||
|
public bool IsBooleanType => _field.Type == Core.Enums.FieldType.Boolean;
|
||||||
|
public bool IsHiddenType => _field.Type == Core.Enums.FieldType.Hidden;
|
||||||
|
public bool IsLinkedType => _field.Type == Core.Enums.FieldType.Linked;
|
||||||
|
public bool ShowViewHidden => IsHiddenType && _cipher.ViewPassword;
|
||||||
|
public bool ShowCopyButton => _field.Type != Core.Enums.FieldType.Boolean &&
|
||||||
|
!string.IsNullOrWhiteSpace(_field.Value) &&
|
||||||
|
!(IsHiddenType && !_cipher.ViewPassword) &&
|
||||||
|
_field.Type != FieldType.Linked;
|
||||||
|
|
||||||
|
public async void ToggleHiddenValue()
|
||||||
|
{
|
||||||
|
if (!await _vm.PromptPasswordAsync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ShowHiddenValue = !ShowHiddenValue;
|
||||||
|
if (ShowHiddenValue)
|
||||||
|
{
|
||||||
|
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
|
var task = eventService.CollectAsync(
|
||||||
|
Core.Enums.EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
@@ -12,7 +12,7 @@ namespace Bit.App.Pages
|
|||||||
public partial class CiphersPage : BaseContentPage
|
public partial class CiphersPage : BaseContentPage
|
||||||
{
|
{
|
||||||
private readonly string _autofillUrl;
|
private readonly string _autofillUrl;
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
|
||||||
private CiphersPageViewModel _vm;
|
private CiphersPageViewModel _vm;
|
||||||
private bool _hasFocused;
|
private bool _hasFocused;
|
||||||
@@ -48,7 +48,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
NavigationPage.SetTitleView(this, _titleLayout);
|
NavigationPage.SetTitleView(this, _titleLayout);
|
||||||
}
|
}
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchBar SearchBar => _searchBar;
|
public SearchBar SearchBar => _searchBar;
|
||||||
@@ -107,7 +107,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_autofillHandler.CloseAutofill();
|
_deviceActionService.CloseAutofill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ namespace Bit.App.Pages
|
|||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
private readonly ISearchService _searchService;
|
private readonly ISearchService _searchService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
@@ -38,7 +37,6 @@ namespace Bit.App.Pages
|
|||||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||||
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||||
@@ -198,7 +196,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_autofillHandler.Autofill(cipher);
|
_deviceActionService.Autofill(cipher);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace Bit.App.Pages
|
|||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
private string _iconImageSource = string.Empty;
|
private string _iconImageSource = string.Empty;
|
||||||
|
|
||||||
|
public int interval { get; set; }
|
||||||
private double _progress;
|
private double _progress;
|
||||||
private string _totpSec;
|
private string _totpSec;
|
||||||
private string _totpCodeFormatted;
|
private string _totpCodeFormatted;
|
||||||
@@ -36,6 +37,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
Cipher = cipherView;
|
Cipher = cipherView;
|
||||||
WebsiteIconsEnabled = websiteIconsEnabled;
|
WebsiteIconsEnabled = websiteIconsEnabled;
|
||||||
|
interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
|
||||||
CopyCommand = new AsyncCommand(CopyToClipboardAsync,
|
CopyCommand = new AsyncCommand(CopyToClipboardAsync,
|
||||||
onException: ex => _logger.Value.Exception(ex),
|
onException: ex => _logger.Value.Exception(ex),
|
||||||
allowsMultipleExecutions: false);
|
allowsMultipleExecutions: false);
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
}
|
}
|
||||||
|
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||||
_doingLoad = true;
|
_doingLoad = true;
|
||||||
LoadedOnce = true;
|
LoadedOnce = true;
|
||||||
ShowNoData = false;
|
ShowNoData = false;
|
||||||
@@ -220,7 +221,7 @@ namespace Bit.App.Pages
|
|||||||
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var uppercaseGroupNames = Device.RuntimePlatform == Device.iOS;
|
var uppercaseGroupNames = _deviceActionService.DeviceType == DeviceType.iOS;
|
||||||
var hasFavorites = FavoriteCiphers?.Any() ?? false;
|
var hasFavorites = FavoriteCiphers?.Any() ?? false;
|
||||||
if (hasFavorites)
|
if (hasFavorites)
|
||||||
{
|
{
|
||||||
@@ -230,7 +231,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
AddTotpGroupItem(groupedItems, uppercaseGroupNames);
|
AddTotpGroupItem(canAccessPremium, groupedItems, uppercaseGroupNames);
|
||||||
|
|
||||||
groupedItems.Add(new GroupingsPageListGroup(
|
groupedItems.Add(new GroupingsPageListGroup(
|
||||||
AppResources.Types, 4, uppercaseGroupNames, !hasFavorites)
|
AppResources.Types, 4, uppercaseGroupNames, !hasFavorites)
|
||||||
@@ -381,9 +382,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddTotpGroupItem(List<GroupingsPageListGroup> groupedItems, bool uppercaseGroupNames)
|
private void AddTotpGroupItem(bool canAccessPremium, List<GroupingsPageListGroup> groupedItems, bool uppercaseGroupNames)
|
||||||
{
|
{
|
||||||
if (TOTPCiphers?.Any() == true)
|
if (canAccessPremium && TOTPCiphers?.Any() == true)
|
||||||
{
|
{
|
||||||
groupedItems.Insert(0, new GroupingsPageListGroup(
|
groupedItems.Insert(0, new GroupingsPageListGroup(
|
||||||
AppResources.Totp, 1, uppercaseGroupNames, false)
|
AppResources.Totp, 1, uppercaseGroupNames, false)
|
||||||
@@ -400,7 +401,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private void CreateCipherGroupedItems(List<GroupingsPageListGroup> groupedItems)
|
private void CreateCipherGroupedItems(List<GroupingsPageListGroup> groupedItems)
|
||||||
{
|
{
|
||||||
var uppercaseGroupNames = Device.RuntimePlatform == Device.iOS;
|
var uppercaseGroupNames = _deviceActionService.DeviceType == DeviceType.iOS;
|
||||||
_totpTickCts?.Cancel();
|
_totpTickCts?.Cancel();
|
||||||
if (ShowTotp)
|
if (ShowTotp)
|
||||||
{
|
{
|
||||||
@@ -536,11 +537,10 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
|
||||||
NoDataText = AppResources.NoItems;
|
NoDataText = AppResources.NoItems;
|
||||||
_allCiphers = await GetAllCiphersAsync();
|
_allCiphers = await GetAllCiphersAsync();
|
||||||
HasCiphers = _allCiphers.Any();
|
HasCiphers = _allCiphers.Any();
|
||||||
TOTPCiphers = _allCiphers.Where(c => c.IsDeleted == Deleted && c.Type == CipherType.Login && !string.IsNullOrEmpty(c.Login?.Totp) && (c.OrganizationUseTotp || canAccessPremium)).ToList();
|
TOTPCiphers = _allCiphers.Where(c => c.IsDeleted == Deleted && c.Type == CipherType.Login && !string.IsNullOrEmpty(c.Login?.Totp)).ToList();
|
||||||
FavoriteCiphers?.Clear();
|
FavoriteCiphers?.Clear();
|
||||||
NoFolderCiphers?.Clear();
|
NoFolderCiphers?.Clear();
|
||||||
_folderCounts.Clear();
|
_folderCounts.Clear();
|
||||||
|
|||||||
9927
src/App/Resources/AppResources.Designer.cs
generated
9927
src/App/Resources/AppResources.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -300,7 +300,7 @@
|
|||||||
<comment>The title for the vault page.</comment>
|
<comment>The title for the vault page.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="Authenticator" xml:space="preserve">
|
<data name="Authenticator" xml:space="preserve">
|
||||||
<value>Waarmerker</value>
|
<value>Authenticator</value>
|
||||||
<comment>Authenticator TOTP feature</comment>
|
<comment>Authenticator TOTP feature</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="Name" xml:space="preserve">
|
<data name="Name" xml:space="preserve">
|
||||||
@@ -900,8 +900,8 @@
|
|||||||
<value>Kan nie waarmerksleutel lees nie.</value>
|
<value>Kan nie waarmerksleutel lees nie.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
|
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
|
||||||
<value>Rig u kamera op die QR-kode.
|
<value>Point your camera at the QR Code.
|
||||||
Skandering gebeur outomaties.</value>
|
Scanning will happen automatically.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScanQrTitle" xml:space="preserve">
|
<data name="ScanQrTitle" xml:space="preserve">
|
||||||
<value>Skandeer QR-kode</value>
|
<value>Skandeer QR-kode</value>
|
||||||
@@ -1575,10 +1575,6 @@ Skandering gebeur outomaties.</value>
|
|||||||
<value>Nord</value>
|
<value>Nord</value>
|
||||||
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="SolarizedDark" xml:space="preserve">
|
|
||||||
<value>Solarized Dark</value>
|
|
||||||
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="AutofillBlockedUris" xml:space="preserve">
|
<data name="AutofillBlockedUris" xml:space="preserve">
|
||||||
<value>Versperde URI’s vir outovul</value>
|
<value>Versperde URI’s vir outovul</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2269,35 +2265,35 @@ Skandering gebeur outomaties.</value>
|
|||||||
<value>TOTP</value>
|
<value>TOTP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VerificationCodes" xml:space="preserve">
|
<data name="VerificationCodes" xml:space="preserve">
|
||||||
<value>Bevestigingskodes</value>
|
<value>Verification Codes</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PremiumSubscriptionRequired" xml:space="preserve">
|
<data name="PremiumSubscriptionRequired" xml:space="preserve">
|
||||||
<value>Premie-intekening word vereis</value>
|
<value>Premium subscription required</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
|
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
|
||||||
<value>Kan nie waarmerksleutel toevoeg nie? </value>
|
<value>Cannot add authenticator key? </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScanQRCode" xml:space="preserve">
|
<data name="ScanQRCode" xml:space="preserve">
|
||||||
<value>Skandeer QR-kode</value>
|
<value>Scan QR Code</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CannotScanQRCode" xml:space="preserve">
|
<data name="CannotScanQRCode" xml:space="preserve">
|
||||||
<value>Kan nie QR-kode skandeer nie? </value>
|
<value>Cannot scan QR Code? </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AuthenticatorKeyScanner" xml:space="preserve">
|
<data name="AuthenticatorKeyScanner" xml:space="preserve">
|
||||||
<value>Waarmerksleutel</value>
|
<value>Authenticator Key</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EnterKeyManually" xml:space="preserve">
|
<data name="EnterKeyManually" xml:space="preserve">
|
||||||
<value>Voer sleutel handmatig in</value>
|
<value>Enter Key Manually</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddTotp" xml:space="preserve">
|
<data name="AddTotp" xml:space="preserve">
|
||||||
<value>Voeg TOTP toe</value>
|
<value>Add TOTP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SetupTotp" xml:space="preserve">
|
<data name="SetupTotp" xml:space="preserve">
|
||||||
<value>Stel TOTP op</value>
|
<value>Set up TOTP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
|
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
|
||||||
<value>Sodra u die sleutel reg ingevoer het,
|
<value>Once the key is successfully entered,
|
||||||
kies u Voeg TOTP toe om die sleutel veilig te bewaar</value>
|
select Add TOTP to store the key safely</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
|
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
|
||||||
<value></value>
|
<value></value>
|
||||||
@@ -2317,159 +2313,4 @@ kies u Voeg TOTP toe om die sleutel veilig te bewaar</value>
|
|||||||
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
||||||
<value>Is u seker u wil skermopname aktiveer?</value>
|
<value>Is u seker u wil skermopname aktiveer?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogInRequested" xml:space="preserve">
|
|
||||||
<value>Aantekening versoek</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouTryingToLogIn" xml:space="preserve">
|
|
||||||
<value>Probeer u aanteken?</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInAttemptByXOnY" xml:space="preserve">
|
|
||||||
<value>Aantekenversoek deir {0} op {1}</value>
|
|
||||||
</data>
|
|
||||||
<data name="DeviceType" xml:space="preserve">
|
|
||||||
<value>Toesteltipe</value>
|
|
||||||
</data>
|
|
||||||
<data name="IpAddress" xml:space="preserve">
|
|
||||||
<value>IP-adres</value>
|
|
||||||
</data>
|
|
||||||
<data name="Time" xml:space="preserve">
|
|
||||||
<value>Tyd</value>
|
|
||||||
</data>
|
|
||||||
<data name="Near" xml:space="preserve">
|
|
||||||
<value>Naby</value>
|
|
||||||
</data>
|
|
||||||
<data name="ConfirmLogIn" xml:space="preserve">
|
|
||||||
<value>Bevestig aantekening</value>
|
|
||||||
</data>
|
|
||||||
<data name="DenyLogIn" xml:space="preserve">
|
|
||||||
<value>Weier aantekening</value>
|
|
||||||
</data>
|
|
||||||
<data name="JustNow" xml:space="preserve">
|
|
||||||
<value>Sopas</value>
|
|
||||||
</data>
|
|
||||||
<data name="XMinutesAgo" xml:space="preserve">
|
|
||||||
<value>{0} minute gelede</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInAccepted" xml:space="preserve">
|
|
||||||
<value>Aantekening bevestig</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInDenied" xml:space="preserve">
|
|
||||||
<value>Aantekening geweier</value>
|
|
||||||
</data>
|
|
||||||
<data name="ApproveLoginRequests" xml:space="preserve">
|
|
||||||
<value>Keur aantekenversoeke goed</value>
|
|
||||||
</data>
|
|
||||||
<data name="UseThisDeviceToApproveLoginRequestsMadeFromOtherDevices" xml:space="preserve">
|
|
||||||
<value>Gebruik hierdie toestel vir die goedkeur van aantekenversoeke van ander toestelle.</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllowNotifications" xml:space="preserve">
|
|
||||||
<value>Laat kennisgewings toe</value>
|
|
||||||
</data>
|
|
||||||
<data name="ReceivePushNotificationsForNewLoginRequests" xml:space="preserve">
|
|
||||||
<value>Ontvang stootkennisgewings vir nuwe aantekenversoeke</value>
|
|
||||||
</data>
|
|
||||||
<data name="NoThanks" xml:space="preserve">
|
|
||||||
<value>Nee dankie</value>
|
|
||||||
</data>
|
|
||||||
<data name="ConfimLogInAttempForX" xml:space="preserve">
|
|
||||||
<value>Bevestig aantekeningspoging vir {0}</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllNotifications" xml:space="preserve">
|
|
||||||
<value>Alle kennisgewings</value>
|
|
||||||
</data>
|
|
||||||
<data name="PasswordType" xml:space="preserve">
|
|
||||||
<value>Wagwoordtipe</value>
|
|
||||||
</data>
|
|
||||||
<data name="WhatWouldYouLikeToGenerate" xml:space="preserve">
|
|
||||||
<value>Wat wil u genereer?</value>
|
|
||||||
</data>
|
|
||||||
<data name="UsernameType" xml:space="preserve">
|
|
||||||
<value>Gebruikersnaamtipe</value>
|
|
||||||
</data>
|
|
||||||
<data name="PlusAddressedEmail" xml:space="preserve">
|
|
||||||
<value>E-posadres met plus</value>
|
|
||||||
</data>
|
|
||||||
<data name="CatchAllEmail" xml:space="preserve">
|
|
||||||
<value>Allesomvattende e-pos</value>
|
|
||||||
</data>
|
|
||||||
<data name="ForwardedEmailAlias" xml:space="preserve">
|
|
||||||
<value>E-posalias vir aanstuur</value>
|
|
||||||
</data>
|
|
||||||
<data name="RandomWord" xml:space="preserve">
|
|
||||||
<value>Lukrake woord</value>
|
|
||||||
</data>
|
|
||||||
<data name="EmailRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>E-pos (vereis)</value>
|
|
||||||
</data>
|
|
||||||
<data name="DomainNameRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>Domeinnaam (vereis)</value>
|
|
||||||
</data>
|
|
||||||
<data name="APIKeyRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>API-sleutel (vereis)</value>
|
|
||||||
</data>
|
|
||||||
<data name="Service" xml:space="preserve">
|
|
||||||
<value>Diens</value>
|
|
||||||
</data>
|
|
||||||
<data name="AnonAddy" xml:space="preserve">
|
|
||||||
<value>AnonAddy</value>
|
|
||||||
<comment>"AnonAddy" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="FirefoxRelay" xml:space="preserve">
|
|
||||||
<value>Firefox Relay</value>
|
|
||||||
<comment>"Firefox Relay" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="SimpleLogin" xml:space="preserve">
|
|
||||||
<value>SimpleLogin</value>
|
|
||||||
<comment>"SimpleLogin" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="APIAccessToken" xml:space="preserve">
|
|
||||||
<value>API-toegangsteken</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouSureYouWantToOverwriteTheCurrentUsername" xml:space="preserve">
|
|
||||||
<value>Is u seker u wil oor die huidige gebruikersnaam skryf?</value>
|
|
||||||
</data>
|
|
||||||
<data name="GenerateUsername" xml:space="preserve">
|
|
||||||
<value>Genereer gebruikersnaam</value>
|
|
||||||
</data>
|
|
||||||
<data name="EmailType" xml:space="preserve">
|
|
||||||
<value>E-postipe</value>
|
|
||||||
</data>
|
|
||||||
<data name="WebsiteRequired" xml:space="preserve">
|
|
||||||
<value>Webwerf (vereis)</value>
|
|
||||||
</data>
|
|
||||||
<data name="UnknownXErrorMessage" xml:space="preserve">
|
|
||||||
<value>Onbekende {0} fout het voorgekom.</value>
|
|
||||||
</data>
|
|
||||||
<data name="PlusAddressedEmailDescription" xml:space="preserve">
|
|
||||||
<value>Gebruik u e-posverskaffer se subadresvermoëns</value>
|
|
||||||
</data>
|
|
||||||
<data name="CatchAllEmailDescription" xml:space="preserve">
|
|
||||||
<value>Gebruik u domein se opgestelde allesomvattende inmandjie.</value>
|
|
||||||
</data>
|
|
||||||
<data name="ForwardedEmailDescription" xml:space="preserve">
|
|
||||||
<value>Genereer ’n e-posalias met ’n eksterne aanstuurdiens.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Random" xml:space="preserve">
|
|
||||||
<value>Lukraak</value>
|
|
||||||
</data>
|
|
||||||
<data name="AccessibilityServiceDisclosure" xml:space="preserve">
|
|
||||||
<value>Toeganklikheidsdiensopenbaarmaking</value>
|
|
||||||
</data>
|
|
||||||
<data name="AccessibilityDisclosureText" xml:space="preserve">
|
|
||||||
<value>Bitwarden gebruik die Toeganklikheidsdiens om na aantekenvelde in toeps en webwerwe te soek en dan die geskikte veld-ID’s om ’n gebruikersnaam en wagwoord in te voer wanner ’n ooreenstemming vir die toep of webwerf gevind is. Ons bewaar geen van die inligting wat deur die diens gebied word nie en ons poog ook nie om enige op-skerm-elemente buiten teksinvoer van velde te beheer nie.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Accept" xml:space="preserve">
|
|
||||||
<value>Aanvaar</value>
|
|
||||||
</data>
|
|
||||||
<data name="Decline" xml:space="preserve">
|
|
||||||
<value>Wys af</value>
|
|
||||||
</data>
|
|
||||||
<data name="LoginRequestHasAlreadyExpired" xml:space="preserve">
|
|
||||||
<value>Aantekenversoek het reeds verstryk.</value>
|
|
||||||
</data>
|
|
||||||
<data name="LoginAttemptFromXDoYouWantToSwitchToThisAccount" xml:space="preserve">
|
|
||||||
<value>Login attempt from:
|
|
||||||
{0}
|
|
||||||
Do you want to switch to this account?</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -300,7 +300,7 @@
|
|||||||
<comment>The title for the vault page.</comment>
|
<comment>The title for the vault page.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="Authenticator" xml:space="preserve">
|
<data name="Authenticator" xml:space="preserve">
|
||||||
<value>المصادقة</value>
|
<value>Authenticator</value>
|
||||||
<comment>Authenticator TOTP feature</comment>
|
<comment>Authenticator TOTP feature</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="Name" xml:space="preserve">
|
<data name="Name" xml:space="preserve">
|
||||||
@@ -900,8 +900,8 @@
|
|||||||
<value>لا يمكن قراءة مفتاح المصادقة.</value>
|
<value>لا يمكن قراءة مفتاح المصادقة.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
|
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
|
||||||
<value>حدد الكاميرا الخاصة بك على رمز QR.
|
<value>Point your camera at the QR Code.
|
||||||
سيتم الفحص تلقائيا.</value>
|
Scanning will happen automatically.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScanQrTitle" xml:space="preserve">
|
<data name="ScanQrTitle" xml:space="preserve">
|
||||||
<value>مسح رمز QR</value>
|
<value>مسح رمز QR</value>
|
||||||
@@ -1575,10 +1575,6 @@
|
|||||||
<value>نورد</value>
|
<value>نورد</value>
|
||||||
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="SolarizedDark" xml:space="preserve">
|
|
||||||
<value>Solarized Dark</value>
|
|
||||||
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="AutofillBlockedUris" xml:space="preserve">
|
<data name="AutofillBlockedUris" xml:space="preserve">
|
||||||
<value>تعبئة العناوين المحجوبة تلقائياً</value>
|
<value>تعبئة العناوين المحجوبة تلقائياً</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2270,35 +2266,35 @@
|
|||||||
<value>TOTP</value>
|
<value>TOTP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VerificationCodes" xml:space="preserve">
|
<data name="VerificationCodes" xml:space="preserve">
|
||||||
<value>رموز التحقق</value>
|
<value>Verification Codes</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PremiumSubscriptionRequired" xml:space="preserve">
|
<data name="PremiumSubscriptionRequired" xml:space="preserve">
|
||||||
<value>الاشتراك المميز مطلوب</value>
|
<value>Premium subscription required</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
|
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
|
||||||
<value>لا يمكن إضافة مفتاح المصادقة؟ </value>
|
<value>Cannot add authenticator key? </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScanQRCode" xml:space="preserve">
|
<data name="ScanQRCode" xml:space="preserve">
|
||||||
<value>مسح رمز QR</value>
|
<value>Scan QR Code</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CannotScanQRCode" xml:space="preserve">
|
<data name="CannotScanQRCode" xml:space="preserve">
|
||||||
<value>لا يمكن مسح رمز QR؟ </value>
|
<value>Cannot scan QR Code? </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AuthenticatorKeyScanner" xml:space="preserve">
|
<data name="AuthenticatorKeyScanner" xml:space="preserve">
|
||||||
<value>مفتاح المصادقة</value>
|
<value>Authenticator Key</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EnterKeyManually" xml:space="preserve">
|
<data name="EnterKeyManually" xml:space="preserve">
|
||||||
<value>أدخل المفتاح يدوياً</value>
|
<value>Enter Key Manually</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddTotp" xml:space="preserve">
|
<data name="AddTotp" xml:space="preserve">
|
||||||
<value>إضافة TOTP</value>
|
<value>Add TOTP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SetupTotp" xml:space="preserve">
|
<data name="SetupTotp" xml:space="preserve">
|
||||||
<value>إعداد TOTP</value>
|
<value>Set up TOTP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
|
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
|
||||||
<value>بمجرد إدخال المفتاح بنجاح،
|
<value>Once the key is successfully entered,
|
||||||
حدد إضافة TOTP لتخزين المفتاح بأمان</value>
|
select Add TOTP to store the key safely</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
|
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
|
||||||
<value></value>
|
<value></value>
|
||||||
@@ -2318,159 +2314,4 @@
|
|||||||
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
||||||
<value>هل أنت متأكد من أنك تريد تمكين التقاط الشاشة؟</value>
|
<value>هل أنت متأكد من أنك تريد تمكين التقاط الشاشة؟</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogInRequested" xml:space="preserve">
|
|
||||||
<value>طلب تسجيل الدخول</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouTryingToLogIn" xml:space="preserve">
|
|
||||||
<value>هل تحاول تسجيل الدخول؟</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInAttemptByXOnY" xml:space="preserve">
|
|
||||||
<value>محاولة تسجيل الدخول بواسطة {0} في {1}</value>
|
|
||||||
</data>
|
|
||||||
<data name="DeviceType" xml:space="preserve">
|
|
||||||
<value>نوع الجهاز</value>
|
|
||||||
</data>
|
|
||||||
<data name="IpAddress" xml:space="preserve">
|
|
||||||
<value>عنوان IP</value>
|
|
||||||
</data>
|
|
||||||
<data name="Time" xml:space="preserve">
|
|
||||||
<value>الوقت</value>
|
|
||||||
</data>
|
|
||||||
<data name="Near" xml:space="preserve">
|
|
||||||
<value>قريب</value>
|
|
||||||
</data>
|
|
||||||
<data name="ConfirmLogIn" xml:space="preserve">
|
|
||||||
<value>تأكيد تسجيل الدخول</value>
|
|
||||||
</data>
|
|
||||||
<data name="DenyLogIn" xml:space="preserve">
|
|
||||||
<value>رفض تسجيل الدخول</value>
|
|
||||||
</data>
|
|
||||||
<data name="JustNow" xml:space="preserve">
|
|
||||||
<value>للتو</value>
|
|
||||||
</data>
|
|
||||||
<data name="XMinutesAgo" xml:space="preserve">
|
|
||||||
<value>منذ {0} دقائق</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInAccepted" xml:space="preserve">
|
|
||||||
<value>تم تأكيد تسجيل الدخول</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInDenied" xml:space="preserve">
|
|
||||||
<value>تم رفض تسجيل الدخول</value>
|
|
||||||
</data>
|
|
||||||
<data name="ApproveLoginRequests" xml:space="preserve">
|
|
||||||
<value>الموافقة على طلبات تسجيل الدخول</value>
|
|
||||||
</data>
|
|
||||||
<data name="UseThisDeviceToApproveLoginRequestsMadeFromOtherDevices" xml:space="preserve">
|
|
||||||
<value>استخدم هذا الجهاز للموافقة على طلبات تسجيل الدخول من الأجهزة الأخرى.</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllowNotifications" xml:space="preserve">
|
|
||||||
<value>السماح بالإشعارات</value>
|
|
||||||
</data>
|
|
||||||
<data name="ReceivePushNotificationsForNewLoginRequests" xml:space="preserve">
|
|
||||||
<value>تلقي إشعارات دفع لطلبات تسجيل الدخول الجديدة</value>
|
|
||||||
</data>
|
|
||||||
<data name="NoThanks" xml:space="preserve">
|
|
||||||
<value>ﻻ، شكرًا</value>
|
|
||||||
</data>
|
|
||||||
<data name="ConfimLogInAttempForX" xml:space="preserve">
|
|
||||||
<value>تأكيد محاولة تسجيل الدخول لـ {0}</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllNotifications" xml:space="preserve">
|
|
||||||
<value>جميع الإشعارات</value>
|
|
||||||
</data>
|
|
||||||
<data name="PasswordType" xml:space="preserve">
|
|
||||||
<value>نوع كلمة المرور</value>
|
|
||||||
</data>
|
|
||||||
<data name="WhatWouldYouLikeToGenerate" xml:space="preserve">
|
|
||||||
<value>ما الذي ترغب في توليده؟</value>
|
|
||||||
</data>
|
|
||||||
<data name="UsernameType" xml:space="preserve">
|
|
||||||
<value>نوع اسم المستخدم</value>
|
|
||||||
</data>
|
|
||||||
<data name="PlusAddressedEmail" xml:space="preserve">
|
|
||||||
<value>بريد إلكتروني موجه أكثر</value>
|
|
||||||
</data>
|
|
||||||
<data name="CatchAllEmail" xml:space="preserve">
|
|
||||||
<value>تجميع كل البريد</value>
|
|
||||||
</data>
|
|
||||||
<data name="ForwardedEmailAlias" xml:space="preserve">
|
|
||||||
<value>إعادة توجيه الاسم المستعار للبريد الإلكتروني</value>
|
|
||||||
</data>
|
|
||||||
<data name="RandomWord" xml:space="preserve">
|
|
||||||
<value>كلمة عشوائية</value>
|
|
||||||
</data>
|
|
||||||
<data name="EmailRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>البريد الإلكتروني (مطلوب)</value>
|
|
||||||
</data>
|
|
||||||
<data name="DomainNameRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>اسم المجال (مطلوب)</value>
|
|
||||||
</data>
|
|
||||||
<data name="APIKeyRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>مفتاح API (مطلوب)</value>
|
|
||||||
</data>
|
|
||||||
<data name="Service" xml:space="preserve">
|
|
||||||
<value>الخدمة</value>
|
|
||||||
</data>
|
|
||||||
<data name="AnonAddy" xml:space="preserve">
|
|
||||||
<value>AnonAddy</value>
|
|
||||||
<comment>"AnonAddy" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="FirefoxRelay" xml:space="preserve">
|
|
||||||
<value>FirefoxRelay</value>
|
|
||||||
<comment>"Firefox Relay" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="SimpleLogin" xml:space="preserve">
|
|
||||||
<value>SimpleLogin</value>
|
|
||||||
<comment>"SimpleLogin" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="APIAccessToken" xml:space="preserve">
|
|
||||||
<value>رمز الوصول API</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouSureYouWantToOverwriteTheCurrentUsername" xml:space="preserve">
|
|
||||||
<value>هل أنت متأكد من أنك تريد الكتابة فوق اسم المستخدم الحالي؟</value>
|
|
||||||
</data>
|
|
||||||
<data name="GenerateUsername" xml:space="preserve">
|
|
||||||
<value>إنشاء اسم المستخدم</value>
|
|
||||||
</data>
|
|
||||||
<data name="EmailType" xml:space="preserve">
|
|
||||||
<value>نوع البريد الإلكتروني</value>
|
|
||||||
</data>
|
|
||||||
<data name="WebsiteRequired" xml:space="preserve">
|
|
||||||
<value>الموقع (مطلوب)</value>
|
|
||||||
</data>
|
|
||||||
<data name="UnknownXErrorMessage" xml:space="preserve">
|
|
||||||
<value>حدث خطأ {0} غير معروف.</value>
|
|
||||||
</data>
|
|
||||||
<data name="PlusAddressedEmailDescription" xml:space="preserve">
|
|
||||||
<value>استخدم قدرات العنوان الفرعي لمقدم البريد الإلكتروني الخاص بك</value>
|
|
||||||
</data>
|
|
||||||
<data name="CatchAllEmailDescription" xml:space="preserve">
|
|
||||||
<value>استخدم علبة البريد الإلكتروني الشاملة التي تم تكوينها في نطاقك.</value>
|
|
||||||
</data>
|
|
||||||
<data name="ForwardedEmailDescription" xml:space="preserve">
|
|
||||||
<value>إنشاء بريد إلكتروني مستعار مع خدمة إعادة توجيه خارجية.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Random" xml:space="preserve">
|
|
||||||
<value>عشوائي</value>
|
|
||||||
</data>
|
|
||||||
<data name="AccessibilityServiceDisclosure" xml:space="preserve">
|
|
||||||
<value>كشف خدمة إمكانية الوصول</value>
|
|
||||||
</data>
|
|
||||||
<data name="AccessibilityDisclosureText" xml:space="preserve">
|
|
||||||
<value>Bitwarden يستخدم خدمة إمكانية الوصول للبحث عن حقول تسجيل الدخول في التطبيقات ومواقع الويب، ثم يقوم بإنشاء معرفات الحقل المناسب لإدخال اسم المستخدم وكلمة المرور عند العثور على تطابق للتطبيق أو الموقع. ونحن لا نخزن أيا من المعلومات التي قدمتها لنا الخدمة، كما أننا لا نحاول السيطرة على أي عناصر على الشاشة تتجاوز إدخال نصوص وثائق التفويض.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Accept" xml:space="preserve">
|
|
||||||
<value>قبول</value>
|
|
||||||
</data>
|
|
||||||
<data name="Decline" xml:space="preserve">
|
|
||||||
<value>رفض</value>
|
|
||||||
</data>
|
|
||||||
<data name="LoginRequestHasAlreadyExpired" xml:space="preserve">
|
|
||||||
<value>انتهت صلاحية طلب تسجيل الدخول.</value>
|
|
||||||
</data>
|
|
||||||
<data name="LoginAttemptFromXDoYouWantToSwitchToThisAccount" xml:space="preserve">
|
|
||||||
<value>محاولة تسجيل الدخول من:
|
|
||||||
{0}
|
|
||||||
هل تريد التبديل إلى هذا الحساب؟</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -229,7 +229,7 @@
|
|||||||
<value>Qovluqlar</value>
|
<value>Qovluqlar</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FolderUpdated" xml:space="preserve">
|
<data name="FolderUpdated" xml:space="preserve">
|
||||||
<value>Qovluq güncəlləndi.</value>
|
<value>Qovluq yeniləndi.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GoToWebsite" xml:space="preserve">
|
<data name="GoToWebsite" xml:space="preserve">
|
||||||
<value>Veb sayta gedin</value>
|
<value>Veb sayta gedin</value>
|
||||||
@@ -251,10 +251,10 @@
|
|||||||
<comment>Title for the alert when internet connection is required to continue.</comment>
|
<comment>Title for the alert when internet connection is required to continue.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidMasterPassword" xml:space="preserve">
|
<data name="InvalidMasterPassword" xml:space="preserve">
|
||||||
<value>Yararsız ana parol. Yenidən sınayın.</value>
|
<value>Etibarsız ana parol. Yenidən sınayın.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidPIN" xml:space="preserve">
|
<data name="InvalidPIN" xml:space="preserve">
|
||||||
<value>Yararsız PIN. Yenidən sınayın.</value>
|
<value>Etibarsız PIN. Yenidən sınayın.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Launch" xml:space="preserve">
|
<data name="Launch" xml:space="preserve">
|
||||||
<value>Başlat</value>
|
<value>Başlat</value>
|
||||||
@@ -566,7 +566,7 @@
|
|||||||
<comment>Message shown when interacting with the server</comment>
|
<comment>Message shown when interacting with the server</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="LoginOrCreateNewAccount" xml:space="preserve">
|
<data name="LoginOrCreateNewAccount" xml:space="preserve">
|
||||||
<value>Güvənli anbarınıza müraciət etmək üçün giriş edin və ya yeni bir hesab yaradın.</value>
|
<value>Təhlükəsiz anbarınıza müraciət etmək üçün giriş edin və ya yeni bir hesab yaradın.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Manage" xml:space="preserve">
|
<data name="Manage" xml:space="preserve">
|
||||||
<value>İdarə et</value>
|
<value>İdarə et</value>
|
||||||
@@ -666,7 +666,7 @@
|
|||||||
<value>Anbarda axtar</value>
|
<value>Anbarda axtar</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Security" xml:space="preserve">
|
<data name="Security" xml:space="preserve">
|
||||||
<value>Güvənlik</value>
|
<value>Təhlükəsizlik</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Select" xml:space="preserve">
|
<data name="Select" xml:space="preserve">
|
||||||
<value>Seçin</value>
|
<value>Seçin</value>
|
||||||
@@ -681,7 +681,7 @@
|
|||||||
<value>Element məlumatları</value>
|
<value>Element məlumatları</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ItemUpdated" xml:space="preserve">
|
<data name="ItemUpdated" xml:space="preserve">
|
||||||
<value>Element güncəlləndi.</value>
|
<value>Element yeniləndi</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Submitting" xml:space="preserve">
|
<data name="Submitting" xml:space="preserve">
|
||||||
<value>Göndərilir...</value>
|
<value>Göndərilir...</value>
|
||||||
@@ -708,7 +708,7 @@
|
|||||||
<value>İki mərhələli giriş</value>
|
<value>İki mərhələli giriş</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TwoStepLoginConfirmation" xml:space="preserve">
|
<data name="TwoStepLoginConfirmation" xml:space="preserve">
|
||||||
<value>İki mərhələli giriş, güvənlik açarı, kimlik təsdiqləyici tətbiq, SMS, telefon zəngi və ya e-poçt kimi digər cihazlarla girişinizi təsdiqləməyinizi tələb edərək hesabınızı daha da güvənli edir. İki mərhələli giriş, bitwarden.com veb anbarında fəallaşdırıla bilər. Veb saytı indi ziyarət etmək istəyirsiniz?</value>
|
<value>İki mərhələli giriş, təhlükəsizlik açarı, kimlik təsdiqləyici tətbiq, SMS, telefon zəngi və ya e-poçt kimi digər cihazlarla girişinizi təsdiqləməyinizi tələb edərək hesabınızı daha da təhlükəsiz edir. İki mərhələli giriş, bitwarden.com veb anbarında fəallaşdırıla bilər. Veb saytı indi ziyarət etmək istəyirsiniz?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockWith" xml:space="preserve">
|
<data name="UnlockWith" xml:space="preserve">
|
||||||
<value>{0} ilə kilidi açın</value>
|
<value>{0} ilə kilidi açın</value>
|
||||||
@@ -831,7 +831,7 @@
|
|||||||
<comment>For 2FA whenever there are no available providers on this device.</comment>
|
<comment>For 2FA whenever there are no available providers on this device.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="NoTwoStepAvailable" xml:space="preserve">
|
<data name="NoTwoStepAvailable" xml:space="preserve">
|
||||||
<value>Bu hesabda ikir mərhələli giriş fəaldır, ancaq konfiqurasiya edilmiş iki mərhələli təchizatçıların heç biri bu cihazda dəstəklənmir. Zəhmət olmasa dəstəklənən cihaz istifadə edin və/və ya fərqli cihazda dəstəklənən yeni provayderlər əlavə edin (məs. kimlik təsdiqləyici tətbiqi).</value>
|
<value>Bu hesabda ikir mərhələli giriş fəaldır, ancaq konfiqurasiya edilmiş iki mərhələli təchizatçıların heç biri bu cihazda dəstəklənmir. Zəhmət olmasa dəstəklənən cihaz istifadə edin və/və ya fərqli cihazda dəstəklənən yeni təchizatçılar əlavə edin (məs. kimlik təsdiqləyici tətbiqi).</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RecoveryCodeTitle" xml:space="preserve">
|
<data name="RecoveryCodeTitle" xml:space="preserve">
|
||||||
<value>Bərpa kodu</value>
|
<value>Bərpa kodu</value>
|
||||||
@@ -863,7 +863,7 @@
|
|||||||
<value>Davam etmək üçün Yubikey NEO açarınızı cihazınızın arxasına tutun və ya YubiKey-i cihazınızın USB portuna taxıb düyməsinə toxunun.</value>
|
<value>Davam etmək üçün Yubikey NEO açarınızı cihazınızın arxasına tutun və ya YubiKey-i cihazınızın USB portuna taxıb düyməsinə toxunun.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="YubiKeyTitle" xml:space="preserve">
|
<data name="YubiKeyTitle" xml:space="preserve">
|
||||||
<value>YubiKey güvənlik açarı</value>
|
<value>YubiKey təhlükəsizlik açarı</value>
|
||||||
<comment>"YubiKey" is the product name and should not be translated.</comment>
|
<comment>"YubiKey" is the product name and should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="AddNewAttachment" xml:space="preserve">
|
<data name="AddNewAttachment" xml:space="preserve">
|
||||||
@@ -952,7 +952,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Maksimal fayl həcmi 100 MB-dır</value>
|
<value>Maksimal fayl həcmi 100 MB-dır</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdateKey" xml:space="preserve">
|
<data name="UpdateKey" xml:space="preserve">
|
||||||
<value>Şifrələmə açarınızı güncəlləyənə qədər bu özəlliyi istifadə edə bilməzsiniz.</value>
|
<value>Şifrələmə açarınızı yeniləyənə qədər bu özəlliyi istifadə edə bilməzsiniz.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LearnMore" xml:space="preserve">
|
<data name="LearnMore" xml:space="preserve">
|
||||||
<value>Daha ətraflı</value>
|
<value>Daha ətraflı</value>
|
||||||
@@ -999,13 +999,13 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Nömrəni kopyala</value>
|
<value>Nömrəni kopyala</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CopySecurityCode" xml:space="preserve">
|
<data name="CopySecurityCode" xml:space="preserve">
|
||||||
<value>Güvənlik kodunu kopyala</value>
|
<value>Təhlükəsizlik kodunu kopyala</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Number" xml:space="preserve">
|
<data name="Number" xml:space="preserve">
|
||||||
<value>Nömrə</value>
|
<value>Nömrə</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SecurityCode" xml:space="preserve">
|
<data name="SecurityCode" xml:space="preserve">
|
||||||
<value>Güvənlik kodu</value>
|
<value>Təhlükəsizlik kodu</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TypeCard" xml:space="preserve">
|
<data name="TypeCard" xml:space="preserve">
|
||||||
<value>Kart</value>
|
<value>Kart</value>
|
||||||
@@ -1017,7 +1017,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Giriş</value>
|
<value>Giriş</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TypeSecureNote" xml:space="preserve">
|
<data name="TypeSecureNote" xml:space="preserve">
|
||||||
<value>Güvənli qeyd</value>
|
<value>Təhlükəsizlik qeydi</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Address1" xml:space="preserve">
|
<data name="Address1" xml:space="preserve">
|
||||||
<value>Ünvan 1</value>
|
<value>Ünvan 1</value>
|
||||||
@@ -1119,7 +1119,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Sentyabr</value>
|
<value>Sentyabr</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SSN" xml:space="preserve">
|
<data name="SSN" xml:space="preserve">
|
||||||
<value>Sosial güvənlik nömrəsi</value>
|
<value>Sosial təhlükəsizlik nömrəsi</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="StateProvince" xml:space="preserve">
|
<data name="StateProvince" xml:space="preserve">
|
||||||
<value>Ölkə/Əyalət</value>
|
<value>Ölkə/Əyalət</value>
|
||||||
@@ -1278,11 +1278,11 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Əlçatımlılıq xidməti, standart avto-doldurma xidmətini dəstəkləməyən tətbiqlərdə kömək edə bilər.</value>
|
<value>Əlçatımlılıq xidməti, standart avto-doldurma xidmətini dəstəkləməyən tətbiqlərdə kömək edə bilər.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DatePasswordUpdated" xml:space="preserve">
|
<data name="DatePasswordUpdated" xml:space="preserve">
|
||||||
<value>Parol güncəlləndi</value>
|
<value>Parol yeniləndi</value>
|
||||||
<comment>ex. Date this password was updated</comment>
|
<comment>ex. Date this password was updated</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="DateUpdated" xml:space="preserve">
|
<data name="DateUpdated" xml:space="preserve">
|
||||||
<value>Güncəlləndi</value>
|
<value>Yeniləndi</value>
|
||||||
<comment>ex. Date this item was updated</comment>
|
<comment>ex. Date this item was updated</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillActivated" xml:space="preserve">
|
<data name="AutofillActivated" xml:space="preserve">
|
||||||
@@ -1325,7 +1325,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Anbarınıza yeni giriş məlumatlarını əlavə etməyin ən asan yolu, Bitwarden parol avto-doldurma genişləndirməsidir. Bu genişləndirmə haqqında daha ətraflı məlumat almaq üçün "Tənzimləmələr" ekranına gedin.</value>
|
<value>Anbarınıza yeni giriş məlumatlarını əlavə etməyin ən asan yolu, Bitwarden parol avto-doldurma genişləndirməsidir. Bu genişləndirmə haqqında daha ətraflı məlumat almaq üçün "Tənzimləmələr" ekranına gedin.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidEmail" xml:space="preserve">
|
<data name="InvalidEmail" xml:space="preserve">
|
||||||
<value>Yararsız e-poçt ünvanı.</value>
|
<value>Etibarsız e-poçt ünvanı.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Cards" xml:space="preserve">
|
<data name="Cards" xml:space="preserve">
|
||||||
<value>Kartlar</value>
|
<value>Kartlar</value>
|
||||||
@@ -1337,7 +1337,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Girişlər</value>
|
<value>Girişlər</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SecureNotes" xml:space="preserve">
|
<data name="SecureNotes" xml:space="preserve">
|
||||||
<value>Güvənli qeydlər</value>
|
<value>Təhlükəsizlik qeydləri</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AllItems" xml:space="preserve">
|
<data name="AllItems" xml:space="preserve">
|
||||||
<value>Bütün elementlər</value>
|
<value>Bütün elementlər</value>
|
||||||
@@ -1575,10 +1575,6 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Nord</value>
|
<value>Nord</value>
|
||||||
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="SolarizedDark" xml:space="preserve">
|
|
||||||
<value>Günəşli tünd</value>
|
|
||||||
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="AutofillBlockedUris" xml:space="preserve">
|
<data name="AutofillBlockedUris" xml:space="preserve">
|
||||||
<value>Əngəllənən URI-lərin avto-doldurulması</value>
|
<value>Əngəllənən URI-lərin avto-doldurulması</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1595,7 +1591,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Tətbiq yenidən başladılanda</value>
|
<value>Tətbiq yenidən başladılanda</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillServiceNotEnabled" xml:space="preserve">
|
<data name="AutofillServiceNotEnabled" xml:space="preserve">
|
||||||
<value>Avto-doldurma, veb sayt və tətbiqlərdən Bitwarden anbarınıza güvənli şəkildə müraciət etməyinizi asanlaşdırır. Deyəsən, Bitwarden üçün avto-doldurma xidmətini fəallaşdırmamısınız. "Tənzimləmələr" ekranında Bitwarden üçün avto-doldurma xidmətini fəallaşdırın.</value>
|
<value>Avto-doldurma, veb sayt və tətbiqlərdən Bitwarden anbarınıza təhlükəsiz şəkildə müraciət etməyinizi asanlaşdırır. Deyəsən, Bitwarden üçün avto-doldurma xidmətini fəallaşdırmamısınız. "Tənzimləmələr" ekranında Bitwarden üçün avto-doldurma xidmətini fəallaşdırın.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ThemeAppliedOnRestart" xml:space="preserve">
|
<data name="ThemeAppliedOnRestart" xml:space="preserve">
|
||||||
<value>Tema dəyişiklikləriniz tətbiq yenidən başladılanda tətbiq ediləcək.</value>
|
<value>Tema dəyişiklikləriniz tətbiq yenidən başladılanda tətbiq ediləcək.</value>
|
||||||
@@ -1665,7 +1661,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Davam etmək üçün kimliyinizi təsdiqləyin.</value>
|
<value>Davam etmək üçün kimliyinizi təsdiqləyin.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExportVaultWarning" xml:space="preserve">
|
<data name="ExportVaultWarning" xml:space="preserve">
|
||||||
<value>Bu ixrac faylındakı anbar verilənləriniz şifrələnməmiş formatdadır. İxrac edilən faylı, güvənli olmayan kanallar üzərində saxlamamalı və ya göndərməməlisiniz (e-poçt kimi). Bu faylı işiniz bitdikdən sonra dərhal silin.</value>
|
<value>Bu ixrac faylındakı anbar verilənləriniz şifrələnməmiş formatdadır. İxrac edilən faylı saxlamamalı və etibarsız yollarla (e-poçt kimi) göndərməməlisiniz. Bu faylı işiniz bitdikdən sonra dərhal silin.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EncExportKeyWarning" xml:space="preserve">
|
<data name="EncExportKeyWarning" xml:space="preserve">
|
||||||
<value>Bu ixrac faylı, hesabınızın şifrələmə açarını istifadə edərək verilənlərinizi şifrələyir. Hesabınızın şifrələmə açarını döndərsəniz, bu ixrac faylının şifrəsini aça bilməyəcəyiniz üçün yenidən ixrac etməli olacaqsınız.</value>
|
<value>Bu ixrac faylı, hesabınızın şifrələmə açarını istifadə edərək verilənlərinizi şifrələyir. Hesabınızın şifrələmə açarını döndərsəniz, bu ixrac faylının şifrəsini aça bilməyəcəyiniz üçün yenidən ixrac etməli olacaqsınız.</value>
|
||||||
@@ -1801,7 +1797,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Bu özəl simvollardan biri və ya daha çoxunu ehtiva etməlidir: {0}</value>
|
<value>Bu özəl simvollardan biri və ya daha çoxunu ehtiva etməlidir: {0}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MasterPasswordPolicyValidationTitle" xml:space="preserve">
|
<data name="MasterPasswordPolicyValidationTitle" xml:space="preserve">
|
||||||
<value>Yararsız parol</value>
|
<value>Etibarsız parol</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MasterPasswordPolicyValidationMessage" xml:space="preserve">
|
<data name="MasterPasswordPolicyValidationMessage" xml:space="preserve">
|
||||||
<value>Parol, şirkət tələblərini qarşılamır. Zəhmət olmasa siyasət məlumatlarını yoxlayıb yenidən sınayın.</value>
|
<value>Parol, şirkət tələblərini qarşılamır. Zəhmət olmasa siyasət məlumatlarını yoxlayıb yenidən sınayın.</value>
|
||||||
@@ -2018,7 +2014,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="SendUpdated" xml:space="preserve">
|
<data name="SendUpdated" xml:space="preserve">
|
||||||
<value>"Send" güncəlləndi.</value>
|
<value>"Send" yeniləndi.</value>
|
||||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="NewSendCreated" xml:space="preserve">
|
<data name="NewSendCreated" xml:space="preserve">
|
||||||
@@ -2086,19 +2082,19 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Captcha uğursuz oldu. Zəhmət olmasa yenidən sınayın.</value>
|
<value>Captcha uğursuz oldu. Zəhmət olmasa yenidən sınayın.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdatedMasterPassword" xml:space="preserve">
|
<data name="UpdatedMasterPassword" xml:space="preserve">
|
||||||
<value>Güncəllənmiş ana parol</value>
|
<value>Yenilənmiş ana parol</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdateMasterPassword" xml:space="preserve">
|
<data name="UpdateMasterPassword" xml:space="preserve">
|
||||||
<value>Ana parolu güncəllə</value>
|
<value>Ana parolu yenilə</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdateMasterPasswordWarning" xml:space="preserve">
|
<data name="UpdateMasterPasswordWarning" xml:space="preserve">
|
||||||
<value>Ana parolunuz təzəlikcə təşkilatınızdakı bir administrator tərəfindən dəyişdirildi. Anbara müraciət üçün Ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər.</value>
|
<value>Ana parolunuz təzəlikcə təşkilatınızdakı bir administrator tərəfindən dəyişdirildi. Anbara müraciət üçün Ana parolunuzu indi yeniləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdatingPassword" xml:space="preserve">
|
<data name="UpdatingPassword" xml:space="preserve">
|
||||||
<value>Parol yenilənir</value>
|
<value>Parol yenilənir</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UpdatePasswordError" xml:space="preserve">
|
<data name="UpdatePasswordError" xml:space="preserve">
|
||||||
<value>Hazırda parol güncəllənə bilmir</value>
|
<value>Hazırda parol yenilənə bilmir</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoveMasterPassword" xml:space="preserve">
|
<data name="RemoveMasterPassword" xml:space="preserve">
|
||||||
<value>Ana parolu sil</value>
|
<value>Ana parolu sil</value>
|
||||||
@@ -2131,7 +2127,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Tətbiqə qayıt</value>
|
<value>Tətbiqə qayıt</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Fido2CheckBrowser" xml:space="preserve">
|
<data name="Fido2CheckBrowser" xml:space="preserve">
|
||||||
<value>Zəhmət olmasa ilkin brauzerinizin WebAuthn-u təsdiqlədiyinə əmin olub yenidən sınayın.</value>
|
<value>Zəhmət olmasa ilkin səyyahınızın WebAuthn-u təsdiqlədiyinə əmin olub yenidən sınayın.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ResetPasswordAutoEnrollInviteWarning" xml:space="preserve">
|
<data name="ResetPasswordAutoEnrollInviteWarning" xml:space="preserve">
|
||||||
<value>Bu təşkilat, sizi "parol sıfırlama"da avtomatik olaraq qeydiyyata alan müəssisə siyasətinə sahibdir. Qeydiyyat, təşkilat administratorlarına ana parolunuzu dəyişdirmə icazəsi verəcək.</value>
|
<value>Bu təşkilat, sizi "parol sıfırlama"da avtomatik olaraq qeydiyyata alan müəssisə siyasətinə sahibdir. Qeydiyyat, təşkilat administratorlarına ana parolunuzu dəyişdirmə icazəsi verəcək.</value>
|
||||||
@@ -2185,7 +2181,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<value>Hesabınız birdəfəlik silindi</value>
|
<value>Hesabınız birdəfəlik silindi</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidVerificationCode" xml:space="preserve">
|
<data name="InvalidVerificationCode" xml:space="preserve">
|
||||||
<value>Yararsız təsdiqləmə kodu.</value>
|
<value>Etibarsız təsdiqləmə kodu</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RequestOTP" xml:space="preserve">
|
<data name="RequestOTP" xml:space="preserve">
|
||||||
<value>Tək istifadəlik parol tələb et</value>
|
<value>Tək istifadəlik parol tələb et</value>
|
||||||
@@ -2316,159 +2312,4 @@ Skan prosesi avtomatik baş tutacaq.</value>
|
|||||||
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
||||||
<value>Ekranın çəkilməsini fəallaşdırmaq istədiyinizə əminsiniz?</value>
|
<value>Ekranın çəkilməsini fəallaşdırmaq istədiyinizə əminsiniz?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogInRequested" xml:space="preserve">
|
|
||||||
<value>Giriş tələb olundu</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouTryingToLogIn" xml:space="preserve">
|
|
||||||
<value>Giriş etməyə çalışırsınız?</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInAttemptByXOnY" xml:space="preserve">
|
|
||||||
<value>{0} tərəfindən {1} tarixində giriş cəhdi</value>
|
|
||||||
</data>
|
|
||||||
<data name="DeviceType" xml:space="preserve">
|
|
||||||
<value>Cihaz növü</value>
|
|
||||||
</data>
|
|
||||||
<data name="IpAddress" xml:space="preserve">
|
|
||||||
<value>IP ünvan</value>
|
|
||||||
</data>
|
|
||||||
<data name="Time" xml:space="preserve">
|
|
||||||
<value>Vaxt</value>
|
|
||||||
</data>
|
|
||||||
<data name="Near" xml:space="preserve">
|
|
||||||
<value>Yaxın</value>
|
|
||||||
</data>
|
|
||||||
<data name="ConfirmLogIn" xml:space="preserve">
|
|
||||||
<value>Girişi təsdiqlə</value>
|
|
||||||
</data>
|
|
||||||
<data name="DenyLogIn" xml:space="preserve">
|
|
||||||
<value>Girişi rədd et</value>
|
|
||||||
</data>
|
|
||||||
<data name="JustNow" xml:space="preserve">
|
|
||||||
<value>İndicə</value>
|
|
||||||
</data>
|
|
||||||
<data name="XMinutesAgo" xml:space="preserve">
|
|
||||||
<value>{0} dəqiqə əvvəl</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInAccepted" xml:space="preserve">
|
|
||||||
<value>Giriş təsdiqləndi</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInDenied" xml:space="preserve">
|
|
||||||
<value>Giriş rədd edildi</value>
|
|
||||||
</data>
|
|
||||||
<data name="ApproveLoginRequests" xml:space="preserve">
|
|
||||||
<value>Giriş tələblərini təsdiqlə</value>
|
|
||||||
</data>
|
|
||||||
<data name="UseThisDeviceToApproveLoginRequestsMadeFromOtherDevices" xml:space="preserve">
|
|
||||||
<value>Digər cihazlardan edilən giriş tələblərini təsdiqləmək üçün bu cihazı istifadə edin.</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllowNotifications" xml:space="preserve">
|
|
||||||
<value>Bildirişlərə icazə ver</value>
|
|
||||||
</data>
|
|
||||||
<data name="ReceivePushNotificationsForNewLoginRequests" xml:space="preserve">
|
|
||||||
<value>Yeni giriş tələbləri üçün ani bildirişlər alın</value>
|
|
||||||
</data>
|
|
||||||
<data name="NoThanks" xml:space="preserve">
|
|
||||||
<value>Xeyr təşəkkürlər</value>
|
|
||||||
</data>
|
|
||||||
<data name="ConfimLogInAttempForX" xml:space="preserve">
|
|
||||||
<value>{0} üçün giriş cəhdini təsdiqlə</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllNotifications" xml:space="preserve">
|
|
||||||
<value>Bütün bildirişlər</value>
|
|
||||||
</data>
|
|
||||||
<data name="PasswordType" xml:space="preserve">
|
|
||||||
<value>Parol növü</value>
|
|
||||||
</data>
|
|
||||||
<data name="WhatWouldYouLikeToGenerate" xml:space="preserve">
|
|
||||||
<value>Nə yaratmaq istəyirsiniz?</value>
|
|
||||||
</data>
|
|
||||||
<data name="UsernameType" xml:space="preserve">
|
|
||||||
<value>İstifadəçi adı növü</value>
|
|
||||||
</data>
|
|
||||||
<data name="PlusAddressedEmail" xml:space="preserve">
|
|
||||||
<value>Plyus ünvanlı e-poçt</value>
|
|
||||||
</data>
|
|
||||||
<data name="CatchAllEmail" xml:space="preserve">
|
|
||||||
<value>Catch-all E-poçt</value>
|
|
||||||
</data>
|
|
||||||
<data name="ForwardedEmailAlias" xml:space="preserve">
|
|
||||||
<value>Yönləndirilən e-poçt ləqəbi</value>
|
|
||||||
</data>
|
|
||||||
<data name="RandomWord" xml:space="preserve">
|
|
||||||
<value>Təsadüfi söz</value>
|
|
||||||
</data>
|
|
||||||
<data name="EmailRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>E-poçt (tələb olunur)</value>
|
|
||||||
</data>
|
|
||||||
<data name="DomainNameRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>Domen adı (tələb olunur)</value>
|
|
||||||
</data>
|
|
||||||
<data name="APIKeyRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>API açarı (tələb olunur)</value>
|
|
||||||
</data>
|
|
||||||
<data name="Service" xml:space="preserve">
|
|
||||||
<value>Xidmət</value>
|
|
||||||
</data>
|
|
||||||
<data name="AnonAddy" xml:space="preserve">
|
|
||||||
<value>AnonAddy</value>
|
|
||||||
<comment>"AnonAddy" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="FirefoxRelay" xml:space="preserve">
|
|
||||||
<value>Firefox Relay</value>
|
|
||||||
<comment>"Firefox Relay" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="SimpleLogin" xml:space="preserve">
|
|
||||||
<value>SimpleLogin</value>
|
|
||||||
<comment>"SimpleLogin" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="APIAccessToken" xml:space="preserve">
|
|
||||||
<value>API müraciət tokeni</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouSureYouWantToOverwriteTheCurrentUsername" xml:space="preserve">
|
|
||||||
<value>Hazırkı istifadəçi adının üzərinə yazmaq istədiyinizə əminsiniz?</value>
|
|
||||||
</data>
|
|
||||||
<data name="GenerateUsername" xml:space="preserve">
|
|
||||||
<value>İstifadəçi adı yarat</value>
|
|
||||||
</data>
|
|
||||||
<data name="EmailType" xml:space="preserve">
|
|
||||||
<value>E-poçt növü</value>
|
|
||||||
</data>
|
|
||||||
<data name="WebsiteRequired" xml:space="preserve">
|
|
||||||
<value>Veb sayt (tələb olunur)</value>
|
|
||||||
</data>
|
|
||||||
<data name="UnknownXErrorMessage" xml:space="preserve">
|
|
||||||
<value>Bilinməyən {0} xəta baş verdi.</value>
|
|
||||||
</data>
|
|
||||||
<data name="PlusAddressedEmailDescription" xml:space="preserve">
|
|
||||||
<value>E-poçt provayderinizin alt ünvan özəlliklərini istifadə edin</value>
|
|
||||||
</data>
|
|
||||||
<data name="CatchAllEmailDescription" xml:space="preserve">
|
|
||||||
<value>Domeninizin konfiqurasiya edilmiş hamısını yaxalama gələn qutusunu istifadə edin.</value>
|
|
||||||
</data>
|
|
||||||
<data name="ForwardedEmailDescription" xml:space="preserve">
|
|
||||||
<value>Xarici yönləndirmə xidməti ilə e-poçt ləqəbi yaradın.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Random" xml:space="preserve">
|
|
||||||
<value>Təsadüfi</value>
|
|
||||||
</data>
|
|
||||||
<data name="AccessibilityServiceDisclosure" xml:space="preserve">
|
|
||||||
<value>Əlçatımlılıq Xidməti açıqlaması</value>
|
|
||||||
</data>
|
|
||||||
<data name="AccessibilityDisclosureText" xml:space="preserve">
|
|
||||||
<value>Bitwarden, tətbiqlərdə və veb saytlarda giriş sahələrini axtarmaq üçün Əlçatımlılıq Xidmətini istifadə edir, daha sonra tətbiq və ya sayt üçün uyğunluq aşkar etdikdə istifadəçi adı və parolun daxil edilməsi üçün müvafiq sahə kimliklərini yaradır. Xidmət tərəfindən bizə təqdim edilən məlumatların heç birini saxlamırıq, kimlik məlumatlarının daxil edilməsindən kənar ekrandakı hər hansısa elementə nəzarət etməyə cəhd etmirik.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Accept" xml:space="preserve">
|
|
||||||
<value>Qəbul et</value>
|
|
||||||
</data>
|
|
||||||
<data name="Decline" xml:space="preserve">
|
|
||||||
<value>Rədd et</value>
|
|
||||||
</data>
|
|
||||||
<data name="LoginRequestHasAlreadyExpired" xml:space="preserve">
|
|
||||||
<value>Giriş tələbinin müddəti artıq bitib.</value>
|
|
||||||
</data>
|
|
||||||
<data name="LoginAttemptFromXDoYouWantToSwitchToThisAccount" xml:space="preserve">
|
|
||||||
<value>Giriş cəhdi:
|
|
||||||
{0}
|
|
||||||
Bu hesaba keçmək istəyirsiniz?</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1575,10 +1575,6 @@
|
|||||||
<value>Норд</value>
|
<value>Норд</value>
|
||||||
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="SolarizedDark" xml:space="preserve">
|
|
||||||
<value>Преекспонирано тъмен</value>
|
|
||||||
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="AutofillBlockedUris" xml:space="preserve">
|
<data name="AutofillBlockedUris" xml:space="preserve">
|
||||||
<value>Блокирани за авт. попълване адреси</value>
|
<value>Блокирани за авт. попълване адреси</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2080,7 +2076,7 @@
|
|||||||
<value>Това действие е защитено. За да продължите, въведете отново главната си парола, за да потвърдите самоличността си.</value>
|
<value>Това действие е защитено. За да продължите, въведете отново главната си парола, за да потвърдите самоличността си.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CaptchaRequired" xml:space="preserve">
|
<data name="CaptchaRequired" xml:space="preserve">
|
||||||
<value>Captcha required</value>
|
<value>Captcha Required</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CaptchaFailed" xml:space="preserve">
|
<data name="CaptchaFailed" xml:space="preserve">
|
||||||
<value>
|
<value>
|
||||||
@@ -2120,7 +2116,7 @@
|
|||||||
<value>FIDO2 WebAuthn</value>
|
<value>FIDO2 WebAuthn</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Fido2Instruction" xml:space="preserve">
|
<data name="Fido2Instruction" xml:space="preserve">
|
||||||
<value>To continue, have your FIDO2 WebAuthn compatible security key ready, then follow the instructions after clicking 'Authenticate WebAuthn' on the next screen.</value>
|
<value>To continue, have your FIDO2 WebAuthn enabled security key ready, then follow the instructions after clicking 'Authenticate WebAuthn' on the next screen.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Fido2Desc" xml:space="preserve">
|
<data name="Fido2Desc" xml:space="preserve">
|
||||||
<value>Идентификация чрез FIDO2 WebAuthn – можете да се идентифицирате чрез външен ключ за сигурност.</value>
|
<value>Идентификация чрез FIDO2 WebAuthn – можете да се идентифицирате чрез външен ключ за сигурност.</value>
|
||||||
@@ -2318,159 +2314,4 @@ select Add TOTP to store the key safely</value>
|
|||||||
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
||||||
<value>Наистина ли искате да разрешите заснемането на екрана?</value>
|
<value>Наистина ли искате да разрешите заснемането на екрана?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogInRequested" xml:space="preserve">
|
|
||||||
<value>Направена е заявка за вписване</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouTryingToLogIn" xml:space="preserve">
|
|
||||||
<value>Опитвате се да се впишете ли?</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInAttemptByXOnY" xml:space="preserve">
|
|
||||||
<value>Опит за вписване от {0} в {1}</value>
|
|
||||||
</data>
|
|
||||||
<data name="DeviceType" xml:space="preserve">
|
|
||||||
<value>Вид устройство</value>
|
|
||||||
</data>
|
|
||||||
<data name="IpAddress" xml:space="preserve">
|
|
||||||
<value>IP адрес</value>
|
|
||||||
</data>
|
|
||||||
<data name="Time" xml:space="preserve">
|
|
||||||
<value>Време</value>
|
|
||||||
</data>
|
|
||||||
<data name="Near" xml:space="preserve">
|
|
||||||
<value>Near</value>
|
|
||||||
</data>
|
|
||||||
<data name="ConfirmLogIn" xml:space="preserve">
|
|
||||||
<value>Потвърждаване на вписването</value>
|
|
||||||
</data>
|
|
||||||
<data name="DenyLogIn" xml:space="preserve">
|
|
||||||
<value>Отказване на вписването</value>
|
|
||||||
</data>
|
|
||||||
<data name="JustNow" xml:space="preserve">
|
|
||||||
<value>Току-що</value>
|
|
||||||
</data>
|
|
||||||
<data name="XMinutesAgo" xml:space="preserve">
|
|
||||||
<value>Преди {0} минути</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInAccepted" xml:space="preserve">
|
|
||||||
<value>Вписването е потвърдено</value>
|
|
||||||
</data>
|
|
||||||
<data name="LogInDenied" xml:space="preserve">
|
|
||||||
<value>Вписването е отказано</value>
|
|
||||||
</data>
|
|
||||||
<data name="ApproveLoginRequests" xml:space="preserve">
|
|
||||||
<value>Одобряване на заявки за вписване</value>
|
|
||||||
</data>
|
|
||||||
<data name="UseThisDeviceToApproveLoginRequestsMadeFromOtherDevices" xml:space="preserve">
|
|
||||||
<value>Използвайте това устройство за одобряване на заявки за вписване направени от други устройства.</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllowNotifications" xml:space="preserve">
|
|
||||||
<value>Разрешаване на известията</value>
|
|
||||||
</data>
|
|
||||||
<data name="ReceivePushNotificationsForNewLoginRequests" xml:space="preserve">
|
|
||||||
<value>Получаване на известия при нови заявки за вписване</value>
|
|
||||||
</data>
|
|
||||||
<data name="NoThanks" xml:space="preserve">
|
|
||||||
<value>Не, благодаря</value>
|
|
||||||
</data>
|
|
||||||
<data name="ConfimLogInAttempForX" xml:space="preserve">
|
|
||||||
<value>Потвърждаване на заявката за вписване за {0}</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllNotifications" xml:space="preserve">
|
|
||||||
<value>Всички известия</value>
|
|
||||||
</data>
|
|
||||||
<data name="PasswordType" xml:space="preserve">
|
|
||||||
<value>Тип парола</value>
|
|
||||||
</data>
|
|
||||||
<data name="WhatWouldYouLikeToGenerate" xml:space="preserve">
|
|
||||||
<value>Какво бихте искали да генерирате?</value>
|
|
||||||
</data>
|
|
||||||
<data name="UsernameType" xml:space="preserve">
|
|
||||||
<value>Тип потребителско име</value>
|
|
||||||
</data>
|
|
||||||
<data name="PlusAddressedEmail" xml:space="preserve">
|
|
||||||
<value>Plus addressed email</value>
|
|
||||||
</data>
|
|
||||||
<data name="CatchAllEmail" xml:space="preserve">
|
|
||||||
<value>Catch-all email</value>
|
|
||||||
</data>
|
|
||||||
<data name="ForwardedEmailAlias" xml:space="preserve">
|
|
||||||
<value>Forwarded email alias</value>
|
|
||||||
</data>
|
|
||||||
<data name="RandomWord" xml:space="preserve">
|
|
||||||
<value>Произволна дума</value>
|
|
||||||
</data>
|
|
||||||
<data name="EmailRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>Е-поща (задължително)</value>
|
|
||||||
</data>
|
|
||||||
<data name="DomainNameRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>Име на домейн (задължително)</value>
|
|
||||||
</data>
|
|
||||||
<data name="APIKeyRequiredParenthesis" xml:space="preserve">
|
|
||||||
<value>Ключ за API (задължително)</value>
|
|
||||||
</data>
|
|
||||||
<data name="Service" xml:space="preserve">
|
|
||||||
<value>Услуга</value>
|
|
||||||
</data>
|
|
||||||
<data name="AnonAddy" xml:space="preserve">
|
|
||||||
<value>AnonAddy</value>
|
|
||||||
<comment>"AnonAddy" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="FirefoxRelay" xml:space="preserve">
|
|
||||||
<value>Firefox Relay</value>
|
|
||||||
<comment>"Firefox Relay" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="SimpleLogin" xml:space="preserve">
|
|
||||||
<value>SimpleLogin</value>
|
|
||||||
<comment>"SimpleLogin" is the product name and should not be translated.</comment>
|
|
||||||
</data>
|
|
||||||
<data name="APIAccessToken" xml:space="preserve">
|
|
||||||
<value>Идентификатор за достъп до API</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouSureYouWantToOverwriteTheCurrentUsername" xml:space="preserve">
|
|
||||||
<value>Наистина ли искате да замените текущото потребителско име?</value>
|
|
||||||
</data>
|
|
||||||
<data name="GenerateUsername" xml:space="preserve">
|
|
||||||
<value>Генериране на потр. име</value>
|
|
||||||
</data>
|
|
||||||
<data name="EmailType" xml:space="preserve">
|
|
||||||
<value>Email Type</value>
|
|
||||||
</data>
|
|
||||||
<data name="WebsiteRequired" xml:space="preserve">
|
|
||||||
<value>Уеб сайт (задължително)</value>
|
|
||||||
</data>
|
|
||||||
<data name="UnknownXErrorMessage" xml:space="preserve">
|
|
||||||
<value>Възникна неизвестна грешка {0}.</value>
|
|
||||||
</data>
|
|
||||||
<data name="PlusAddressedEmailDescription" xml:space="preserve">
|
|
||||||
<value>Използвайте възможностите за под-адресиране на е-поща на своя доставчик</value>
|
|
||||||
</data>
|
|
||||||
<data name="CatchAllEmailDescription" xml:space="preserve">
|
|
||||||
<value>Use your domain's configured catch-all inbox.</value>
|
|
||||||
</data>
|
|
||||||
<data name="ForwardedEmailDescription" xml:space="preserve">
|
|
||||||
<value>Generate an email alias with an external forwarding service.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Random" xml:space="preserve">
|
|
||||||
<value>Произволно</value>
|
|
||||||
</data>
|
|
||||||
<data name="AccessibilityServiceDisclosure" xml:space="preserve">
|
|
||||||
<value>Използване на услугата за достъпност</value>
|
|
||||||
</data>
|
|
||||||
<data name="AccessibilityDisclosureText" xml:space="preserve">
|
|
||||||
<value>Битуорден използва услугата за достъпност, за да разпознава полета за вписване в приложенията и уеб сайтовете и, ако в трезора има съвпадение, да попълва потребителското име и паролата. Никаква информация, предоставена ни от услугата, не се запазва, както и не правим опити да контролираме елементите на екрана, освен въвеждането на данните за идентификация.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Accept" xml:space="preserve">
|
|
||||||
<value>Приемане</value>
|
|
||||||
</data>
|
|
||||||
<data name="Decline" xml:space="preserve">
|
|
||||||
<value>Отказване</value>
|
|
||||||
</data>
|
|
||||||
<data name="LoginRequestHasAlreadyExpired" xml:space="preserve">
|
|
||||||
<value>Заявката за вписване вече е изтекла.</value>
|
|
||||||
</data>
|
|
||||||
<data name="LoginAttemptFromXDoYouWantToSwitchToThisAccount" xml:space="preserve">
|
|
||||||
<value>Опит за вписване от:
|
|
||||||
{0}
|
|
||||||
Искате ли да превключите към тази регистрация?</value>
|
|
||||||
</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