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

Compare commits

..

14 Commits

Author SHA1 Message Date
github-actions[bot]
efe128b932 Bumped version to 2022.6.2 (#1981)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit 7802da2b9c)
2022-07-21 11:16:57 -04:00
Federico Maccaroni
df2e52f82c EC-308 Fix crash produced by creating avatar image on AccountSwitchingOverlayHelper and also added more logging to see when it happens. (#1983)
(cherry picked from commit 846d3a85a2)
2022-07-20 17:51:00 -04:00
github-actions[bot]
2d224f5e22 Bumped version to 2022.6.1 (#1969)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit a6ddc2496f)
2022-07-06 18:56:39 -04:00
Matt Gibson
4831097c0b Add user verification to reset password request (#1980)
We only need master password hash because this is currently
only used for sso password setting after auto-provisioning. Key
Connector is not involved in these accounts

(cherry picked from commit 58a3662d0f)
2022-07-06 18:24:33 -04:00
github-actions[bot]
72f2983885 Bumped version to 2022.6.0 (#1968)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit d9a818279f)
2022-06-29 09:12:27 -07:00
Matt Gibson
ee68dcf3b0 Add ssoToken to limit lifetime of SSO redirect (#1965)
(cherry picked from commit 6e2e613fee)
2022-06-27 16:05:11 -04:00
Federico Andrés Maccaroni
daab473cbb SG-396 Fix tappable area after hiding account switching 2022-06-21 09:33:59 -03:00
Joseph Flinn
e6b99270cf Pin NuGet version (#1957)
* Pinning the version of NuGet to 5.x

* pinning NuGet verison to 5.9.x

* pinning NuGet to 5.9.0.7134

* pinning NuGet to 5.9.0

(cherry picked from commit c892e9fa57)
2022-06-16 14:48:47 -07:00
Federico Andrés Maccaroni
a8d9aaa7fe [SG-386] iOS Update user state when coming from background 2022-06-16 14:01:28 -03:00
mp-bw
542ef5f31a fix for missing personal items added prior to joining org with personal ownership policy (#1955) 2022-06-16 09:56:03 -04:00
mp-bw
0b2fc2a647 separate init and showVaultFilter property set (#1954) 2022-06-15 15:19:34 -04:00
mp-bw
a95fdf67a1 [SG-390] Fix for missing org items with single org & personal ownership enabled (#1953)
* fix for missing org items with single org & personal ownership enabled

* fix for ui issue with vault filter state change on pull to refresh
2022-06-15 10:45:01 -04:00
mp-bw
73890162bf Additional logic around filter display (#1951) 2022-06-14 13:05:28 -04:00
Joseph Flinn
ef14a8f850 Updating the release version check to use the new action (#1934)
* Updating the release version check to use the new action

* Update .github/workflows/release.yml

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
(cherry picked from commit 5579817f9f)
2022-06-13 12:59:24 -07:00
213 changed files with 10171 additions and 24224 deletions

View File

@@ -1,64 +0,0 @@
---
name: Automatic responses
on:
issues:
types:
- labeled
jobs:
close-issue:
name: 'Close issue with automatic response'
runs-on: ubuntu-20.04
permissions:
issues: write
steps:
# Feature request
- if: github.event.label.name == 'feature-request'
name: Feature request
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
Please [sign up on our forums](https://community.bitwarden.com/signup) and search to see if this request already exists. If so, you can vote for it and contribute to any discussions about it. If not, you can re-create the request there so that it can be properly tracked.
This issue will now be closed. Thanks!
# Intended behavior
- if: github.event.label.name == 'intended-behavior'
name: Intended behaviour
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
Your issue appears to be describing the intended behavior of the software. If you want this to be changed, it would be a feature request.
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
Please [sign up on our forums](https://community.bitwarden.com/signup) and search to see if this request already exists. If so, you can vote for it and contribute to any discussions about it. If not, you can re-create the request there so that it can be properly tracked.
This issue will now be closed. Thanks!
# Customer support request
- if: github.event.label.name == 'customer-support'
name: Customer Support request
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
We use GitHub issues as a place to track bugs and other development related issues. Your issue appears to be a support request, or would otherwise be better handled by our dedicated Customer Success team.
Please contact us using our [Contact page](https://bitwarden.com/contact). You can include a link to this issue in the message content.
Alternatively, you can also search for an answer in our [help documentation](https://bitwarden.com/help/) or get help from other Bitwarden users on our [community forums](https://community.bitwarden.com/c/support/). The issue here will be closed.
# Resolved
- if: github.event.label.name == 'resolved'
name: Resolved
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
# Stale
- if: github.event.label.name == 'stale'
name: Stale
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with:
comment: |
As we havent heard from you about this problem in some time, this issue will now be closed.
If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.

View File

@@ -57,7 +57,7 @@ jobs:
android:
name: Android
runs-on: windows-2022
runs-on: windows-2019
needs: setup
steps:
- name: Setup NuGet
@@ -68,26 +68,6 @@ jobs:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
- name: Work Around for broken Windows 2022 Runner Image
run: |
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
$InstallPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
$componentsToAdd = @(
"Component.Xamarin"
)
[string]$workloadArgs = $componentsToAdd | ForEach-Object {" --add " + $_}
$Arguments = ('/c', "vs_installer.exe", 'modify', '--installPath', "`"$InstallPath`"",$workloadArgs, '--quiet', '--norestart', '--nocache')
$process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden
if ($process.ExitCode -eq 0)
{
Write-Host "components have been successfully added"
}
else
{
Write-Host "components were not installed"
exit 1
}
- name: Print environment
run: |
nuget help | grep Version
@@ -232,7 +212,7 @@ jobs:
f-droid:
name: F-Droid Build
runs-on: windows-2022
runs-on: windows-2019
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
@@ -242,26 +222,6 @@ jobs:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
- name: Work Around for broken Windows 2022 Runner Image
run: |
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
$InstallPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
$componentsToAdd = @(
"Component.Xamarin"
)
[string]$workloadArgs = $componentsToAdd | ForEach-Object {" --add " + $_}
$Arguments = ('/c', "vs_installer.exe", 'modify', '--installPath', "`"$InstallPath`"",$workloadArgs, '--quiet', '--norestart', '--nocache')
$process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden
if ($process.ExitCode -eq 0)
{
Write-Host "components have been successfully added"
}
else
{
Write-Host "components were not installed"
exit 1
}
- name: Print environment
run: |
nuget help | grep Version

View File

@@ -30,7 +30,7 @@ jobs:
secrets: "crowdin-api-token"
- name: Download translations
uses: crowdin/github-action@12143a68c213f3c6d9913c9e5023224f7231face
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@@ -13,11 +13,6 @@ on:
- Initial Release
- Redeploy
- Dry Run
fdroid_publish:
description: 'Publish to f-droid store'
required: true
default: true
type: boolean
jobs:
release:
@@ -53,38 +48,18 @@ jobs:
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Create GitHub deployment
uses: chrnorm/deployment-action@1b599fe41a0ef1f95191e7f2eec4743f2d7dfc48
id: deployment
with:
token: '${{ secrets.GITHUB_TOKEN }}'
initial-status: 'in_progress'
environment: 'production'
description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ steps.branch.outputs.branch-name }}'
task: release
- name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
workflow: build.yml
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
- name: Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
workflow: build.yml
workflow_conclusion: success
branch: master
- name: Prep Bitwarden iOS release asset
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
if: github.event.inputs.release_type != 'Dry Run'
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
@@ -98,34 +73,16 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
- name: Update deployment status to Success
if: ${{ success() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure
if: ${{ failure() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
f-droid:
name: F-Droid Release
runs-on: ubuntu-20.04
needs: release
if: inputs.fdroid_publish
steps:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
workflow: build.yml
@@ -133,15 +90,6 @@ jobs:
branch: ${{ needs.release.outputs.branch-name }}
name: com.x8bit.bitwarden-fdroid.apk
- name: Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
workflow: build.yml
workflow_conclusion: success
branch: master
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node
uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
with:
@@ -207,5 +155,5 @@ jobs:
cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
if: github.event.inputs.release_type != 'Dry Run'
run: npm run deploy

View File

@@ -1,30 +0,0 @@
---
name: 'Close stale issues and PRs'
on:
workflow_dispatch:
schedule: # Run once a day at 5.23am (arbitrary but should avoid peak loads on the hour)
- cron: '23 5 * * *'
jobs:
stale:
name: 'Check for stale issues and PRs'
runs-on: ubuntu-20.04
steps:
- name: 'Run stale action'
uses: actions/stale@3cc123766321e9f15a6676375c154ccffb12a358 # v5.0.0
with:
stale-issue-label: 'needs-reply'
stale-pr-label: 'needs-changes'
days-before-stale: -1 # Do not apply the stale labels automatically, this is a manual process
days-before-issue-close: 14 # Close issue if no further activity after X days
days-before-pr-close: 21 # Close PR if no further activity after X days
close-issue-message: |
We need more information before we can help you with your problem. As we havent heard from you recently, this issue will be closed.
If this happens again or continues to be an problem, please respond to this issue with the information weve requested and anything else relevant.
close-pr-message: |
We cant merge your pull request until you make the changes weve requested. As we havent heard from you recently, this pull request will be closed.
If youre still working on this, please respond here after youve made the changes weve requested and our team will re-open it for further review.
Please make sure to resolve any conflicts with the master branch before requesting another review.

View File

@@ -1,67 +0,0 @@
---
name: Version Auto Bump
on:
release:
types: [published]
jobs:
setup:
name: "Setup"
runs-on: ubuntu-20.04
outputs:
version_number: ${{ steps.version.outputs.new-version }}
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Get version to bump
id: version
env:
RELEASE_TAG: ${{ github.event.release.tag }}
run: |
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]\.)([0-9])/\1/')
CURR_VER=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]\.)([0-9])/\2/')
echo $CURR_VER
((CURR_VER++))
NEW_VER=$CURR_MAJOR$CURR_VER
echo $NEW_VER
echo "::set-output name=new-version::$NEW_VER"
trigger_version_bump:
name: "Trigger version bump workflow"
runs-on: ubuntu-20.04
needs:
- setup
steps:
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
env:
KEYVAULT: bitwarden-prod-kv
SECRET: "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
env:
TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
VERSION: ${{ needs.setup.outputs.version_number}}
run: |
JSON_STRING=$(printf '{"ref":"master", "inputs": { "version_number":"%s"}}' "$VERSION")
curl \
-X POST \
-i -u bitwarden-devops-bot:$TOKEN \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/bitwarden/mobile/actions/workflows/version-bump.yml/dispatches \
-d $JSON_STRING

View File

@@ -19,6 +19,12 @@ jobs:
- name: Create Version Branch
run: |
git switch -c version_bump_${{ github.event.inputs.version_number }}
git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Checkout Version Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
with:
ref: version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
@@ -50,32 +56,16 @@ jobs:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS/Info.plist"
- name: Setup git
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
- name: Check if version changed
id: version-changed
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "::set-output name=changes_to_commit::TRUE"
else
echo "::set-output name=changes_to_commit::FALSE"
echo "No changes to commit!";
fi
- name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: |
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Create Version PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env:
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -1,3 +1,40 @@
# How to Contribute
Our [Contributing Guidelines](https://contributing.bitwarden.com/contributing/) are located in our [Contributing Documentation](https://contributing.bitwarden.com/). The documentation also includes recommended tooling, code style tips, and lots of other great information to get you started.
Contributions of all kinds are welcome!
Please visit our [Community Forums](https://community.bitwarden.com/) for general community discussion and the development roadmap.
Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (i10n) section below
## Contributor Agreement
Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/mobile) if you intend on contributing to any Github repository. Pull requests cannot be accepted and merged unless the author has signed the Contributor Agreement.
## Pull Request Guidelines
* commit any pull requests against the `master` branch
* include a link to your Community Forums post
# Localization (l10n)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-mobile/localized.svg)](https://crowdin.com/project/bitwarden-mobile)
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
If you are interested in helping translate the Bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit).
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/

View File

@@ -12,7 +12,16 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
# Build/Run
Please refer to the [Mobile section](https://contributing.bitwarden.com/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
**Requirements**
- [Visual Studio](https://visualstudio.microsoft.com/)
- [Xamarin](https://docs.microsoft.com/en-us/xamarin/get-started/installation/?pivots=windows)
**Run the app**
- Open the solution file in Visual Studio.
- Restore the nuget packages.
- Build and run the app.
# We're Hiring!
@@ -20,7 +29,8 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
# Contribute
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
Code contributions are welcome! Visual Studio with Xamarin is required to work on this project. Please commit any pull requests against the `master` branch.
Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
@@ -35,3 +45,11 @@ We recently migrated to using dotnet-format as code formatter. All previous bran
5. Commit
6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357`
7. Push
#### Git blame
We also recommend that you configure git to ignore the prettier revision using:
```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```

View File

@@ -1,22 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"schedule:monthly",
":maintainLockFilesMonthly",
":preserveSemverRanges",
":rebaseStalePrs",
":disableDependencyDashboard"
],
"enabledManagers": [
"nuget"
],
"packageRules": [
{
"matchManagers": ["nuget"],
"groupName": "Nuget updates",
"groupSlug": "nuget",
"separateMajorMinor": false
}
]
}

View File

@@ -54,7 +54,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.google.android.apps.chrome", "url_bar"),
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
// Rem. for "com.google.android.captiveportallogin": URL displayed in ActionBar subtitle without viewId.
new Browser("com.iode.firefox", "mozac_browser_toolbar_url_view"),
new Browser("com.jamal2367.styx", "search"),
new Browser("com.kiwibrowser.browser", "url_bar"),
new Browser("com.kiwibrowser.browser.dev", "url_bar"),
@@ -68,7 +67,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.naver.whale", "url_bar"),
new Browser("com.opera.browser", "url_field"),
new Browser("com.opera.browser.beta", "url_field"),
new Browser("com.opera.gx", "addressbarEdit"),
new Browser("com.opera.mini.native", "url_field"),
new Browser("com.opera.mini.native.beta", "url_field"),
new Browser("com.opera.touch", "addressbarEdit"),

View File

@@ -151,7 +151,6 @@
<Compile Include="Services\ClipboardService.cs" />
<Compile Include="Utilities\IntentExtensions.cs" />
<Compile Include="Renderers\CustomPageRenderer.cs" />
<Compile Include="Effects\NoEmojiKeyboardEffect.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" />

View File

@@ -73,7 +73,6 @@ namespace Bit.Droid.Autofill
"com.google.android.apps.chrome",
"com.google.android.apps.chrome_dev",
"com.google.android.captiveportallogin",
"com.iode.firefox",
"com.jamal2367.styx",
"com.kiwibrowser.browser",
"com.kiwibrowser.browser.dev",
@@ -87,7 +86,6 @@ namespace Bit.Droid.Autofill
"com.naver.whale",
"com.opera.browser",
"com.opera.browser.beta",
"com.opera.gx",
"com.opera.mini.native",
"com.opera.mini.native.beta",
"com.opera.touch",

View File

@@ -1,24 +0,0 @@
using Android.Widget;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportEffect(typeof(NoEmojiKeyboardEffect), nameof(NoEmojiKeyboardEffect))]
namespace Bit.Droid.Effects
{
public class NoEmojiKeyboardEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is EditText editText)
{
editText.InputType = Android.Text.InputTypes.ClassText | Android.Text.InputTypes.TextVariationVisiblePassword | Android.Text.InputTypes.TextFlagMultiLine;
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -64,11 +64,10 @@ namespace Bit.Droid
Intent?.Validate();
base.OnCreate(savedInstanceState);
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
if (!CoreHelpers.InDebugMode())
{
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
});
}
ServiceContainer.Resolve<ILogger>("logger").InitAsync();

View File

@@ -20,7 +20,6 @@ using System.Net;
using Bit.App.Utilities;
using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
using Bit.App.Controls;
#if !FDROID
using Android.Gms.Security;
#endif
@@ -70,8 +69,7 @@ namespace Bit.Droid
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
ServiceContainer.Resolve<IStateService>("stateService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"),
ServiceContainer.Resolve<ILogger>("logger"));
ServiceContainer.Resolve<IAuthService>("authService"));
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
}
#if !FDROID
@@ -101,13 +99,12 @@ namespace Bit.Droid
{
ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
#if FDROID
var logger = new StubLogger();
ServiceContainer.Register<ILogger>("logger", new StubLogger());
#elif DEBUG
var logger = DebugLogger.Instance;
ServiceContainer.Register<ILogger>("logger", DebugLogger.Instance);
#else
var logger = Logger.Instance;
ServiceContainer.Register<ILogger>("logger", Logger.Instance);
#endif
ServiceContainer.Register("logger", logger);
// Note: This might cause a race condition. Investigate more.
Task.Run(() =>
@@ -127,7 +124,7 @@ namespace Bit.Droid
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
var localizeService = new LocalizeService();
var broadcasterService = new BroadcasterService(logger);
var broadcasterService = new BroadcasterService();
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
var secureStorageService = new SecureStorageService();
@@ -162,7 +159,6 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
// Push
#if FDROID

View File

@@ -1,49 +1,57 @@
<?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.9.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<application android:label="Bitwarden" android:theme="@style/LaunchTheme" android:allowBackup="false" tools:replace="android:allowBackup" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.x8bit.bitwarden.fileprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
</provider>
<meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
<!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) -->
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true" />
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true" />
<!-- Support for LG "Dual Window" mode (for Android < 7.0 users) -->
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true" />
<!-- Declare MainActivity manually so we can set LaunchMode using API dependant resource -->
<activity android:name="com.x8bit.bitwarden.MainActivity" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="Bitwarden" android:launchMode="@integer/launchModeAPIlevel" android:theme="@style/LaunchTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/*" />
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
<data android:mimeType="text/*" />
</intent-filter>
</activity>
</application>
<!-- Package visibility (for Android 11+) -->
<queries>
<intent>
<action android:name="*" />
</intent>
</queries>
</manifest>
<?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.6.2" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<application android:label="Bitwarden" android:theme="@style/LaunchTheme" android:allowBackup="false" tools:replace="android:allowBackup" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.x8bit.bitwarden.fileprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
</provider>
<meta-data android:name="android.max_aspect" android:value="2.1"/>
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions"/>
<!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) -->
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true"/>
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true"/>
<!-- Support for LG "Dual Window" mode (for Android < 7.0 users) -->
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true"/>
<!-- Declare MainActivity manually so we can set LaunchMode using API dependant resource -->
<activity android:name="com.x8bit.bitwarden.MainActivity" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="Bitwarden" android:launchMode="@integer/launchModeAPIlevel" android:theme="@style/LaunchTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/*"/>
<data android:mimeType="image/*"/>
<data android:mimeType="video/*"/>
<data android:mimeType="text/*"/>
</intent-filter>
</activity>
</application>
<!-- Package visibility (for Android 11+) -->
<queries>
<intent>
<action android:name="*"/>
</intent>
</queries>
</manifest>

View File

@@ -77,9 +77,6 @@
<compatibility-package
android:name="com.google.android.captiveportallogin"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.iode.firefox"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.jamal2367.styx"
android:maxLongVersionCode="10000000000"/>
@@ -119,9 +116,6 @@
<compatibility-package
android:name="com.opera.browser.beta"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.opera.gx"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.opera.mini.native"
android:maxLongVersionCode="10000000000"/>

View File

@@ -28,25 +28,17 @@ namespace Bit.Droid.Services
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
{
try
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
if ((int)Build.VERSION.SdkInt < 33)
{
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
if ((int)Build.VERSION.SdkInt < 33)
{
await Clipboard.SetTextAsync(text);
}
else
{
CopyToClipboard(text, isSensitive);
}
await Clipboard.SetTextAsync(text);
}
else
{
CopyToClipboard(text, isSensitive);
}
await ClearClipboardAlarmAsync(expiresInMs);
}
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
{
// #1962 Just ignore, the content is copied either way but there is some app interfiering in the process
// that the OS catches and just throws this exception.
}
await ClearClipboardAlarmAsync(expiresInMs);
}
public bool IsCopyNotificationHandledByPlatform()

View File

@@ -948,21 +948,5 @@ namespace Bit.Droid.Services
{
// for any Android-specific cleanup required after switching accounts
}
public async Task SetScreenCaptureAllowedAsync()
{
if (CoreHelpers.ForceScreenCaptureEnabled())
{
return;
}
var activity = CrossCurrentActivity.Current?.Activity;
if (await _stateService.GetScreenCaptureAllowedAsync())
{
activity.RunOnUiThread(() => activity.Window.ClearFlags(WindowManagerFlags.Secure));
return;
}
activity.RunOnUiThread(() => activity.Window.AddFlags(WindowManagerFlags.Secure));
}
}
}

View File

@@ -59,10 +59,10 @@ namespace Bit.Droid.Utilities
{
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
{
theme = ThemeManager.Dark;
theme = "dark";
}
if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
if (theme == "dark" || theme == "black" || theme == "nord")
{
LightTheme = false;
}

View File

@@ -8,6 +8,5 @@ namespace Bit.App.Abstractions
{
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
Task LogOutAsync(string userId, bool userInitiated, bool expired);
}
}

View File

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

View File

@@ -97,11 +97,11 @@
<Compile Update="Pages\Vault\PasswordHistoryPage.xaml.cs">
<DependentUpon>PasswordHistoryPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Vault\CipherDetailsPage.xaml.cs">
<DependentUpon>CipherDetailsPage.xaml</DependentUpon>
<Compile Update="Pages\Vault\AddEditPage.xaml.cs">
<DependentUpon>AddEditPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Vault\CipherAddEditPage.xaml.cs">
<DependentUpon>CipherAddEditPage.xaml</DependentUpon>
<Compile Update="Pages\Vault\ViewPage.xaml.cs">
<DependentUpon>ViewPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Settings\SettingsPage\SettingsPage.xaml.cs">
<DependentUpon>SettingsPage.xaml</DependentUpon>
@@ -129,10 +129,12 @@
<Folder Include="Behaviors\" />
<Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" />
<Folder Include="Controls\DateTime\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
</ItemGroup>
@@ -160,6 +162,12 @@
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Styles\Base.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\AppResources.cs.Designer.cs">
<DependentUpon>AppResources.cs.resx</DependentUpon>
@@ -414,6 +422,5 @@
<None Remove="Xamarin.CommunityToolkit" />
<None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" />
<None Remove="Controls\DateTime\" />
</ItemGroup>
</Project>

View File

@@ -10,7 +10,6 @@ using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@@ -57,93 +56,86 @@ namespace Bit.App
Bootstrap();
_broadcasterService.Subscribe(nameof(App), async (message) =>
{
try
if (message.Command == "showDialog")
{
if (message.Command == "showDialog")
var details = message.Data as DialogDetails;
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
Device.BeginInvokeOnMainThread(async () =>
{
var details = message.Data as DialogDetails;
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
Device.BeginInvokeOnMainThread(async () =>
if (!string.IsNullOrWhiteSpace(details.CancelText))
{
if (!string.IsNullOrWhiteSpace(details.CancelText))
{
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
else
{
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
else if (message.Command == "resumed")
{
if (Device.RuntimePlatform == Device.iOS)
{
ResumedAsync().FireAndForget();
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
}
else if (message.Command == "slept")
{
if (Device.RuntimePlatform == Device.iOS)
else
{
await SleptAsync();
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}
}
else if (message.Command == "migrated")
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
else if (message.Command == "resumed")
{
if (Device.RuntimePlatform == Device.iOS)
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend" ||
message.Command == "popAllAndGoToAutofillCiphers")
{
Device.BeginInvokeOnMainThread(async () =>
{
if (Current.MainPage is TabsPage tabsPage)
{
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == "popAllAndGoToAutofillCiphers")
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (message.Command == "popAllAndGoToTabMyVault")
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == "popAllAndGoToTabGenerator")
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
ResumedAsync().FireAndForget();
}
}
catch (Exception ex)
else if (message.Command == "slept")
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
if (Device.RuntimePlatform == Device.iOS)
{
await SleptAsync();
}
}
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend" ||
message.Command == "popAllAndGoToAutofillCiphers")
{
Device.BeginInvokeOnMainThread(async () =>
{
if (Current.MainPage is TabsPage tabsPage)
{
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == "popAllAndGoToAutofillCiphers")
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (message.Command == "popAllAndGoToTabMyVault")
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == "popAllAndGoToTabGenerator")
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
});
}
@@ -301,7 +293,7 @@ namespace Bit.App
UpdateThemeAsync();
};
Current.MainPage = new NavigationPage(new HomePage(Options));
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
}
@@ -330,20 +322,20 @@ namespace Bit.App
var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1];
if (topPage is NavigationPage navPage)
{
if (navPage.CurrentPage is CipherDetailsPage cipherDetailsPage)
if (navPage.CurrentPage is ViewPage viewPage)
{
lastPageBeforeLock = new PreviousPageInfo
{
Page = "view",
CipherId = cipherDetailsPage.ViewModel.CipherId
CipherId = viewPage.ViewModel.CipherId
};
}
else if (navPage.CurrentPage is CipherAddEditPage cipherAddEditPage && cipherAddEditPage.ViewModel.EditMode)
else if (navPage.CurrentPage is AddEditPage addEditPage && addEditPage.ViewModel.EditMode)
{
lastPageBeforeLock = new PreviousPageInfo
{
Page = "edit",
CipherId = cipherAddEditPage.ViewModel.CipherId
CipherId = addEditPage.ViewModel.CipherId
};
}
}
@@ -378,7 +370,7 @@ namespace Bit.App
Current.MainPage = new TabsPage(Options);
break;
case NavigationTarget.AddEditCipher:
Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
break;
case NavigationTarget.AutofillCiphers:
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));

View File

@@ -13,8 +13,7 @@ namespace Bit.App.Controls
public AccountViewCellViewModel(AccountView accountView)
{
AccountView = accountView;
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
?.GetOrCreateAvatar(AccountView.Name, AccountView.Email);
AvatarImageSource = new AvatarImageSource(AccountView.Name, AccountView.Email);
}
public AccountView AccountView

View File

@@ -1,127 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.AuthenticatorViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
StyleClass="list-row, list-row-platform"
HorizontalOptions="FillAndExpand"
x:DataType="pages:GroupingsPageTOTPListItem"
ColumnDefinitions="40,*,40,Auto,40"
RowSpacing="0"
Padding="0,10,0,0"
RowDefinitions="*,*">
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
<u:InverseBoolConverter x:Key="inverseBool" />
</Grid.Resources>
<controls:IconLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Grid.RowSpan="2"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage
Grid.Column="0"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="22"
HeightRequest="22"
Grid.RowSpan="2"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="1"
Grid.Row="0"
VerticalTextAlignment="Center"
VerticalOptions="Fill"
StyleClass="list-title, list-title-platform"
Text="{Binding Cipher.Name}" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="1"
Grid.Row="1"
VerticalTextAlignment="Center"
VerticalOptions="Fill"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}" />
<controls:CircularProgressbarView
Progress="{Binding Progress}"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2"
HorizontalOptions="Fill"
VerticalOptions="CenterAndExpand" />
<Label
Text="{Binding TotpSec, Mode=OneWay}"
Style="{DynamicResource textTotp}"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2"
StyleClass="text-sm"
HorizontalTextAlignment="Center"
HorizontalOptions="Fill"
VerticalTextAlignment="Center"
VerticalOptions="Fill" />
<StackLayout
Grid.Row="0"
Grid.Column="3"
Margin="3,0,2,0"
Spacing="5"
Grid.RowSpan="2"
Orientation="Horizontal"
HorizontalOptions="Fill"
VerticalOptions="Fill">
<controls:MonoLabel
Text="{Binding TotpCodeFormattedStart, Mode=OneWay}"
Style="{DynamicResource textTotp}"
StyleClass="text-lg"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HorizontalOptions="Center"
VerticalOptions="FillAndExpand" />
<controls:MonoLabel
Text="{Binding TotpCodeFormattedEnd, Mode=OneWay}"
Style="{DynamicResource textTotp}"
StyleClass="text-lg"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HorizontalOptions="Center"
VerticalOptions="FillAndExpand" />
</StackLayout>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
CommandParameter="LoginTotp"
Grid.Row="0"
Grid.Column="4"
Grid.RowSpan="2"
Padding="0,0,1,0"
HorizontalOptions="Center"
VerticalOptions="Center"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyTotp}" />
</controls:ExtendedGrid>

View File

@@ -1,67 +0,0 @@
using System;
using Bit.App.Pages;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class AuthenticatorViewCell : ExtendedGrid
{
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
nameof(Cipher), typeof(CipherView), typeof(AuthenticatorViewCell), default(CipherView), BindingMode.TwoWay);
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(AuthenticatorViewCell));
public static readonly BindableProperty TotpSecProperty = BindableProperty.Create(
nameof(TotpSec), typeof(long), typeof(AuthenticatorViewCell));
public AuthenticatorViewCell()
{
InitializeComponent();
}
public Command CopyCommand { get; set; }
public CipherView Cipher
{
get => GetValue(CipherProperty) as CipherView;
set => SetValue(CipherProperty, value);
}
public bool? WebsiteIconsEnabled
{
get => (bool)GetValue(WebsiteIconsEnabledProperty);
set => SetValue(WebsiteIconsEnabledProperty, value);
}
public long TotpSec
{
get => (long)GetValue(TotpSecProperty);
set => SetValue(TotpSecProperty, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled ?? false
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
private string _iconImageSource = string.Empty;
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SkiaSharp;
@@ -51,7 +50,7 @@ namespace Bit.App.Controls
private Stream Draw()
{
string chars;
string chars = null;
string upperData = null;
if (string.IsNullOrEmpty(_data))
@@ -72,83 +71,62 @@ namespace Bit.App.Controls
var textColor = Color.White;
var size = 50;
using (var bitmap = new SKBitmap(size * 2,
var bitmap = new SKBitmap(
size * 2,
size * 2,
SKImageInfo.PlatformColorType,
SKAlphaType.Premul))
SKAlphaType.Premul);
var canvas = new SKCanvas(bitmap);
canvas.Clear(SKColors.Transparent);
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
var radius = midX - midX / 5;
var circlePaint = new SKPaint
{
using (var canvas = new SKCanvas(bitmap))
{
canvas.Clear(SKColors.Transparent);
using (var paint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor.ToHex())
})
{
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
var midY = canvas.LocalClipBounds.Size.ToSizeI().Height / 2;
var radius = midX - midX / 5;
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor.ToHex())
};
canvas.DrawCircle(midX, midY, radius, circlePaint);
using (var circlePaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor.ToHex())
})
{
canvas.DrawCircle(midX, midY, radius, circlePaint);
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
var textSize = midX / 1.3f;
var textPaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse(textColor.ToHex()),
TextSize = textSize,
TextAlign = SKTextAlign.Center,
Typeface = typeface
};
var rect = new SKRect();
textPaint.MeasureText(chars, ref rect);
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
var textSize = midX / 1.3f;
using (var textPaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse(textColor.ToHex()),
TextSize = textSize,
TextAlign = SKTextAlign.Center,
Typeface = typeface
})
{
var rect = new SKRect();
textPaint.MeasureText(chars, ref rect);
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
using (var img = SKImage.FromBitmap(bitmap))
{
var data = img.Encode(SKEncodedImageFormat.Png, 100);
return data?.AsStream(true);
}
}
}
}
}
}
return SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100).AsStream();
}
private string GetFirstLetters(string data, int charCount)
{
var sanitizedData = data.Trim();
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var parts = data.Split();
if (parts.Length > 1 && charCount <= 2)
{
var text = string.Empty;
for (var i = 0; i < charCount; i++)
var text = "";
for (int i = 0; i < charCount; i++)
{
text += parts[i][0];
text += parts[i].Substring(0, 1);
}
return text;
}
if (sanitizedData.Length > 2)
if (data.Length > 2)
{
return sanitizedData.Substring(0, 2);
return data.Substring(0, 2);
}
return sanitizedData;
return data;
}
private Color StringToColor(string str)

View File

@@ -1,33 +0,0 @@
using System;
using System.Collections.Concurrent;
namespace Bit.App.Controls
{
public interface IAvatarImageSourcePool
{
AvatarImageSource GetOrCreateAvatar(string name, string email);
}
public class AvatarImageSourcePool : IAvatarImageSourcePool
{
private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();
public AvatarImageSource GetOrCreateAvatar(string name, string email)
{
var key = $"{name}{email}";
if (!_cache.TryGetValue(key, out var avatar))
{
avatar = new AvatarImageSource(name, email);
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.
{
// if add and get after fails, then something wrong is going on with this method.
throw new InvalidOperationException("Something is wrong creating the avatar image");
}
}
return avatar;
}
}
}

View File

@@ -1,139 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class CircularProgressbarView : SKCanvasView
{
private Circle _circle;
public static readonly BindableProperty ProgressProperty = BindableProperty.Create(
nameof(Progress), typeof(double), typeof(CircularProgressbarView), propertyChanged: OnProgressChanged);
public static readonly BindableProperty RadiusProperty = BindableProperty.Create(
nameof(Radius), typeof(float), typeof(CircularProgressbarView), 15f);
public static readonly BindableProperty StrokeWidthProperty = BindableProperty.Create(
nameof(StrokeWidth), typeof(float), typeof(CircularProgressbarView), 3f);
public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(
nameof(ProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public static readonly BindableProperty EndingProgressColorProperty = BindableProperty.Create(
nameof(EndingProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public static readonly BindableProperty BackgroundProgressColorProperty = BindableProperty.Create(
nameof(BackgroundProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public double Progress
{
get { return (double)GetValue(ProgressProperty); }
set { SetValue(ProgressProperty, value); }
}
public float Radius
{
get => (float)GetValue(RadiusProperty);
set => SetValue(RadiusProperty, value);
}
public float StrokeWidth
{
get => (float)GetValue(StrokeWidthProperty);
set => SetValue(StrokeWidthProperty, value);
}
public Color ProgressColor
{
get => (Color)GetValue(ProgressColorProperty);
set => SetValue(ProgressColorProperty, value);
}
public Color EndingProgressColor
{
get => (Color)GetValue(EndingProgressColorProperty);
set => SetValue(EndingProgressColorProperty, value);
}
public Color BackgroundProgressColor
{
get => (Color)GetValue(BackgroundProgressColorProperty);
set => SetValue(BackgroundProgressColorProperty, value);
}
private static void OnProgressChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var context = bindable as CircularProgressbarView;
context.InvalidateSurface();
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(Progress))
{
_circle = new Circle(Radius * (float)DeviceDisplay.MainDisplayInfo.Density, (info) => new SKPoint((float)info.Width / 2, (float)info.Height / 2));
}
}
protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
base.OnPaintSurface(e);
if (_circle != null)
{
_circle.CalculateCenter(e.Info);
e.Surface.Canvas.Clear();
DrawCircle(e.Surface.Canvas, _circle, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, BackgroundProgressColor.ToSKColor());
DrawArc(e.Surface.Canvas, _circle, () => (float)Progress, StrokeWidth * (float)DeviceDisplay.MainDisplayInfo.Density, ProgressColor.ToSKColor(), EndingProgressColor.ToSKColor());
}
}
private void DrawCircle(SKCanvas canvas, Circle circle, float strokewidth, SKColor color)
{
canvas.DrawCircle(circle.Center, circle.Redius,
new SKPaint()
{
StrokeWidth = strokewidth,
Color = color,
IsStroke = true,
IsAntialias = true
});
}
private void DrawArc(SKCanvas canvas, Circle circle, Func<float> progress, float strokewidth, SKColor color, SKColor progressEndColor)
{
var progressValue = progress();
var angle = progressValue * 3.6f;
canvas.DrawArc(circle.Rect, 270, angle, false,
new SKPaint()
{
StrokeWidth = strokewidth,
Color = progressValue < 20f ? progressEndColor : color,
IsStroke = true,
IsAntialias = true
});
}
}
public class Circle
{
private readonly Func<SKImageInfo, SKPoint> _centerFunc;
public Circle(float redius, Func<SKImageInfo, SKPoint> centerFunc)
{
_centerFunc = centerFunc;
Redius = redius;
}
public SKPoint Center { get; set; }
public float Redius { get; set; }
public SKRect Rect => new SKRect(Center.X - Redius, Center.Y - Redius, Center.X + Redius, Center.Y + Redius);
public void CalculateCenter(SKImageInfo argsInfo)
{
Center = _centerFunc(argsInfo);
}
}
}

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Grid
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:Class="Bit.App.Controls.DateTimePicker"
ColumnDefinitions="*,*">
<controls:ExtendedDatePicker
x:Name="_datePicker"
Grid.Column="0"
NullableDate="{Binding Date, Mode=TwoWay}"
Format="d"
AutomationProperties.IsInAccessibleTree="True" />
<controls:ExtendedTimePicker
x:Name="_timePicker"
Grid.Column="1"
NullableTime="{Binding Time, Mode=TwoWay}"
Format="t"
AutomationProperties.IsInAccessibleTree="True" />
</Grid>

View File

@@ -1,34 +0,0 @@
using System.Runtime.CompilerServices;
using Xamarin.CommunityToolkit.UI.Views;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class DateTimePicker : Grid
{
public DateTimePicker()
{
InitializeComponent();
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(BindingContext)
&&
BindingContext is DateTimeViewModel dateTimeViewModel)
{
AutomationProperties.SetName(_datePicker, dateTimeViewModel.DateName);
AutomationProperties.SetName(_timePicker, dateTimeViewModel.TimeName);
_datePicker.PlaceHolder = dateTimeViewModel.DatePlaceholder;
_timePicker.PlaceHolder = dateTimeViewModel.TimePlaceholder;
}
}
}
public class LazyDateTimePicker : LazyView<DateTimePicker>
{
}
}

View File

@@ -1,70 +0,0 @@
using System;
using Bit.Core.Utilities;
namespace Bit.App.Controls
{
public class DateTimeViewModel : ExtendedViewModel
{
DateTime? _date;
TimeSpan? _time;
public DateTimeViewModel(string dateName, string timeName)
{
DateName = dateName;
TimeName = timeName;
}
public Action<DateTime?> OnDateChanged { get; set; }
public Action<TimeSpan?> OnTimeChanged { get; set; }
public DateTime? Date
{
get => _date;
set
{
if (SetProperty(ref _date, value))
{
OnDateChanged?.Invoke(value);
}
}
}
public TimeSpan? Time
{
get => _time;
set
{
if (SetProperty(ref _time, value))
{
OnTimeChanged?.Invoke(value);
}
}
}
public string DateName { get; }
public string TimeName { get; }
public string DatePlaceholder { get; set; }
public string TimePlaceholder { get; set; }
public DateTime? DateTime
{
get
{
if (Date.HasValue)
{
if (Time.HasValue)
{
return Date.Value.Add(Time.Value);
}
return Date;
}
return null;
}
set
{
Date = value?.Date;
Time = value?.Date.TimeOfDay;
}
}
}
}

View File

@@ -1,12 +0,0 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Effects
{
public class NoEmojiKeyboardEffect : RoutingEffect
{
public NoEmojiKeyboardEffect()
: base("Bitwarden.NoEmojiKeyboardEffect")
{ }
}
}

View File

@@ -14,7 +14,7 @@
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Save}" Command="{Binding SubmitCommand}" />
<ToolbarItem Text="{u:I18n Save}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>
<ScrollView>

View File

@@ -36,6 +36,14 @@ namespace Bit.App.Pages
};
}
private async void Submit_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.SubmitAsync();
}
}
private async Task SubmitSuccessAsync()
{
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);

View File

@@ -1,17 +1,15 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class EnvironmentPageViewModel : BaseViewModel
{
private readonly IEnvironmentService _environmentService;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public EnvironmentPageViewModel()
{
@@ -24,10 +22,10 @@ namespace Bit.App.Pages
IdentityUrl = _environmentService.IdentityUrl;
IconsUrl = _environmentService.IconsUrl;
NotificationsUrls = _environmentService.NotificationsUrl;
SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
SubmitCommand = new Command(async () => await SubmitAsync());
}
public ICommand SubmitCommand { get; }
public Command SubmitCommand { get; }
public string BaseUrl { get; set; }
public string ApiUrl { get; set; }
public string IdentityUrl { get; set; }
@@ -39,12 +37,6 @@ namespace Bit.App.Pages
public async Task SubmitAsync()
{
if (!ValidateUrls())
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.EnvironmentPageUrlsError, AppResources.Ok);
return;
}
var resUrls = await _environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
{
Base = BaseUrl,
@@ -65,25 +57,5 @@ namespace Bit.App.Pages
SubmitSuccessAction?.Invoke();
}
public bool ValidateUrls()
{
bool IsUrlValid(string url)
{
return string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute);
}
return IsUrlValid(BaseUrl)
&& IsUrlValid(ApiUrl)
&& IsUrlValid(IdentityUrl)
&& IsUrlValid(WebVaultUrl)
&& IsUrlValid(IconsUrl);
}
private void OnSubmitException(Exception ex)
{
_logger.Value.Exception(ex);
Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
}
}
}

View File

@@ -14,7 +14,7 @@
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Submit}" Command="{Binding SubmitCommand}" />
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>
<ScrollView>

View File

@@ -1,4 +1,5 @@
using Xamarin.Forms;
using System;
using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -23,6 +24,14 @@ namespace Bit.App.Pages
RequestFocus(_email);
}
private async void Submit_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.SubmitAsync();
}
}
private async void Close_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())

View File

@@ -1,11 +1,10 @@
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -14,26 +13,18 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IApiService _apiService;
private readonly ILogger _logger;
public HintPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_logger = ServiceContainer.Resolve<ILogger>();
PageTitle = AppResources.PasswordHint;
SubmitCommand = new AsyncCommand(SubmitAsync,
onException: ex =>
{
_logger.Exception(ex);
_deviceActionService.DisplayAlertAsync(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok).FireAndForget();
},
allowsMultipleExecutions: false);
SubmitCommand = new Command(async () => await SubmitAsync());
}
public ICommand SubmitCommand { get; }
public Command SubmitCommand { get; }
public string Email { get; set; }
public async Task SubmitAsync()
@@ -46,14 +37,14 @@ namespace Bit.App.Pages
}
if (string.IsNullOrWhiteSpace(Email))
{
await _deviceActionService.DisplayAlertAsync(AppResources.AnErrorHasOccurred,
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress),
AppResources.Ok);
return;
}
if (!Email.Contains("@"))
{
await _deviceActionService.DisplayAlertAsync(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok);
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.InvalidEmail, AppResources.Ok);
return;
}
@@ -63,7 +54,7 @@ namespace Bit.App.Pages
await _apiService.PostPasswordHintAsync(
new Core.Models.Request.PasswordHintRequest { Email = Email });
await _deviceActionService.HideLoadingAsync();
await _deviceActionService.DisplayAlertAsync(null, AppResources.PasswordHintAlert, AppResources.Ok);
await Page.DisplayAlert(null, AppResources.PasswordHintAlert, AppResources.Ok);
await Page.Navigation.PopModalAsync();
}
catch (ApiException e)

View File

@@ -54,7 +54,7 @@ namespace Bit.App.Pages
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
{
if (message.Command == "updatedTheme")
{

View File

@@ -25,6 +25,8 @@ namespace Bit.App.Pages
_vm = BindingContext as LockPageViewModel;
_vm.Page = this;
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
MasterPasswordEntry = _masterPassword;
PinEntry = _pin;
if (Device.RuntimePlatform == Device.iOS)
{
@@ -36,17 +38,8 @@ namespace Bit.App.Pages
}
}
public Entry SecretEntry
{
get
{
if (_vm?.PinLock ?? false)
{
return _pin;
}
return _masterPassword;
}
}
public Entry MasterPasswordEntry { get; set; }
public Entry PinEntry { get; set; }
public async Task PromptBiometricAfterResumeAsync()
{
@@ -77,12 +70,16 @@ namespace Bit.App.Pages
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
await _vm.InitAsync();
_vm.FocusSecretEntry += PerformFocusSecretEntry;
if (!_vm.BiometricLock)
{
RequestFocus(SecretEntry);
if (_vm.PinLock)
{
RequestFocus(PinEntry);
}
else
{
RequestFocus(MasterPasswordEntry);
}
}
else
{
@@ -102,18 +99,6 @@ namespace Bit.App.Pages
}
}
private void PerformFocusSecretEntry(int? cursorPosition)
{
Device.BeginInvokeOnMainThread(() =>
{
SecretEntry.Focus();
if (cursorPosition.HasValue)
{
SecretEntry.CursorPosition = cursorPosition.Value;
}
});
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)

View File

@@ -10,7 +10,6 @@ using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.Helpers;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -28,7 +27,6 @@ namespace Bit.App.Pages
private readonly IBiometricService _biometricService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly ILogger _logger;
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
private string _email;
private bool _showPassword;
@@ -135,11 +133,6 @@ namespace Bit.App.Pages
public string MasterPassword { get; set; }
public string Pin { get; set; }
public Action UnlockedAction { get; set; }
public event Action<int?> FocusSecretEntry
{
add => _secretEntryFocusWeakEventManager.AddEventHandler(value);
remove => _secretEntryFocusWeakEventManager.RemoveEventHandler(value);
}
public async Task InitAsync()
{
@@ -353,8 +346,11 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
var secret = PinLock ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
var page = (Page as LockPage);
var entry = PinLock ? page.PinEntry : page.MasterPasswordEntry;
var str = PinLock ? Pin : MasterPassword;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(str) ? 0 : str.Length;
}
public async Task PromptBiometricAsync()
@@ -365,8 +361,18 @@ namespace Bit.App.Pages
return;
}
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
PinLock ? AppResources.PIN : AppResources.MasterPassword, () =>
{
var page = Page as LockPage;
if (PinLock)
{
page.PinEntry.Focus();
}
else
{
page.MasterPasswordEntry.Focus();
}
});
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{

View File

@@ -55,7 +55,6 @@
<Entry
x:Name="_email"
Text="{Binding Email}"
IsEnabled="{Binding IsEmailEnabled}"
Keyboard="Email"
StyleClass="box-value">
<VisualStateManager.VisualStateGroups>

View File

@@ -1,8 +1,8 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -15,8 +15,6 @@ namespace Bit.App.Pages
private bool _inputFocused;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public LoginPage(string email = null, AppOptions appOptions = null)
{
_appOptions = appOptions;
@@ -32,10 +30,11 @@ namespace Bit.App.Pages
await _accountListOverlay.HideAsync();
await Navigation.PopModalAsync();
};
_vm.IsEmailEnabled = string.IsNullOrWhiteSpace(email);
_vm.IsIosExtension = _appOptions?.IosExtension ?? false;
if (_vm.IsEmailEnabled)
if (!string.IsNullOrWhiteSpace(email))
{
_email.IsEnabled = false;
}
else
{
_vm.ShowCancelButton = true;
}
@@ -54,7 +53,7 @@ namespace Bit.App.Pages
ToolbarItems.Add(_getPasswordHint);
}
if (Device.RuntimePlatform == Device.Android && !_vm.IsEmailEnabled)
if (Device.RuntimePlatform == Device.Android && !_email.IsEnabled)
{
ToolbarItems.Add(_removeAccount);
}
@@ -111,7 +110,7 @@ namespace Bit.App.Pages
{
if (DoOnce())
{
await _vm.LogInAsync(true, _vm.IsEmailEnabled);
await _vm.LogInAsync(true, _email.IsEnabled);
}
}
@@ -140,16 +139,26 @@ namespace Bit.App.Pages
}
}
private async void More_Clicked(object sender, EventArgs e)
private async void More_Clicked(object sender, System.EventArgs e)
{
try
await _accountListOverlay.HideAsync();
if (!DoOnce())
{
await _accountListOverlay.HideAsync();
_vm.MoreCommand.Execute(null);
return;
}
catch (Exception ex)
var buttons = _email.IsEnabled ? new[] { AppResources.GetPasswordHint }
: new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount };
var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, buttons);
if (selection == AppResources.GetPasswordHint)
{
_logger.Value.Exception(ex);
await Navigation.PushModalAsync(new NavigationPage(new HintPage()));
}
else if (selection == AppResources.RemoveAccount)
{
await _vm.RemoveAccountAsync();
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
@@ -9,7 +8,6 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -30,7 +28,6 @@ namespace Bit.App.Pages
private bool _showCancelButton;
private string _email;
private string _masterPassword;
private bool _isEmailEnabled;
public LoginPageViewModel()
{
@@ -47,7 +44,6 @@ namespace Bit.App.Pages
PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword);
LogInCommand = new Command(async () => await LogInAsync());
MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
@@ -85,19 +81,10 @@ namespace Bit.App.Pages
set => SetProperty(ref _masterPassword, value);
}
public bool IsEmailEnabled
{
get => _isEmailEnabled;
set => SetProperty(ref _isEmailEnabled, value);
}
public bool IsIosExtension { get; set; }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; }
public ICommand MoreCommand { get; internal set; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public Action StartTwoFactorAction { get; set; }
@@ -214,28 +201,6 @@ namespace Bit.App.Pages
}
}
private async Task MoreAsync()
{
var buttons = IsEmailEnabled
? new[] { AppResources.GetPasswordHint }
: new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount };
var selection = await _deviceActionService.DisplayActionSheetAsync(AppResources.Options, AppResources.Cancel, null, buttons);
if (selection == AppResources.GetPasswordHint)
{
var hintNavigationPage = new NavigationPage(new HintPage());
if (IsIosExtension)
{
ThemeManager.ApplyResourcesTo(hintNavigationPage);
}
await Page.Navigation.PushModalAsync(hintNavigationPage);
}
else if (selection == AppResources.RemoveAccount)
{
await RemoveAccountAsync();
}
}
public void TogglePassword()
{
ShowPassword = !ShowPassword;

View File

@@ -69,12 +69,12 @@ namespace Bit.App.Pages
}
}
private void LogIn_Clicked(object sender, EventArgs e)
private async void LogIn_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
CopyAppOptions();
_vm.LogInCommand.Execute(null);
await _vm.LogInAsync();
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
@@ -9,15 +8,13 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LoginSsoPageViewModel : BaseViewModel
{
private const string REDIRECT_URI = "bitwarden://sso-callback";
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
@@ -26,7 +23,6 @@ namespace Bit.App.Pages
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private readonly ILogger _logger;
private string _orgIdentifier;
@@ -41,11 +37,9 @@ namespace Bit.App.Pages
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>("cryptoFunctionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.Bitwarden;
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
LogInCommand = new Command(async () => await LogInAsync());
}
public string OrgIdentifier
@@ -54,7 +48,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _orgIdentifier, value);
}
public ICommand LogInCommand { get; }
public Command LogInCommand { get; }
public Action StartTwoFactorAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; }
@@ -71,91 +65,81 @@ namespace Bit.App.Pages
public async Task LogInAsync()
{
if (Connectivity.NetworkAccess == NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return;
}
if (string.IsNullOrWhiteSpace(OrgIdentifier))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.OrgIdentifier),
AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
string ssoToken;
try
{
if (Connectivity.NetworkAccess == NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return;
}
if (string.IsNullOrWhiteSpace(OrgIdentifier))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.OrgIdentifier),
AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
var response = await _apiService.PreValidateSso(OrgIdentifier);
if (string.IsNullOrWhiteSpace(response?.Token))
{
_logger.Error(response is null ? "Login SSO Error: response is null" : "Login SSO Error: response.Token is null or whitespace");
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError);
return;
}
var ssoToken = response.Token;
var passwordOptions = new PasswordGenerationOptions(true);
passwordOptions.Length = 64;
var codeVerifier = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions);
var codeVerifierHash = await _cryptoFunctionService.HashAsync(codeVerifier, CryptoHashAlgorithm.Sha256);
var codeChallenge = CoreHelpers.Base64UrlEncode(codeVerifierHash);
var state = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions);
var url = _apiService.IdentityBaseUrl + "/connect/authorize?" +
"client_id=" + _platformUtilsService.GetClientType().GetString() + "&" +
"redirect_uri=" + Uri.EscapeDataString(REDIRECT_URI) + "&" +
"response_type=code&scope=api%20offline_access&" +
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
"code_challenge_method=S256&response_mode=query&" +
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
"ssoToken=" + Uri.EscapeDataString(ssoToken);
WebAuthenticatorResult authResult = null;
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
new Uri(REDIRECT_URI));
var code = GetResultCode(authResult, state);
if (!string.IsNullOrEmpty(code))
{
await LogIn(code, codeVerifier, OrgIdentifier);
}
else
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
AppResources.AnErrorHasOccurred);
}
ssoToken = response.Token;
}
catch (ApiException e)
{
_logger.Exception(e);
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(e?.Error?.GetSingleMessage() ?? AppResources.LoginSsoError,
await _platformUtilsService.ShowDialogAsync(
(e?.Error != null ? e.Error.GetSingleMessage() : AppResources.LoginSsoError),
AppResources.AnErrorHasOccurred);
return;
}
var passwordOptions = new PasswordGenerationOptions(true);
passwordOptions.Length = 64;
var codeVerifier = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions);
var codeVerifierHash = await _cryptoFunctionService.HashAsync(codeVerifier, CryptoHashAlgorithm.Sha256);
var codeChallenge = CoreHelpers.Base64UrlEncode(codeVerifierHash);
var state = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions);
var redirectUri = "bitwarden://sso-callback";
var url = _apiService.IdentityBaseUrl + "/connect/authorize?" +
"client_id=" + _platformUtilsService.GetClientType().GetString() + "&" +
"redirect_uri=" + Uri.EscapeDataString(redirectUri) + "&" +
"response_type=code&scope=api%20offline_access&" +
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
"code_challenge_method=S256&response_mode=query&" +
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
"ssoToken=" + Uri.EscapeDataString(ssoToken);
WebAuthenticatorResult authResult = null;
try
{
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
new Uri(redirectUri));
}
catch (TaskCanceledException)
{
// user canceled
await _deviceActionService.HideLoadingAsync();
return;
}
catch (Exception ex)
var code = GetResultCode(authResult, state);
if (!string.IsNullOrEmpty(code))
{
await LogIn(code, codeVerifier, redirectUri, OrgIdentifier);
}
else
{
_logger.Exception(ex);
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred);
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
AppResources.AnErrorHasOccurred);
}
}
@@ -174,11 +158,11 @@ namespace Bit.App.Pages
return code;
}
private async Task LogIn(string code, string codeVerifier, string orgId)
private async Task LogIn(string code, string codeVerifier, string redirectUri, string orgId)
{
try
{
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri, orgId);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
await _deviceActionService.HideLoadingAsync();

View File

@@ -132,7 +132,7 @@
IsToggled="{Binding AcceptPolicies}"
StyleClass="box-value"
HorizontalOptions="Start"
Margin="0, 0, 10, 0"/>
Margin="{Binding SwitchMargin}"/>
<Label StyleClass="box-footer-label"
HorizontalOptions="Fill">
<Label.FormattedText>

View File

@@ -61,6 +61,14 @@ namespace Bit.App.Pages
get => _acceptPolicies;
set => SetProperty(ref _acceptPolicies, value);
}
public Thickness SwitchMargin
{
get => Device.RuntimePlatform == Device.Android
? new Thickness(0, 0, 0, 0)
: new Thickness(0, 0, 10, 0);
}
public bool ShowTerms { get; set; }
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }

View File

@@ -17,19 +17,15 @@
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Name="_cancelItem" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:IsNullConverter x:Key="isNull" />
<ToolbarItem
x:Name="_moreItem"
x:Key="moreItem"
Icon="more_vert.png"
Order="Primary"
Command="{Binding MoreCommand}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
<ToolbarItem Icon="more_vert.png" Clicked="More_Clicked" Order="Primary"
x:Name="_moreItem" x:Key="moreItem"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
<ToolbarItem Text="{u:I18n UseAnotherTwoStepMethod}"
Clicked="Methods_Clicked"
Order="Secondary"

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
@@ -136,6 +137,21 @@ namespace Bit.App.Pages
}
}
private async void More_Clicked(object sender, EventArgs e)
{
if (!DoOnce())
{
return;
}
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel, null, AppResources.UseAnotherTwoStepMethod);
if (selection == AppResources.UseAnotherTwoStepMethod)
{
await _vm.AnotherMethodAsync();
}
}
private async void ResendEmail_Clicked(object sender, EventArgs e)
{
if (DoOnce())

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
@@ -13,7 +12,6 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -32,7 +30,6 @@ namespace Bit.App.Pages
private readonly IStateService _stateService;
private readonly II18nService _i18nService;
private readonly IAppIdService _appIdService;
private readonly ILogger _logger;
private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction;
@@ -54,11 +51,9 @@ namespace Bit.App.Pages
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_logger = ServiceContainer.Resolve<ILogger>();
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false);
}
public string TotpInstruction
@@ -116,7 +111,6 @@ namespace Bit.App.Pages
});
}
public Command SubmitCommand { get; }
public ICommand MoreCommand { get; }
public Action TwoFactorAuthSuccessAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; }
@@ -343,15 +337,6 @@ namespace Bit.App.Pages
}
}
private async Task MoreAsync()
{
var selection = await _deviceActionService.DisplayActionSheetAsync(AppResources.Options, AppResources.Cancel, null, AppResources.UseAnotherTwoStepMethod);
if (selection == AppResources.UseAnotherTwoStepMethod)
{
await AnotherMethodAsync();
}
}
public async Task AnotherMethodAsync()
{
var supportedProviders = _authService.GetSupportedTwoFactorProviders();

View File

@@ -6,7 +6,6 @@
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects"
x:DataType="pages:GeneratorPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
@@ -129,11 +128,7 @@
Text="{Binding WordSeparator}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
StyleClass="box-value">
<Entry.Effects>
<effects:NoEmojiKeyboardEffect />
</Entry.Effects>
</Entry>
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?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"
@@ -303,14 +303,14 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:ExtendedDatePicker
NullableDate="{Binding DeletionDateTimeViewModel.Date, Mode=TwoWay}"
NullableDate="{Binding DeletionDate, Mode=TwoWay}"
Format="d"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionDate}"
Grid.Column="0" />
<controls:ExtendedTimePicker
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
NullableTime="{Binding DeletionTime, Mode=TwoWay}"
Format="t"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True"
@@ -343,7 +343,7 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:ExtendedDatePicker
NullableDate="{Binding ExpirationDateTimeViewModel.Date, Mode=TwoWay}"
NullableDate="{Binding ExpirationDate, Mode=TwoWay}"
PlaceHolder="mm/dd/yyyy"
Format="d"
IsEnabled="{Binding SendEnabled}"
@@ -351,7 +351,7 @@
AutomationProperties.Name="{u:I18n ExpirationDate}"
Grid.Column="0" />
<controls:ExtendedTimePicker
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
NullableTime="{Binding ExpirationTime, Mode=TwoWay}"
PlaceHolder="--:-- --"
Format="t"
IsEnabled="{Binding SendEnabled}"

View File

@@ -23,6 +23,7 @@ namespace Bit.App.Pages
private AppOptions _appOptions;
private SendAddEditPageViewModel _vm;
public Action OnClose { get; set; }
public Action AfterSubmit { get; set; }
public SendAddEditPage(
@@ -135,7 +136,14 @@ namespace Bit.App.Pages
private async Task CloseAsync()
{
await Navigation.PopModalAsync();
if (OnClose is null)
{
await Navigation.PopModalAsync();
}
else
{
OnClose();
}
}
protected override bool OnBackButtonPressed()

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
@@ -24,7 +23,7 @@ namespace Bit.App.Pages
private readonly IStateService _stateService;
private readonly ISendService _sendService;
private readonly ILogger _logger;
private bool _sendEnabled = true;
private bool _sendEnabled;
private bool _canAccessPremium;
private bool _emailVerified;
private SendView _send;
@@ -34,7 +33,11 @@ namespace Bit.App.Pages
private int _deletionDateTypeSelectedIndex;
private int _expirationDateTypeSelectedIndex;
private DateTime _simpleDeletionDateTime;
private DateTime _deletionDate;
private TimeSpan _deletionTime;
private DateTime? _simpleExpirationDateTime;
private DateTime? _expirationDate;
private TimeSpan? _expirationTime;
private bool _isOverridingPickers;
private int? _maxAccessCount;
private string[] _additionalSendProperties = new[]
@@ -86,34 +89,8 @@ namespace Bit.App.Pages
new KeyValuePair<string, string>(AppResources.ThirtyDays, AppResources.ThirtyDays),
new KeyValuePair<string, string>(AppResources.Custom, AppResources.Custom),
};
DeletionDateTimeViewModel = new DateTimeViewModel(AppResources.DeletionDate, AppResources.DeletionTime);
ExpirationDateTimeViewModel = new DateTimeViewModel(AppResources.ExpirationDate, AppResources.ExpirationTime)
{
OnDateChanged = date =>
{
if (!_isOverridingPickers && !ExpirationDateTimeViewModel.Time.HasValue)
{
// auto-set time to current time upon setting date
ExpirationDateTimeViewModel.Time = DateTimeNow().TimeOfDay;
}
},
OnTimeChanged = time =>
{
if (!_isOverridingPickers && !ExpirationDateTimeViewModel.Date.HasValue)
{
// auto-set date to current date upon setting time
ExpirationDateTimeViewModel.Date = DateTime.Today;
}
},
DatePlaceholder = "mm/dd/yyyy",
TimePlaceholder = "--:-- --"
};
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command TogglePasswordCommand { get; set; }
public Command ToggleOptionsCommand { get; set; }
public string SendId { get; set; }
@@ -149,14 +126,23 @@ namespace Bit.App.Pages
}
}
}
public DateTime DeletionDate
{
get => _deletionDate;
set => SetProperty(ref _deletionDate, value);
}
public TimeSpan DeletionTime
{
get => _deletionTime;
set => SetProperty(ref _deletionTime, value);
}
public bool ShowOptions
{
get => _showOptions;
set => SetProperty(ref _showOptions, value,
additionalPropertyNames: new[]
{
nameof(OptionsAccessilibityText),
nameof(OptionsShowHideIcon)
nameof(OptionsAccessilibityText)
});
}
public int ExpirationDateTypeSelectedIndex
@@ -170,7 +156,28 @@ namespace Bit.App.Pages
}
}
}
public DateTime? ExpirationDate
{
get => _expirationDate;
set
{
if (SetProperty(ref _expirationDate, value))
{
ExpirationDateChanged();
}
}
}
public TimeSpan? ExpirationTime
{
get => _expirationTime;
set
{
if (SetProperty(ref _expirationTime, value))
{
ExpirationTimeChanged();
}
}
}
public int? MaxAccessCount
{
get => _maxAccessCount;
@@ -198,7 +205,7 @@ namespace Bit.App.Pages
}
public string FileName
{
get => _fileName ?? AppResources.NoFileChosen;
get => _fileName;
set
{
if (SetProperty(ref _fileName, value))
@@ -233,13 +240,10 @@ namespace Bit.App.Pages
public bool IsFile => Send?.Type == SendType.File;
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
public DateTimeViewModel DeletionDateTimeViewModel { get; }
public DateTimeViewModel ExpirationDateTimeViewModel { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
public string OptionsShowHideIcon => ShowOptions ? BitwardenIcons.ChevronUp : BitwardenIcons.AngleDown;
public async Task InitAsync()
{
@@ -264,8 +268,10 @@ namespace Bit.App.Pages
return false;
}
Send = await send.DecryptAsync();
DeletionDateTimeViewModel.DateTime = Send.DeletionDate.ToLocalTime();
ExpirationDateTimeViewModel.DateTime = Send.ExpirationDate?.ToLocalTime();
DeletionDate = Send.DeletionDate.ToLocalTime();
DeletionTime = DeletionDate.TimeOfDay;
ExpirationDate = Send.ExpirationDate?.ToLocalTime();
ExpirationTime = ExpirationDate?.TimeOfDay;
}
else
{
@@ -274,7 +280,8 @@ namespace Bit.App.Pages
{
Type = Type.GetValueOrDefault(defaultType),
};
DeletionDateTimeViewModel.DateTime = DateTimeNow().AddDays(7);
_deletionDate = DateTimeNow().AddDays(7);
_deletionTime = DeletionDate.TimeOfDay;
DeletionDateTypeSelectedIndex = 4;
ExpirationDateTypeSelectedIndex = 0;
}
@@ -298,22 +305,23 @@ namespace Bit.App.Pages
public void ClearExpirationDate()
{
_isOverridingPickers = true;
ExpirationDateTimeViewModel.DateTime = null;
ExpirationDate = null;
ExpirationTime = null;
_isOverridingPickers = false;
}
private void UpdateSendData()
{
// filename
if (Send.File != null && _fileName != null)
if (Send.File != null && FileName != null)
{
Send.File.FileName = _fileName;
Send.File.FileName = FileName;
}
// deletion date
if (ShowDeletionCustomPickers)
{
Send.DeletionDate = DeletionDateTimeViewModel.DateTime.Value.ToUniversalTime();
Send.DeletionDate = DeletionDate.Date.Add(DeletionTime).ToUniversalTime();
}
else
{
@@ -321,9 +329,9 @@ namespace Bit.App.Pages
}
// expiration date
if (ShowExpirationCustomPickers && ExpirationDateTimeViewModel.DateTime.HasValue)
if (ShowExpirationCustomPickers && ExpirationDate.HasValue && ExpirationTime.HasValue)
{
Send.ExpirationDate = ExpirationDateTimeViewModel.DateTime.Value.ToUniversalTime();
Send.ExpirationDate = ExpirationDate.Value.Date.Add(ExpirationTime.Value).ToUniversalTime();
}
else if (_simpleExpirationDateTime.HasValue)
{
@@ -476,7 +484,7 @@ namespace Bit.App.Pages
return;
}
if (Page is SendAddOnlyPage sendPage && sendPage.OnClose != null)
if (Page is SendAddEditPage sendPage && sendPage.OnClose != null)
{
sendPage.OnClose();
return;
@@ -617,6 +625,24 @@ namespace Bit.App.Pages
}
}
private void ExpirationDateChanged()
{
if (!_isOverridingPickers && !ExpirationTime.HasValue)
{
// auto-set time to current time upon setting date
ExpirationTime = DateTimeNow().TimeOfDay;
}
}
private void ExpirationTimeChanged()
{
if (!_isOverridingPickers && !ExpirationDate.HasValue)
{
// auto-set date to current date upon setting time
ExpirationDate = DateTime.Today;
}
}
private void MaxAccessCountChanged()
{
Send.MaxAccessCount = _maxAccessCount;
@@ -640,10 +666,5 @@ namespace Bit.App.Pages
DateTimeKind.Local
);
}
internal void TriggerSendTextPropertyChanged()
{
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(Send)));
}
}
}

View File

@@ -1,183 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
x:DataType="pages:SendAddEditPageViewModel"
x:Class="Bit.App.Pages.SendAddOnlyOptionsView">
<ContentView.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentView.Resources>
<ContentView.Content>
<StackLayout>
<StackLayout
StyleClass="box-row"
Margin="0,10,0,0">
<Label
Text="{u:I18n DeletionDate}"
StyleClass="box-label" />
<Picker
x:Name="_deletionDateTypePicker"
ItemsSource="{Binding DeletionTypeOptions, Mode=OneTime}"
SelectedIndex="{Binding DeletionDateTypeSelectedIndex}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
ItemDisplayBinding="{Binding Key}"
ios:Picker.UpdateMode="WhenFinished"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionTime}" />
<controls:LazyDateTimePicker
x:Name="_lazyDeletionDateTimePicker"
BindingContext="{Binding DeletionDateTimeViewModel}"
IsVisible="{Binding ShowDeletionCustomPickers}"
Margin="0,5,0,0" />
<Label
Text="{u:I18n DeletionDateInfo}"
StyleClass="box-footer-label"
Margin="0,5,0,0" />
</StackLayout>
<StackLayout StyleClass="box-row" Margin="0,5,0,0">
<Label
Text="{u:I18n ExpirationDate}"
StyleClass="box-label" />
<Picker
x:Name="_expirationDateTypePicker"
ItemsSource="{Binding ExpirationTypeOptions, Mode=OneTime}"
SelectedIndex="{Binding ExpirationDateTypeSelectedIndex}"
ItemDisplayBinding="{Binding Key}"
ios:Picker.UpdateMode="WhenFinished"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ExpirationTime}" />
<controls:LazyDateTimePicker
x:Name="_lazyExpirationDateTimePicker"
BindingContext="{Binding ExpirationDateTimeViewModel}"
IsVisible="{Binding ShowExpirationCustomPickers}"
Margin="0,5,0,0" />
<Label
Text="{u:I18n ExpirationDateInfo}"
StyleClass="box-footer-label"
HorizontalOptions="StartAndExpand"
Margin="0,5,0,0" />
</StackLayout>
<StackLayout
StyleClass="box-row"
Margin="0,5,0,0">
<Label
Text="{u:I18n MaximumAccessCount}"
StyleClass="box-label" />
<StackLayout
StyleClass="box-row"
Orientation="Horizontal">
<Entry
Text="{Binding MaxAccessCount}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
Keyboard="Numeric"
MaxLength="9"
TextChanged="OnMaxAccessCountTextChanged"
HorizontalOptions="FillAndExpand" />
<controls:ExtendedStepper
x:Name="_maxAccessCountStepper"
Value="{Binding MaxAccessCount}"
Maximum="999999999"
IsEnabled="{Binding SendEnabled}"
Margin="10,0,0,0" />
</StackLayout>
<Label
Text="{u:I18n MaximumAccessCountInfo}"
StyleClass="box-footer-label" />
</StackLayout>
<StackLayout
StyleClass="box-row"
Margin="0,5,0,0">
<Label
Text="{u:I18n NewPassword}"
StyleClass="box-label" />
<StackLayout Orientation="Horizontal">
<Entry
Text="{Binding NewPassword}"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
HorizontalOptions="FillAndExpand" />
<controls:IconButton
IsEnabled="{Binding SendEnabled}"
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Margin="10,0,0,0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</StackLayout>
<Label
Text="{u:I18n PasswordInfo}"
StyleClass="box-footer-label"
Margin="0,5,0,0" />
</StackLayout>
<StackLayout
StyleClass="box-row"
Margin="0,5,0,0">
<Label
Text="{u:I18n Notes}"
StyleClass="box-label" />
<Editor
x:Name="_notesEditor"
AutoSize="TextChanges"
Text="{Binding Send.Notes}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
Margin="0,10,0,5"
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
<Editor.Effects>
<effects:ScrollEnabledEffect />
</Editor.Effects>
</Editor>
<BoxView
StyleClass="box-row-separator" />
<Label
Text="{u:I18n NotesInfo}"
StyleClass="box-footer-label"
Margin="0,5,0,0" />
</StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch"
Margin="0,5,0,0">
<Label
Text="{u:I18n HideEmail}"
StyleClass="box-label-regular"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Send.HideEmail}"
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
HorizontalOptions="End"
Margin="10,0,0,0" />
</StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch"
Margin="0,5,0,0">
<Label
Text="{u:I18n DisableSend}"
StyleClass="box-label-regular"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Send.Disabled}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End"
Margin="10,0,0,0" />
</StackLayout>
</StackLayout>
</ContentView.Content>
</ContentView>

View File

@@ -1,91 +0,0 @@
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Bit.App.Behaviors;
using Xamarin.CommunityToolkit.UI.Views;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class SendAddOnlyOptionsView : ContentView
{
public SendAddOnlyOptionsView()
{
InitializeComponent();
}
private SendAddEditPageViewModel ViewModel => BindingContext as SendAddEditPageViewModel;
public void SetMainScrollView(ScrollView scrollView)
{
_notesEditor.Behaviors.Add(new EditorPreventAutoBottomScrollingOnFocusedBehavior { ParentScrollView = scrollView });
}
private void OnMaxAccessCountTextChanged(object sender, TextChangedEventArgs e)
{
if (ViewModel is null)
{
return;
}
if (string.IsNullOrWhiteSpace(e.NewTextValue))
{
ViewModel.MaxAccessCount = null;
_maxAccessCountStepper.Value = 0;
return;
}
// accept only digits
if (!int.TryParse(e.NewTextValue, out int _))
{
((Entry)sender).Text = e.OldTextValue;
}
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(BindingContext)
&&
ViewModel != null)
{
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!_lazyDeletionDateTimePicker.IsLoaded
&&
e.PropertyName == nameof(SendAddEditPageViewModel.ShowDeletionCustomPickers)
&&
ViewModel.ShowDeletionCustomPickers)
{
_lazyDeletionDateTimePicker.LoadViewAsync();
}
if (!_lazyExpirationDateTimePicker.IsLoaded
&&
e.PropertyName == nameof(SendAddEditPageViewModel.ShowExpirationCustomPickers)
&&
ViewModel.ShowExpirationCustomPickers)
{
_lazyExpirationDateTimePicker.LoadViewAsync();
}
}
}
public class SendAddOnlyOptionsLazyView : LazyView<SendAddOnlyOptionsView>
{
public ScrollView MainScrollView { get; set; }
public override async ValueTask LoadViewAsync()
{
await base.LoadViewAsync();
if (Content is SendAddOnlyOptionsView optionsView)
{
optionsView.SetMainScrollView(MainScrollView);
}
}
}
}

View File

@@ -1,190 +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.SendAddOnlyPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
xmlns:effects="clr-namespace:Bit.App.Effects"
x:DataType="pages:SendAddEditPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:SendAddEditPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<!--Order matters here or the avatar's image won't be updated correctly, check iOS CustomNavigationRenderer for more info-->
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-2"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" x:Name="_closeItem" />
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" x:Name="_saveItem"/>
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout>
<ScrollView
x:Name="_scrollView"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All">
<StackLayout x:Name="_mainContainer" StyleClass="box">
<Frame
IsVisible="{Binding SendEnabled, Converter={StaticResource inverseBool}}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n SendDisabledWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<Frame
IsVisible="{Binding SendOptionsPolicyInEffect}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n SendOptionsPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n Name}"
StyleClass="box-label" />
<Entry
x:Name="_nameEntry"
Text="{Binding Send.Name}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" />
<Label
Text="{u:I18n NameInfo}"
StyleClass="box-footer-label"
Margin="0,5,0,0" />
</StackLayout>
<StackLayout
StyleClass="box-row"
IsVisible="{Binding IsFile}">
<Label
Text="{u:I18n TypeFile}"
StyleClass="box-label" />
<StackLayout
StyleClass="box-row">
<Label
Text="{Binding FileName}"
LineBreakMode="CharacterWrap"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center" />
<Label
Margin="0, 5, 0, 0"
Text="{u:I18n MaxFileSize}"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center" />
</StackLayout>
</StackLayout>
<StackLayout
StyleClass="box-row"
IsVisible="{Binding IsText}">
<Label
Text="{u:I18n TypeText}"
StyleClass="box-label" />
<Editor
x:Name="_textEditor"
AutoSize="TextChanges"
Text="{Binding Send.Text.Text}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
Margin="{Binding EditorMargins}"
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
<Editor.Behaviors>
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
</Editor.Behaviors>
<Editor.Effects>
<effects:ScrollEnabledEffect />
</Editor.Effects>
</Editor>
<BoxView
StyleClass="box-row-separator" />
<Label
Text="{u:I18n TypeTextInfo}"
StyleClass="box-footer-label"
Margin="0,5,0,10" />
<StackLayout
StyleClass="box-row, box-row-switch"
Margin="0,10,0,0">
<Label
Text="{u:I18n HideTextByDefault}"
StyleClass="box-label-regular"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Send.Text.Hidden}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End"
Margin="10,0,0,0" />
</StackLayout>
</StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch">
<Label
Text="{Binding ShareOnSaveText}"
StyleClass="box-label-regular"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding ShareOnSave}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End"
Margin="10,0,0,0" />
</StackLayout>
<StackLayout
Orientation="Horizontal"
Spacing="0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="OptionsHeader_Tapped" />
</StackLayout.GestureRecognizers>
<Label
Text="{u:I18n Options}"
TextColor="{DynamicResource PrimaryColor}"
Margin="0,0,5,0"
AutomationProperties.IsInAccessibleTree="False"/>
<controls:IconLabel
Text="{Binding OptionsShowHideIcon}"
TextColor="{DynamicResource PrimaryColor}"
AutomationProperties.IsInAccessibleTree="False"/>
</StackLayout>
<pages:SendAddOnlyOptionsLazyView x:Name="_lazyOptionsView" IsVisible="{Binding ShowOptions}" />
</StackLayout>
</ScrollView>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
LongPressAccountEnabled="False"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -1,178 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
/// <summary>
/// This is a version of <see cref="SendAddEditPage"/> that is reduced for adding only and adapted
/// for performance for iOS Share extension.
/// </summary>
/// <remarks>
/// This should NOT be used in Android.
/// </remarks>
public partial class SendAddOnlyPage : BaseContentPage
{
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private AppOptions _appOptions;
private SendAddEditPageViewModel _vm;
public Action OnClose { get; set; }
public Action AfterSubmit { get; set; }
public SendAddOnlyPage(
AppOptions appOptions = null,
string sendId = null,
SendType? type = null)
{
if (appOptions?.IosExtension != true)
{
throw new InvalidOperationException(nameof(SendAddOnlyPage) + " is only prepared to be used in iOS share extension");
}
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as SendAddEditPageViewModel;
_vm.Page = this;
_vm.SendId = sendId;
_vm.Type = appOptions?.CreateSend?.Item1 ?? type;
if (_vm.IsText)
{
_nameEntry.ReturnType = ReturnType.Next;
_nameEntry.ReturnCommand = new Command(() => _textEditor.Focus());
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
try
{
if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
}
if (await _vaultTimeoutService.IsLockedAsync())
{
return;
}
await _vm.InitAsync();
if (!await _vm.LoadAsync())
{
await CloseAsync();
return;
}
_accountAvatar?.OnAppearing();
await Device.InvokeOnMainThreadAsync(async () => _vm.AvatarImageSource = await GetAvatarImageSourceAsync());
await HandleCreateRequest();
if (string.IsNullOrWhiteSpace(_vm.Send?.Name))
{
RequestFocus(_nameEntry);
}
AdjustToolbar();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
await CloseAsync();
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_accountAvatar?.OnDisappearing();
}
private async Task CloseAsync()
{
if (OnClose is null)
{
await Navigation.PopModalAsync();
}
else
{
OnClose();
}
}
private async void Save_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
var submitted = await _vm.SubmitAsync();
if (submitted)
{
AfterSubmit?.Invoke();
}
}
}
private async void Close_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await CloseAsync();
}
}
private void AdjustToolbar()
{
_saveItem.IsEnabled = _vm.SendEnabled;
}
private Task HandleCreateRequest()
{
if (_appOptions?.CreateSend == null)
{
return Task.CompletedTask;
}
_vm.IsAddFromShare = true;
_vm.CopyInsteadOfShareAfterSaving = _appOptions.CopyInsteadOfShareAfterSaving;
var name = _appOptions.CreateSend.Item2;
_vm.Send.Name = name;
var type = _appOptions.CreateSend.Item1;
if (type == SendType.File)
{
_vm.FileData = _appOptions.CreateSend.Item3;
_vm.FileName = name;
}
else
{
var text = _appOptions.CreateSend.Item4;
_vm.Send.Text.Text = text;
_vm.TriggerSendTextPropertyChanged();
}
_appOptions.CreateSend = null;
return Task.CompletedTask;
}
void OptionsHeader_Tapped(object sender, EventArgs e)
{
_vm.ToggleOptionsCommand.Execute(null);
if (!_lazyOptionsView.IsLoaded)
{
_lazyOptionsView.MainScrollView = _scrollView;
_lazyOptionsView.LoadViewAsync();
}
}
}
}

View File

@@ -5,7 +5,6 @@ using Bit.App.Controls;
using Bit.App.Models;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -69,28 +68,21 @@ namespace Bit.App.Pages
_broadcasterService.Subscribe(_pageName, async (message) =>
{
try
if (message.Command == "syncStarted")
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted" || message.Command == "sendUpdated")
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
catch (Exception ex)
else if (message.Command == "syncCompleted" || message.Command == "sendUpdated")
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
});

View File

@@ -33,23 +33,6 @@
StyleClass="box-footer-label"
Text="{u:I18n ThemeDescription}" />
</StackLayout>
<StackLayout
StyleClass="box"
IsVisible="{Binding ShowAutoDarkThemeOptions}">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n DefaultDarkTheme}"
StyleClass="box-label" />
<Picker
x:Name="_autoDarkThemePicker"
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
StyleClass="box-value" />
</StackLayout>
<Label
StyleClass="box-footer-label"
Text="{u:I18n DefaultDarkThemeDescription}" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
@@ -83,31 +66,31 @@
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n CopyTotpAutomatically}"
Text="{u:I18n DisableAutoTotpCopy}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding AutoTotpCopy}"
IsToggled="{Binding DisableAutoTotpCopy}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<Label
Text="{u:I18n CopyTotpAutomaticallyDescription}"
Text="{u:I18n DisableAutoTotpCopyDescription}"
StyleClass="box-footer-label, box-footer-label-switch" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n ShowWebsiteIcons}"
Text="{u:I18n DisableWebsiteIcons}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Favicon}"
IsToggled="{Binding DisableFavicon}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<Label
Text="{u:I18n ShowWebsiteIconsDescription}"
Text="{u:I18n DisableWebsiteIconsDescription}"
StyleClass="box-footer-label, box-footer-label-switch" />
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
@@ -117,35 +100,35 @@
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n AskToAddLogin}"
Text="{u:I18n DisableSavePrompt}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding AutofillSavePrompt}"
IsToggled="{Binding AutofillDisableSavePrompt}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<Label
Text="{u:I18n AskToAddLoginDescription}"
Text="{u:I18n DisableSavePromptDescription}"
StyleClass="box-footer-label, box-footer-label-switch" />
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
<StackLayout StyleClass="box-row, box-row-input">
<Label
Text="{u:I18n AutofillBlockedUris}"
Text="{u:I18n BlacklistedUris}"
StyleClass="box-label" />
<Editor
x:Name="_autofillBlockedUrisEditor"
Text="{Binding AutofillBlockedUris}"
x:Name="_blacklistedUrisEditor"
Text="{Binding AutofillBlacklistedUris}"
StyleClass="box-value"
AutoSize="TextChanges"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
Keyboard="Url"
Unfocused="AutofillBlockedUrisEditor_Unfocused" />
Unfocused="BlacklistedUrisEditor_Unfocused" />
</StackLayout>
<Label
Text="{u:I18n AutofillBlockedUrisDescription}"
Text="{u:I18n BlacklistedUrisDescription}"
StyleClass="box-footer-label" />
</StackLayout>
</StackLayout>

View File

@@ -19,7 +19,6 @@ namespace Bit.App.Pages
_vm = BindingContext as OptionsPageViewModel;
_vm.Page = this;
_themePicker.ItemDisplayBinding = new Binding("Value");
_autoDarkThemePicker.ItemDisplayBinding = new Binding("Value");
_uriMatchPicker.ItemDisplayBinding = new Binding("Value");
_clearClipboardPicker.ItemDisplayBinding = new Binding("Value");
if (Device.RuntimePlatform == Device.Android)
@@ -30,7 +29,6 @@ namespace Bit.App.Pages
else
{
_themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_autoDarkThemePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
}
@@ -45,12 +43,12 @@ namespace Bit.App.Pages
protected async override void OnDisappearing()
{
base.OnDisappearing();
await _vm.UpdateAutofillBlockedUris();
await _vm.UpdateAutofillBlacklistedUris();
}
private async void AutofillBlockedUrisEditor_Unfocused(object sender, FocusEventArgs e)
private async void BlacklistedUrisEditor_Unfocused(object sender, FocusEventArgs e)
{
await _vm.UpdateAutofillBlockedUris();
await _vm.UpdateAutofillBlacklistedUris();
}
private async void Close_Clicked(object sender, System.EventArgs e)

View File

@@ -12,17 +12,17 @@ namespace Bit.App.Pages
{
public class OptionsPageViewModel : BaseViewModel
{
private readonly ITotpService _totpService;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private bool _autofillSavePrompt;
private string _autofillBlockedUris;
private bool _favicon;
private bool _autoTotpCopy;
private bool _autofillDisableSavePrompt;
private string _autofillBlacklistedUris;
private bool _disableFavicon;
private bool _disableAutoTotpCopy;
private int _clearClipboardSelectedIndex;
private int _themeSelectedIndex;
private int _autoDarkThemeSelectedIndex;
private int _uriMatchSelectedIndex;
private bool _inited;
private bool _updatingAutofill;
@@ -30,6 +30,7 @@ namespace Bit.App.Pages
public OptionsPageViewModel()
{
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@@ -52,16 +53,10 @@ namespace Bit.App.Pages
ThemeOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
new KeyValuePair<string, string>(ThemeManager.Light, AppResources.Light),
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
};
AutoDarkThemeOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
new KeyValuePair<string, string>("light", AppResources.Light),
new KeyValuePair<string, string>("dark", AppResources.Dark),
new KeyValuePair<string, string>("black", AppResources.Black),
new KeyValuePair<string, string>("nord", "Nord"),
};
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
{
@@ -76,7 +71,6 @@ namespace Bit.App.Pages
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
public List<KeyValuePair<string, string>> AutoDarkThemeOptions { get; set; }
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
public int ClearClipboardSelectedIndex
@@ -86,7 +80,7 @@ namespace Bit.App.Pages
{
if (SetProperty(ref _clearClipboardSelectedIndex, value))
{
SaveClipboardChangedAsync().FireAndForget();
var task = SaveClipboardChangedAsync();
}
}
}
@@ -96,25 +90,9 @@ namespace Bit.App.Pages
get => _themeSelectedIndex;
set
{
if (SetProperty(ref _themeSelectedIndex, value,
additionalPropertyNames: new[] { nameof(ShowAutoDarkThemeOptions) })
)
if (SetProperty(ref _themeSelectedIndex, value))
{
SaveThemeAsync().FireAndForget();
}
}
}
public bool ShowAutoDarkThemeOptions => ThemeOptions[ThemeSelectedIndex].Key == null;
public int AutoDarkThemeSelectedIndex
{
get => _autoDarkThemeSelectedIndex;
set
{
if (SetProperty(ref _autoDarkThemeSelectedIndex, value))
{
SaveThemeAsync().FireAndForget();
var task = SaveThemeAsync();
}
}
}
@@ -126,51 +104,51 @@ namespace Bit.App.Pages
{
if (SetProperty(ref _uriMatchSelectedIndex, value))
{
SaveDefaultUriAsync().FireAndForget();
var task = SaveDefaultUriAsync();
}
}
}
public bool Favicon
public bool DisableFavicon
{
get => _favicon;
get => _disableFavicon;
set
{
if (SetProperty(ref _favicon, value))
if (SetProperty(ref _disableFavicon, value))
{
UpdateFaviconAsync().FireAndForget();
var task = UpdateDisableFaviconAsync();
}
}
}
public bool AutoTotpCopy
public bool DisableAutoTotpCopy
{
get => _autoTotpCopy;
get => _disableAutoTotpCopy;
set
{
if (SetProperty(ref _autoTotpCopy, value))
if (SetProperty(ref _disableAutoTotpCopy, value))
{
UpdateAutoTotpCopyAsync().FireAndForget();
var task = UpdateAutoTotpCopyAsync();
}
}
}
public bool AutofillSavePrompt
public bool AutofillDisableSavePrompt
{
get => _autofillSavePrompt;
get => _autofillDisableSavePrompt;
set
{
if (SetProperty(ref _autofillSavePrompt, value))
if (SetProperty(ref _autofillDisableSavePrompt, value))
{
UpdateAutofillSavePromptAsync().FireAndForget();
var task = UpdateAutofillDisableSavePromptAsync();
}
}
}
public string AutofillBlockedUris
public string AutofillBlacklistedUris
{
get => _autofillBlockedUris;
set => SetProperty(ref _autofillBlockedUris, value);
get => _autofillBlacklistedUris;
set => SetProperty(ref _autofillBlacklistedUris, value);
}
public bool ShowAndroidAutofillSettings
@@ -181,15 +159,13 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
AutofillBlockedUris = blockedUrisList != null ? string.Join(", ", blockedUrisList) : null;
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
AutofillDisableSavePrompt = (await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
var blacklistedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
AutofillBlacklistedUris = blacklistedUrisList != null ? string.Join(", ", blacklistedUrisList) : null;
DisableAutoTotpCopy = !(await _totpService.IsAutoCopyEnabledAsync());
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var theme = await _stateService.GetThemeAsync();
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
AutoDarkThemeSelectedIndex = AutoDarkThemeOptions.FindIndex(k => k.Key == autoDarkTheme);
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
@@ -202,17 +178,15 @@ namespace Bit.App.Pages
{
if (_inited)
{
// TODO: [PS-961] Fix negative function names
await _stateService.SetDisableAutoTotpCopyAsync(!AutoTotpCopy);
await _stateService.SetDisableAutoTotpCopyAsync(DisableAutoTotpCopy);
}
}
private async Task UpdateFaviconAsync()
private async Task UpdateDisableFaviconAsync()
{
if (_inited)
{
// TODO: [PS-961] Fix negative function names
await _stateService.SetDisableFaviconAsync(!Favicon);
await _stateService.SetDisableFaviconAsync(DisableFavicon);
}
}
@@ -228,8 +202,8 @@ namespace Bit.App.Pages
{
if (_inited && ThemeSelectedIndex > -1)
{
await _stateService.SetThemeAsync(ThemeOptions[ThemeSelectedIndex].Key);
await _stateService.SetAutoDarkThemeAsync(AutoDarkThemeOptions[AutoDarkThemeSelectedIndex].Key);
var theme = ThemeOptions[ThemeSelectedIndex].Key;
await _stateService.SetThemeAsync(theme);
ThemeManager.SetTheme(Application.Current.Resources);
_messagingService.Send("updatedTheme");
}
@@ -243,28 +217,27 @@ namespace Bit.App.Pages
}
}
private async Task UpdateAutofillSavePromptAsync()
private async Task UpdateAutofillDisableSavePromptAsync()
{
if (_inited)
{
// TODO: [PS-961] Fix negative function names
await _stateService.SetAutofillDisableSavePromptAsync(!AutofillSavePrompt);
await _stateService.SetAutofillDisableSavePromptAsync(AutofillDisableSavePrompt);
}
}
public async Task UpdateAutofillBlockedUris()
public async Task UpdateAutofillBlacklistedUris()
{
if (_inited)
{
if (string.IsNullOrWhiteSpace(AutofillBlockedUris))
if (string.IsNullOrWhiteSpace(AutofillBlacklistedUris))
{
await _stateService.SetAutofillBlacklistedUrisAsync(null);
AutofillBlockedUris = null;
AutofillBlacklistedUris = null;
return;
}
try
{
var csv = AutofillBlockedUris;
var csv = AutofillBlacklistedUris;
var urisList = new List<string>();
foreach (var uri in csv.Split(','))
{
@@ -281,7 +254,7 @@ namespace Bit.App.Pages
urisList.Add(cleanedUri);
}
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
AutofillBlockedUris = string.Join(", ", urisList);
AutofillBlacklistedUris = string.Join(", ", urisList);
}
catch { }
}

View File

@@ -2,13 +2,18 @@
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Pages.Accounts;
using Bit.App.Resources;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class SettingsPage : BaseContentPage
{
private readonly IDeviceActionService _deviceActionService;
private readonly TabsPage _tabsPage;
private SettingsPageViewModel _vm;
@@ -16,6 +21,7 @@ namespace Bit.App.Pages
{
_tabsPage = tabsPage;
InitializeComponent();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vm = BindingContext as SettingsPageViewModel;
_vm.Page = this;
}
@@ -61,12 +67,122 @@ namespace Bit.App.Pages
}
}
private void RowSelected(object sender, SelectionChangedEventArgs e)
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ExtendedCollectionView)sender).SelectedItem = null;
if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item)
if (!DoOnce())
{
_vm?.ExecuteSettingItemCommand.Execute(item);
return;
}
if (!(e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item))
{
return;
}
if (item.Name == AppResources.Sync)
{
await Navigation.PushModalAsync(new NavigationPage(new SyncPage()));
}
else if (item.Name == AppResources.AutofillServices)
{
await Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(this)));
}
else if (item.Name == AppResources.PasswordAutofill)
{
await Navigation.PushModalAsync(new NavigationPage(new AutofillPage()));
}
else if (item.Name == AppResources.AppExtension)
{
await Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()));
}
else if (item.Name == AppResources.Options)
{
await Navigation.PushModalAsync(new NavigationPage(new OptionsPage()));
}
else if (item.Name == AppResources.Folders)
{
await Navigation.PushModalAsync(new NavigationPage(new FoldersPage()));
}
else if (item.Name == AppResources.About)
{
await _vm.AboutAsync();
}
else if (item.Name == AppResources.HelpAndFeedback)
{
_vm.Help();
}
else if (item.Name == AppResources.FingerprintPhrase)
{
await _vm.FingerprintAsync();
}
else if (item.Name == AppResources.RateTheApp)
{
_vm.Rate();
}
else if (item.Name == AppResources.ImportItems)
{
_vm.Import();
}
else if (item.Name == AppResources.ExportVault)
{
await Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage()));
}
else if (item.Name == AppResources.LearnOrg)
{
await _vm.ShareAsync();
}
else if (item.Name == AppResources.WebVault)
{
_vm.WebVault();
}
else if (item.Name == AppResources.ChangeMasterPassword)
{
await _vm.ChangePasswordAsync();
}
else if (item.Name == AppResources.TwoStepLogin)
{
await _vm.TwoStepAsync();
}
else if (item.Name == AppResources.LogOut)
{
await _vm.LogOutAsync();
}
else if (item.Name == AppResources.DeleteAccount)
{
await Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()));
}
else if (item.Name == AppResources.LockNow)
{
await _vm.LockAsync();
}
else if (item.Name == AppResources.VaultTimeout)
{
await _vm.VaultTimeoutAsync();
}
else if (item.Name == AppResources.VaultTimeoutAction)
{
await _vm.VaultTimeoutActionAsync();
}
else if (item.Name == AppResources.UnlockWithPIN)
{
await _vm.UpdatePinAsync();
}
else if (item.Name == AppResources.SubmitCrashLogs)
{
await _vm.LoggerReportingAsync();
}
else
{
var biometricName = AppResources.Biometrics;
if (Device.RuntimePlatform == Device.iOS)
{
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
biometricName = supportsFace ? AppResources.FaceID : AppResources.TouchID;
}
if (item.Name == string.Format(AppResources.UnlockWith, biometricName))
{
await _vm.UpdateBiometricAsync();
}
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities;
using Xamarin.Forms;
@@ -13,9 +12,7 @@ namespace Bit.App.Pages
public string SubLabel { get; set; }
public TimeSpan? Time { get; set; }
public bool UseFrame { get; set; }
public Func<Task> ExecuteAsync { get; set; }
public bool SubLabelTextEnabled => SubLabel == AppResources.On;
public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled;
public string LineBreakMode => SubLabel == null ? "TailTruncation" : "";
public bool ShowSubLabel => SubLabel.Length != 0;
public bool ShowTimeInput => Time != null;

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Pages.Accounts;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
@@ -30,13 +30,11 @@ namespace Bit.App.Pages
private readonly IKeyConnectorService _keyConnectorService;
private readonly IClipboardService _clipboardService;
private readonly ILogger _loggerService;
private const int CustomVaultTimeoutValue = -100;
private bool _supportsBiometric;
private bool _pin;
private bool _biometric;
private bool _screenCaptureAllowed;
private string _lastSyncDate;
private string _vaultTimeoutDisplayValue;
private string _vaultTimeoutActionDisplayValue;
@@ -86,14 +84,10 @@ namespace Bit.App.Pages
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
PageTitle = AppResources.Settings;
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
}
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
public async Task InitAsync()
{
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
@@ -123,7 +117,6 @@ namespace Bit.App.Pages
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2;
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
if (_vaultTimeoutDisplayValue == null)
{
@@ -264,17 +257,6 @@ namespace Bit.App.Pages
}
var cleanSelection = selection.Replace("✓ ", string.Empty);
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
// Check if the selected Timeout action is "Never" and if it's different from the previous selected value
if (selectionOption.Value == null && selectionOption.Value != oldTimeout)
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.NeverLockWarning,
AppResources.Warning, AppResources.Yes, AppResources.Cancel);
if (!confirmed)
{
return;
}
}
_vaultTimeoutDisplayValue = selectionOption.Key;
newTimeout = selectionOption.Value;
}
@@ -441,8 +423,6 @@ namespace Bit.App.Pages
public void BuildList()
{
//TODO: Refactor this once navigation is abstracted so that it doesn't depend on Page, e.g. Page.Navigation.PushModalAsync...
var doUpper = Device.RuntimePlatform != Device.Android;
var autofillItems = new List<SettingsPageListItem>();
if (Device.RuntimePlatform == Device.Android)
@@ -450,69 +430,38 @@ namespace Bit.App.Pages
autofillItems.Add(new SettingsPageListItem
{
Name = AppResources.AutofillServices,
SubLabel = _deviceActionService.AutofillServicesEnabled() ? AppResources.On : AppResources.Off,
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(Page as SettingsPage)))
SubLabel = _deviceActionService.AutofillServicesEnabled() ?
AppResources.Enabled : AppResources.Disabled
});
}
else
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{
autofillItems.Add(new SettingsPageListItem
{
Name = AppResources.PasswordAutofill,
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage()))
});
autofillItems.Add(new SettingsPageListItem { Name = AppResources.PasswordAutofill });
}
autofillItems.Add(new SettingsPageListItem
{
Name = AppResources.AppExtension,
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()))
});
autofillItems.Add(new SettingsPageListItem { Name = AppResources.AppExtension });
}
var manageItems = new List<SettingsPageListItem>
{
new SettingsPageListItem
{
Name = AppResources.Folders,
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage()))
},
new SettingsPageListItem
{
Name = AppResources.Sync,
SubLabel = _lastSyncDate,
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new SyncPage()))
}
new SettingsPageListItem { Name = AppResources.Folders },
new SettingsPageListItem { Name = AppResources.Sync, SubLabel = _lastSyncDate }
};
var securityItems = new List<SettingsPageListItem>
{
new SettingsPageListItem
{
Name = AppResources.VaultTimeout,
SubLabel = _vaultTimeoutDisplayValue,
ExecuteAsync = () => VaultTimeoutAsync() },
new SettingsPageListItem { Name = AppResources.VaultTimeout, SubLabel = _vaultTimeoutDisplayValue },
new SettingsPageListItem
{
Name = AppResources.VaultTimeoutAction,
SubLabel = _vaultTimeoutActionDisplayValue,
ExecuteAsync = () => VaultTimeoutActionAsync()
SubLabel = _vaultTimeoutActionDisplayValue
},
new SettingsPageListItem
{
Name = AppResources.UnlockWithPIN,
SubLabel = _pin ? AppResources.On : AppResources.Off,
ExecuteAsync = () => UpdatePinAsync()
SubLabel = _pin ? AppResources.Enabled : AppResources.Disabled
},
new SettingsPageListItem
{
Name = AppResources.LockNow,
ExecuteAsync = () => LockAsync()
},
new SettingsPageListItem
{
Name = AppResources.TwoStepLogin,
ExecuteAsync = () => TwoStepAsync()
}
new SettingsPageListItem { Name = AppResources.LockNow },
new SettingsPageListItem { Name = AppResources.TwoStepLogin }
};
if (_supportsBiometric || _biometric)
{
@@ -525,8 +474,7 @@ namespace Bit.App.Pages
var item = new SettingsPageListItem
{
Name = string.Format(AppResources.UnlockWith, biometricName),
SubLabel = _biometric ? AppResources.On : AppResources.Off,
ExecuteAsync = () => UpdateBiometricAsync()
SubLabel = _biometric ? AppResources.Enabled : AppResources.Disabled
};
securityItems.Insert(2, item);
}
@@ -549,98 +497,40 @@ namespace Bit.App.Pages
UseFrame = true,
});
}
if (Device.RuntimePlatform == Device.Android)
{
securityItems.Add(new SettingsPageListItem
{
Name = AppResources.AllowScreenCapture,
SubLabel = _screenCaptureAllowed ? AppResources.On : AppResources.Off,
ExecuteAsync = () => SetScreenCaptureAllowedAsync()
});
}
var accountItems = new List<SettingsPageListItem>
{
new SettingsPageListItem
{
Name = AppResources.FingerprintPhrase,
ExecuteAsync = () => FingerprintAsync()
},
new SettingsPageListItem
{
Name = AppResources.LogOut,
ExecuteAsync = () => LogOutAsync()
}
new SettingsPageListItem { Name = AppResources.FingerprintPhrase },
new SettingsPageListItem { Name = AppResources.LogOut }
};
if (_showChangeMasterPassword)
{
accountItems.Insert(0, new SettingsPageListItem
{
Name = AppResources.ChangeMasterPassword,
ExecuteAsync = () => ChangePasswordAsync()
});
accountItems.Insert(0, new SettingsPageListItem { Name = AppResources.ChangeMasterPassword });
}
var toolsItems = new List<SettingsPageListItem>
{
new SettingsPageListItem
{
Name = AppResources.ImportItems,
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Import())
},
new SettingsPageListItem
{
Name = AppResources.ExportVault,
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage()))
}
new SettingsPageListItem { Name = AppResources.ImportItems },
new SettingsPageListItem { Name = AppResources.ExportVault }
};
if (IncludeLinksWithSubscriptionInfo())
{
toolsItems.Add(new SettingsPageListItem
{
Name = AppResources.LearnOrg,
ExecuteAsync = () => ShareAsync()
});
toolsItems.Add(new SettingsPageListItem
{
Name = AppResources.WebVault,
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => WebVault())
});
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
}
var otherItems = new List<SettingsPageListItem>
{
new SettingsPageListItem
{
Name = AppResources.Options,
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new OptionsPage()))
},
new SettingsPageListItem
{
Name = AppResources.About,
ExecuteAsync = () => AboutAsync()
},
new SettingsPageListItem
{
Name = AppResources.HelpAndFeedback,
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Help())
},
new SettingsPageListItem { Name = AppResources.Options },
new SettingsPageListItem { Name = AppResources.About },
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
#if !FDROID
new SettingsPageListItem
{
Name = AppResources.SubmitCrashLogs,
SubLabel = _reportLoggingEnabled ? AppResources.On : AppResources.Off,
ExecuteAsync = () => LoggerReportingAsync()
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
},
#endif
new SettingsPageListItem
{
Name = AppResources.RateTheApp,
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Rate())
},
new SettingsPageListItem
{
Name = AppResources.DeleteAccount,
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()))
}
new SettingsPageListItem { Name = AppResources.RateTheApp },
new SettingsPageListItem { Name = AppResources.DeleteAccount }
};
// TODO: improve this. Leaving this as is to reduce error possibility on the hotfix.
@@ -720,33 +610,5 @@ namespace Bit.App.Pages
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
public async Task SetScreenCaptureAllowedAsync()
{
if (CoreHelpers.ForceScreenCaptureEnabled())
{
return;
}
try
{
if (!_screenCaptureAllowed
&&
!await Page.DisplayAlert(AppResources.AllowScreenCapture, AppResources.AreYouSureYouWantToEnableScreenCapture, AppResources.Yes, AppResources.No))
{
return;
}
await _stateService.SetScreenCaptureAllowedAsync(!_screenCaptureAllowed);
_screenCaptureAllowed = !_screenCaptureAllowed;
await _deviceActionService.SetScreenCaptureAllowedAsync();
BuildList();
}
catch (Exception ex)
{
_loggerService.Exception(ex);
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
}
}
}
}

View File

@@ -2,7 +2,7 @@
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.CipherAddEditPage"
x:Class="Bit.App.Pages.AddEditPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
@@ -10,11 +10,11 @@
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:CipherAddEditPageViewModel"
x:DataType="pages:AddEditPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:CipherAddEditPageViewModel />
<pages:AddEditPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
@@ -184,61 +184,31 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n AuthenticatorKey}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<Frame
IsVisible="{Binding HasTotpValue, Converter={StaticResource inverseBool}}"
Margin="0,5,0,0"
StyleClass="btn-icon-row"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Padding="0"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3">
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="ScanTotp_Clicked" />
</Frame.GestureRecognizers>
<controls:IconLabel
Text="{Binding SetupTotpText}"
Padding="0,15"
HorizontalOptions="Center"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
</Frame>
<controls:MonoEntry
x:Name="_loginTotpEntry"
Text="{Binding Cipher.Login.Totp}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsVisible="{Binding HasTotpValue}"
IsPassword="{Binding Cipher.ViewPassword, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding Cipher.ViewPassword}"
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="{Binding TotpColumnSpan}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
IsVisible="{Binding HasTotpValue}"
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.Camera}}"
Clicked="ScanTotp_Clicked"
Grid.Row="0"
Grid.Column="2"
Grid.Column="1"
Grid.RowSpan="2"
IsVisible="{Binding HasTotpValue}"
IsVisible="{Binding Cipher.ViewPassword}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ScanQrTitle}" />
</Grid>
@@ -638,7 +608,7 @@
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Fields}">
<controls:RepeaterView.ItemTemplate>
<DataTemplate x:DataType="pages:CipherAddEditPageFieldViewModel">
<DataTemplate x:DataType="pages:AddEditPageFieldViewModel">
<StackLayout Spacing="0" Padding="0">
<Grid StyleClass="box-row, box-row-input">
<Grid.RowDefinitions>

View File

@@ -14,7 +14,7 @@ using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
public partial class CipherAddEditPage : BaseContentPage
public partial class AddEditPage : BaseContentPage
{
private readonly AppOptions _appOptions;
private readonly IStateService _stateService;
@@ -22,10 +22,10 @@ namespace Bit.App.Pages
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IKeyConnectorService _keyConnectorService;
private CipherAddEditPageViewModel _vm;
private AddEditPageViewModel _vm;
private bool _fromAutofill;
public CipherAddEditPage(
public AddEditPage(
string cipherId = null,
CipherType? type = null,
string folderId = null,
@@ -36,7 +36,7 @@ namespace Bit.App.Pages
bool fromAutofill = false,
AppOptions appOptions = null,
bool cloneMode = false,
CipherDetailsPage cipherDetailsPage = null)
ViewPage viewPage = null)
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@@ -47,7 +47,7 @@ namespace Bit.App.Pages
_fromAutofill = fromAutofill;
FromAutofillFramework = _appOptions?.FromAutofillFramework ?? false;
InitializeComponent();
_vm = BindingContext as CipherAddEditPageViewModel;
_vm = BindingContext as AddEditPageViewModel;
_vm.Page = this;
_vm.CipherId = cipherId;
_vm.FolderId = folderId == "none" ? null : folderId;
@@ -57,7 +57,7 @@ namespace Bit.App.Pages
_vm.DefaultName = name ?? appOptions?.SaveName;
_vm.DefaultUri = uri ?? appOptions?.Uri;
_vm.CloneMode = cloneMode;
_vm.CipherDetailsPage = cipherDetailsPage;
_vm.ViewPage = viewPage;
_vm.Init();
SetActivityIndicator();
if (_vm.EditMode && !_vm.CloneMode && Device.RuntimePlatform == Device.Android)
@@ -145,7 +145,7 @@ namespace Bit.App.Pages
}
public bool FromAutofillFramework { get; set; }
public CipherAddEditPageViewModel ViewModel => _vm;
public AddEditPageViewModel ViewModel => _vm;
protected override async void OnAppearing()
{

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core;
@@ -10,23 +11,26 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class CipherAddEditPageViewModel : BaseCipherViewModel
public class AddEditPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly IStateService _stateService;
private readonly IOrganizationService _organizationService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService;
private readonly IEventService _eventService;
private readonly IPolicyService _policyService;
private readonly IClipboardService _clipboardService;
private readonly ILogger _logger;
private CipherView _cipher;
private bool _showNotesSeparator;
private bool _showPassword;
private bool _showCardNumber;
@@ -40,7 +44,7 @@ namespace Bit.App.Pages
private bool _hasCollections;
private string _previousCipherId;
private List<Core.Models.View.CollectionView> _writeableCollections;
protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[]
private string[] _additionalCipherProperties = new string[]
{
nameof(IsLogin),
nameof(IsIdentity),
@@ -49,9 +53,7 @@ namespace Bit.App.Pages
nameof(ShowUris),
nameof(ShowAttachments),
nameof(ShowCollections),
nameof(HasTotpValue)
};
private List<KeyValuePair<UriMatchType?, string>> _matchDetectionOptions =
new List<KeyValuePair<UriMatchType?, string>>
{
@@ -64,28 +66,31 @@ namespace Bit.App.Pages
new KeyValuePair<UriMatchType?, string>(UriMatchType.Never, AppResources.Never)
};
public CipherAddEditPageViewModel()
public AddEditPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode);
CheckPasswordCommand = new Command(CheckPasswordAsync);
UriOptionsCommand = new Command<LoginUriView>(UriOptions);
FieldOptionsCommand = new Command<CipherAddEditPageFieldViewModel>(FieldOptions);
FieldOptionsCommand = new Command<AddEditPageFieldViewModel>(FieldOptions);
PasswordPromptHelpCommand = new Command(PasswordPromptHelp);
CopyCommand = new AsyncCommand(CopyTotpClipboardAsync, onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
Uris = new ExtendedObservableCollection<LoginUriView>();
Fields = new ExtendedObservableCollection<CipherAddEditPageFieldViewModel>();
Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>();
Collections = new ExtendedObservableCollection<CollectionViewModel>();
AllowPersonal = true;
@@ -141,10 +146,10 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; set; }
public Command ToggleCardNumberCommand { get; set; }
public Command ToggleCardCodeCommand { get; set; }
public Command CheckPasswordCommand { get; set; }
public Command UriOptionsCommand { get; set; }
public Command FieldOptionsCommand { get; set; }
public Command PasswordPromptHelpCommand { get; set; }
public AsyncCommand CopyCommand { get; set; }
public string CipherId { get; set; }
public string OrganizationId { get; set; }
public string FolderId { get; set; }
@@ -159,7 +164,7 @@ namespace Bit.App.Pages
public List<KeyValuePair<string, string>> FolderOptions { get; set; }
public List<KeyValuePair<string, string>> OwnershipOptions { get; set; }
public ExtendedObservableCollection<LoginUriView> Uris { get; set; }
public ExtendedObservableCollection<CipherAddEditPageFieldViewModel> Fields { get; set; }
public ExtendedObservableCollection<AddEditPageFieldViewModel> Fields { get; set; }
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
public int TypeSelectedIndex
@@ -228,6 +233,11 @@ namespace Bit.App.Pages
}
}
}
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value, additionalPropertyNames: _additionalCipherProperties);
}
public bool ShowNotesSeparator
{
get => _showNotesSeparator;
@@ -275,7 +285,7 @@ namespace Bit.App.Pages
public bool ShowOwnershipOptions => !EditMode || CloneMode;
public bool OwnershipPolicyInEffect => ShowOwnershipOptions && !AllowPersonal;
public bool CloneMode { get; set; }
public CipherDetailsPage CipherDetailsPage { get; set; }
public ViewPage ViewPage { get; set; }
public bool IsLogin => Cipher?.Type == CipherType.Login;
public bool IsIdentity => Cipher?.Type == CipherType.Identity;
public bool IsCard => Cipher?.Type == CipherType.Card;
@@ -290,8 +300,7 @@ namespace Bit.App.Pages
public bool AllowPersonal { get; set; }
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
public void Init()
{
PageTitle = EditMode && !CloneMode ? AppResources.EditItem : AppResources.AddItem;
@@ -412,7 +421,7 @@ namespace Bit.App.Pages
}
if (Cipher.Fields != null)
{
Fields.ResetWithRange(Cipher.Fields?.Select(f => new CipherAddEditPageFieldViewModel(Cipher, f)));
Fields.ResetWithRange(Cipher.Fields?.Select(f => new AddEditPageFieldViewModel(Cipher, f)));
}
}
@@ -500,7 +509,7 @@ namespace Bit.App.Pages
EditMode && !CloneMode ? AppResources.ItemUpdated : AppResources.NewItemCreated);
_messagingService.Send(EditMode && !CloneMode ? "editedCipher" : "addedCipher", Cipher.Id);
if (Page is CipherAddEditPage page && page.FromAutofillFramework)
if (Page is AddEditPage page && page.FromAutofillFramework)
{
// Close and go back to app
_deviceActionService.CloseAutofill();
@@ -509,7 +518,7 @@ namespace Bit.App.Pages
{
if (CloneMode)
{
CipherDetailsPage?.UpdateCipherId(this.Cipher.Id);
ViewPage?.UpdateCipherId(this.Cipher.Id);
}
// if the app is tombstoned then PopModalAsync would throw index out of bounds
if (Page.Navigation?.ModalStack?.Count > 0)
@@ -594,7 +603,7 @@ namespace Bit.App.Pages
public async void UriOptions(LoginUriView uri)
{
if (!(Page as CipherAddEditPage).DoOnce())
if (!(Page as AddEditPage).DoOnce())
{
return;
}
@@ -630,9 +639,9 @@ namespace Bit.App.Pages
Uris.Add(new LoginUriView());
}
public async void FieldOptions(CipherAddEditPageFieldViewModel field)
public async void FieldOptions(AddEditPageFieldViewModel field)
{
if (!(Page as CipherAddEditPage).DoOnce())
if (!(Page as AddEditPage).DoOnce())
{
return;
}
@@ -692,10 +701,10 @@ namespace Bit.App.Pages
}
if (Fields == null)
{
Fields = new ExtendedObservableCollection<CipherAddEditPageFieldViewModel>();
Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>();
}
var type = fieldTypeOptions.FirstOrDefault(f => f.Value == typeSelection).Key;
Fields.Add(new CipherAddEditPageFieldViewModel(Cipher, new FieldView
Fields.Add(new AddEditPageFieldViewModel(Cipher, new FieldView
{
Type = type,
Name = string.IsNullOrWhiteSpace(name) ? null : name,
@@ -823,24 +832,35 @@ namespace Bit.App.Pages
private void TriggerCipherChanged()
{
TriggerPropertyChanged(nameof(Cipher), AdditionalPropertiesToRaiseOnCipherChanged);
TriggerPropertyChanged(nameof(Cipher), _additionalCipherProperties);
}
private async Task CopyTotpClipboardAsync()
private async void CheckPasswordAsync()
{
try
if (!(Page as BaseContentPage).DoOnce())
{
await _clipboardService.CopyTextAsync(Cipher.Login.Totp);
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, AppResources.AuthenticatorKeyScanner));
return;
}
catch (Exception ex)
if (string.IsNullOrWhiteSpace(Cipher.Login?.Password))
{
_logger.Exception(ex);
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.CheckingPassword);
var matches = await _auditService.PasswordLeakedAsync(Cipher.Login.Password);
await _deviceActionService.HideLoadingAsync();
if (matches > 0)
{
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.PasswordExposed,
matches.ToString("N0")));
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.PasswordSafe);
}
}
}
public class CipherAddEditPageFieldViewModel : ExtendedViewModel
public class AddEditPageFieldViewModel : ExtendedViewModel
{
private II18nService _i18nService;
private FieldView _field;
@@ -856,7 +876,7 @@ namespace Bit.App.Pages
nameof(IsLinkedType),
};
public CipherAddEditPageFieldViewModel(CipherView cipher, FieldView field)
public AddEditPageFieldViewModel(CipherView cipher, FieldView field)
{
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_cipher = cipher;

View File

@@ -6,7 +6,6 @@ using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -56,28 +55,21 @@ namespace Bit.App.Pages
_broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) =>
{
try
if (message.Command == "syncStarted")
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted")
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
catch (Exception ex)
else if (message.Command == "syncCompleted")
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
});
@@ -137,11 +129,11 @@ namespace Bit.App.Pages
}
if (_appOptions.FillType.HasValue && _appOptions.FillType != CipherType.Login)
{
var pageForOther = new CipherAddEditPage(type: _appOptions.FillType, fromAutofill: true);
var pageForOther = new AddEditPage(type: _appOptions.FillType, fromAutofill: true);
await Navigation.PushModalAsync(new NavigationPage(pageForOther));
return;
}
var pageForLogin = new CipherAddEditPage(null, CipherType.Login, uri: _vm.Uri, name: _vm.Name,
var pageForLogin = new AddEditPage(null, CipherType.Login, uri: _vm.Uri, name: _vm.Name,
fromAutofill: true);
await Navigation.PushModalAsync(new NavigationPage(pageForLogin));
}

View File

@@ -1,76 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
namespace Bit.App.Pages
{
public abstract class BaseCipherViewModel : BaseViewModel
{
private readonly IAuditService _auditService;
protected readonly IDeviceActionService _deviceActionService;
protected readonly ILogger _logger;
protected readonly IPlatformUtilsService _platformUtilsService;
private CipherView _cipher;
protected abstract string[] AdditionalPropertiesToRaiseOnCipherChanged { get; }
public BaseCipherViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
CheckPasswordCommand = new AsyncCommand(CheckPasswordAsync, allowsMultipleExecutions: false);
}
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value, additionalPropertyNames: AdditionalPropertiesToRaiseOnCipherChanged);
}
public AsyncCommand CheckPasswordCommand { get; }
protected async Task CheckPasswordAsync()
{
try
{
if (string.IsNullOrWhiteSpace(Cipher?.Login?.Password))
{
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.CheckingPassword);
var matches = await _auditService.PasswordLeakedAsync(Cipher.Login.Password);
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(matches > 0
? string.Format(AppResources.PasswordExposed, matches.ToString("N0"))
: AppResources.PasswordSafe);
}
catch (ApiException apiException)
{
_logger.Exception(apiException);
await _deviceActionService.HideLoadingAsync();
if (apiException?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(apiException.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
catch (Exception ex)
{
_logger.Exception(ex);
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
}
}
}
}

View File

@@ -10,6 +10,7 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -156,7 +157,7 @@ namespace Bit.App.Pages
}
if (selection == AppResources.View || string.IsNullOrWhiteSpace(AutofillUrl))
{
var page = new CipherDetailsPage(cipher.Id);
var page = new ViewPage(cipher.Id);
await Page.Navigation.PushModalAsync(new NavigationPage(page));
}
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)

View File

@@ -6,7 +6,6 @@
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:GroupingsPageViewModel"
Title="{Binding PageTitle}"
@@ -54,14 +53,6 @@
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}" />
</DataTemplate>
<DataTemplate x:Key="authenticatorTemplate"
x:DataType="pages:GroupingsPageTOTPListItem">
<controls:AuthenticatorViewCell
Cipher="{Binding Cipher}"
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
TotpSec="{Binding TotpSec}"/>
</DataTemplate>
<DataTemplate x:Key="groupTemplate"
x:DataType="pages:GroupingsPageListItem">
<controls:ExtendedStackLayout Orientation="Horizontal"
@@ -113,7 +104,6 @@
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
HeaderTemplate="{StaticResource headerTemplate}"
CipherTemplate="{StaticResource cipherTemplate}"
AuthenticatorTemplate="{StaticResource authenticatorTemplate}"
GroupTemplate="{StaticResource groupTemplate}" />
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
@@ -140,6 +130,7 @@
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Filter}" />
</StackLayout>
<StackLayout
VerticalOptions="CenterAndExpand"
Padding="20, 0"

View File

@@ -7,7 +7,6 @@ using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -22,7 +21,6 @@ namespace Bit.App.Pages
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly GroupingsPageViewModel _vm;
private readonly string _pageName;
@@ -30,7 +28,7 @@ namespace Bit.App.Pages
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
PreviousPageInfo previousPage = null, bool deleted = false, bool showTotp = false)
PreviousPageInfo previousPage = null, bool deleted = false)
{
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent();
@@ -42,7 +40,6 @@ namespace Bit.App.Pages
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_vm = BindingContext as GroupingsPageViewModel;
_vm.Page = this;
_vm.MainPage = mainPage;
@@ -50,7 +47,6 @@ namespace Bit.App.Pages
_vm.FolderId = folderId;
_vm.CollectionId = collectionId;
_vm.Deleted = deleted;
_vm.ShowTotp = showTotp;
_previousPage = previousPage;
if (pageTitle != null)
{
@@ -72,7 +68,7 @@ namespace Bit.App.Pages
ToolbarItems.Add(_lockItem);
ToolbarItems.Add(_exitItem);
}
if (deleted || showTotp)
if (deleted)
{
_absLayout.Children.Remove(_fab);
ToolbarItems.Remove(_addItem);
@@ -99,28 +95,21 @@ namespace Bit.App.Pages
_broadcasterService.Subscribe(_pageName, async (message) =>
{
try
if (message.Command == "syncStarted")
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted")
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
catch (Exception ex)
else if (message.Command == "syncCompleted")
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
});
@@ -192,11 +181,10 @@ namespace Bit.App.Pages
return false;
}
protected override async void OnDisappearing()
protected override void OnDisappearing()
{
base.OnDisappearing();
IsBusy = false;
_vm.StopCiphersTotpTick().FireAndForget();
_broadcasterService.Unsubscribe(_pageName);
_vm.DisableRefreshing();
_accountAvatar?.OnDisappearing();
@@ -204,54 +192,35 @@ namespace Bit.App.Pages
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
try
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (e.CurrentSelection?.FirstOrDefault() is GroupingsPageTOTPListItem totpItem)
{
await _vm.SelectCipherAsync(totpItem.Cipher);
return;
}
if (!(e.CurrentSelection?.FirstOrDefault() is GroupingsPageListItem item))
{
return;
}
if (item.IsTrash)
{
await _vm.SelectTrashAsync();
}
else if (item.IsTotpCode)
{
await _vm.SelectTotpCodesAsync();
}
else if (item.Cipher != null)
{
await _vm.SelectCipherAsync(item.Cipher);
}
else if (item.Folder != null)
{
await _vm.SelectFolderAsync(item.Folder);
}
else if (item.Collection != null)
{
await _vm.SelectCollectionAsync(item.Collection);
}
else if (item.Type != null)
{
await _vm.SelectTypeAsync(item.Type.Value);
}
return;
}
catch (Exception ex)
if (!(e.CurrentSelection?.FirstOrDefault() is GroupingsPageListItem item))
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
_platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok).FireAndForget();
return;
}
if (item.IsTrash)
{
await _vm.SelectTrashAsync();
}
else if (item.Cipher != null)
{
await _vm.SelectCipherAsync(item.Cipher);
}
else if (item.Folder != null)
{
await _vm.SelectFolderAsync(item.Folder);
}
else if (item.Collection != null)
{
await _vm.SelectCollectionAsync(item.Collection);
}
else if (item.Type != null)
{
await _vm.SelectTypeAsync(item.Type.Value);
}
}
@@ -294,7 +263,7 @@ namespace Bit.App.Pages
}
if (!_vm.Deleted && DoOnce())
{
var page = new CipherAddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
@@ -308,11 +277,11 @@ namespace Bit.App.Pages
await _accountListOverlay.HideAsync();
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId))
{
await Navigation.PushModalAsync(new NavigationPage(new CipherDetailsPage(_previousPage.CipherId)));
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.CipherId)));
}
else if (_previousPage.Page == "edit" && !string.IsNullOrWhiteSpace(_previousPage.CipherId))
{
await Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(_previousPage.CipherId)));
await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_previousPage.CipherId)));
}
_previousPage = null;
}

View File

@@ -2,13 +2,13 @@
namespace Bit.App.Pages
{
public class GroupingsPageListGroup : List<IGroupingsPageListItem>
public class GroupingsPageListGroup : List<GroupingsPageListItem>
{
public GroupingsPageListGroup(string name, int count, bool doUpper = true, bool first = false)
: this(new List<IGroupingsPageListItem>(), name, count, doUpper, first)
: this(new List<GroupingsPageListItem>(), name, count, doUpper, first)
{ }
public GroupingsPageListGroup(IEnumerable<IGroupingsPageListItem> groupItems, string name, int count,
public GroupingsPageListGroup(List<GroupingsPageListItem> groupItems, string name, int count,
bool doUpper = true, bool first = false)
{
AddRange(groupItems);

View File

@@ -17,7 +17,6 @@ namespace Bit.App.Pages
public string ItemCount { get; set; }
public bool FuzzyAutofill { get; set; }
public bool IsTrash { get; set; }
public bool IsTotpCode { get; set; }
public string Name
{
@@ -39,10 +38,6 @@ namespace Bit.App.Pages
{
_name = Collection.Name;
}
else if (IsTotpCode)
{
_name = AppResources.VerificationCodes;
}
else if (Type != null)
{
switch (Type.Value)
@@ -87,10 +82,6 @@ namespace Bit.App.Pages
{
_icon = BitwardenIcons.Collection;
}
else if (IsTotpCode)
{
_icon = BitwardenIcons.Clock;
}
else if (Type != null)
{
switch (Type.Value)

View File

@@ -7,7 +7,6 @@ namespace Bit.App.Pages
public DataTemplate HeaderTemplate { get; set; }
public DataTemplate CipherTemplate { get; set; }
public DataTemplate GroupTemplate { get; set; }
public DataTemplate AuthenticatorTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
@@ -16,16 +15,10 @@ namespace Bit.App.Pages
return HeaderTemplate;
}
if (item is GroupingsPageTOTPListItem)
{
return AuthenticatorTemplate;
}
if (item is GroupingsPageListItem listItem)
{
return listItem.Cipher != null ? CipherTemplate : GroupTemplate;
}
return null;
}
}

View File

@@ -1,123 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class GroupingsPageTOTPListItem : ExtendedViewModel, IGroupingsPageListItem
{
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private readonly ITotpService _totpService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IClipboardService _clipboardService;
private CipherView _cipher;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public int interval { get; set; }
private double _progress;
private string _totpSec;
private string _totpCodeFormatted;
private TotpHelper _totpTickHelper;
public GroupingsPageTOTPListItem(CipherView cipherView, bool websiteIconsEnabled)
{
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
CopyCommand = new AsyncCommand(CopyToClipboardAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
_totpTickHelper = new TotpHelper(cipherView);
}
public AsyncCommand CopyCommand { get; set; }
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public string TotpCodeFormatted
{
get => _totpCodeFormatted;
set => SetProperty(ref _totpCodeFormatted, value,
additionalPropertyNames: new string[]
{
nameof(TotpCodeFormattedStart),
nameof(TotpCodeFormattedEnd),
});
}
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value);
}
public double Progress
{
get => _progress;
set => SetProperty(ref _progress, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
public string TotpCodeFormattedStart => TotpCodeFormatted?.Split(' ')[0];
public string TotpCodeFormattedEnd => TotpCodeFormatted?.Split(' ')[1];
public async Task CopyToClipboardAsync()
{
await _clipboardService.CopyTextAsync(TotpCodeFormatted?.Replace(" ", string.Empty));
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
}
public async Task TotpTickAsync()
{
await _totpTickHelper.GenerateNewTotpValues();
MainThread.BeginInvokeOnMainThread(() =>
{
TotpSec = _totpTickHelper.TotpSec;
Progress = _totpTickHelper.Progress;
TotpCodeFormatted = _totpTickHelper.TotpCodeFormatted;
});
}
}
}

View File

@@ -1,9 +1,7 @@
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.Controls;
using Bit.App.Resources;
@@ -31,16 +29,13 @@ namespace Bit.App.Pages
private bool _showList;
private bool _websiteIconsEnabled;
private bool _syncRefreshing;
private bool _showTotpFilter;
private bool _totpFilterEnable;
private string _noDataText;
private List<CipherView> _allCiphers;
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
private Dictionary<CipherType, int> _typeCounts = new Dictionary<CipherType, int>();
private int _deletedCount = 0;
private CancellationTokenSource _totpTickCts;
private Task _totpTickTask;
private readonly ICipherService _cipherService;
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
@@ -79,9 +74,6 @@ namespace Bit.App.Pages
await LoadAsync();
});
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
@@ -102,7 +94,6 @@ namespace Bit.App.Pages
&& NoFolderCiphers.Count < NoFolderListSize
&& (Collections is null || !Collections.Any());
public List<CipherView> Ciphers { get; set; }
public List<CipherView> TOTPCiphers { get; set; }
public List<CipherView> FavoriteCiphers { get; set; }
public List<CipherView> NoFolderCiphers { get; set; }
public List<FolderView> Folders { get; set; }
@@ -160,12 +151,9 @@ namespace Bit.App.Pages
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowTotp
{
get => _showTotpFilter;
set => SetProperty(ref _showTotpFilter, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public Command RefreshCommand { get; set; }
public Command<CipherView> CipherOptionsCommand { get; set; }
@@ -193,21 +181,18 @@ namespace Bit.App.Pages
return;
}
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget();
await InitVaultFilterAsync(MainPage);
if (MainPage)
{
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
}
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
_doingLoad = true;
LoadedOnce = true;
ShowNoData = false;
Loading = true;
ShowList = false;
ShowAddCipherButton = !Deleted;
var groupedItems = new List<GroupingsPageListGroup>();
var page = Page as GroupingsPage;
@@ -231,8 +216,6 @@ namespace Bit.App.Pages
}
if (MainPage)
{
AddTotpGroupItem(canAccessPremium, groupedItems, uppercaseGroupNames);
groupedItems.Add(new GroupingsPageListGroup(
AppResources.Types, 4, uppercaseGroupNames, !hasFavorites)
{
@@ -289,12 +272,10 @@ namespace Bit.App.Pages
}
if (Ciphers?.Any() ?? false)
{
CreateCipherGroupedItems(groupedItems);
}
if (ShowTotp && (!TOTPCiphers?.Any() ?? false))
{
Page.Navigation.PopAsync();
return;
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted)
.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
}
if (ShowNoFolderCipherGroup)
{
@@ -382,60 +363,6 @@ namespace Bit.App.Pages
}
}
private void AddTotpGroupItem(bool canAccessPremium, List<GroupingsPageListGroup> groupedItems, bool uppercaseGroupNames)
{
if (canAccessPremium && TOTPCiphers?.Any() == true)
{
groupedItems.Insert(0, new GroupingsPageListGroup(
AppResources.Totp, 1, uppercaseGroupNames, false)
{
new GroupingsPageListItem
{
IsTotpCode = true,
Type = CipherType.Login,
ItemCount = TOTPCiphers.Count().ToString("N0")
}
});
}
}
private void CreateCipherGroupedItems(List<GroupingsPageListGroup> groupedItems)
{
var uppercaseGroupNames = _deviceActionService.DeviceType == DeviceType.iOS;
_totpTickCts?.Cancel();
if (ShowTotp)
{
var ciphersListItems = TOTPCiphers.Select(c => new GroupingsPageTOTPListItem(c, true)).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
StartCiphersTotpTick(ciphersListItems);
}
else
{
var ciphersListItems = Ciphers.Where(c => c.IsDeleted == Deleted)
.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
}
}
private void StartCiphersTotpTick(List<GroupingsPageTOTPListItem> ciphersListItems)
{
_totpTickCts?.Cancel();
_totpTickCts = new CancellationTokenSource();
_totpTickTask = new TimerTask(logger, () => ciphersListItems.ForEach(i => i.TotpTickAsync()), _totpTickCts).RunPeriodic();
}
public async Task StopCiphersTotpTick()
{
_totpTickCts?.Cancel();
if (_totpTickTask != null)
{
await _totpTickTask;
}
}
public void DisableRefreshing()
{
Refreshing = false;
@@ -449,7 +376,7 @@ namespace Bit.App.Pages
public async Task SelectCipherAsync(CipherView cipher)
{
var page = new CipherDetailsPage(cipher.Id);
var page = new ViewPage(cipher.Id);
await Page.Navigation.PushModalAsync(new NavigationPage(page));
}
@@ -496,13 +423,6 @@ namespace Bit.App.Pages
await Page.Navigation.PushAsync(page);
}
public async Task SelectTotpCodesAsync()
{
var page = new GroupingsPage(false, CipherType.Login, null, null, AppResources.VerificationCodes, _vaultFilterSelection, null,
false, true);
await Page.Navigation.PushAsync(page);
}
public async Task ExitAsync()
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.ExitConfirmation,
@@ -540,7 +460,6 @@ namespace Bit.App.Pages
NoDataText = AppResources.NoItems;
_allCiphers = await GetAllCiphersAsync();
HasCiphers = _allCiphers.Any();
TOTPCiphers = _allCiphers.Where(c => c.IsDeleted == Deleted && c.Type == CipherType.Login && !string.IsNullOrEmpty(c.Login?.Totp)).ToList();
FavoriteCiphers?.Clear();
NoFolderCiphers?.Clear();
_folderCounts.Clear();
@@ -566,10 +485,6 @@ namespace Bit.App.Pages
Filter = c => c.IsDeleted;
NoDataText = AppResources.NoItemsTrash;
}
else if (ShowTotp)
{
Filter = c => c.Type == CipherType.Login && !c.IsDeleted && !string.IsNullOrEmpty(c.Login?.Totp);
}
else if (Type != null)
{
Filter = c => c.Type == Type.Value && !c.IsDeleted;

View File

@@ -1,26 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<pages:BaseContentPage
<?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.ScanPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:forms="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
xmlns:zxing="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms"
x:Name="_page"
Title="{Binding ScanQrPageTitle}">
<ContentPage.BindingContext>
<pages:ScanPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
Title="{u:I18n ScanQrTitle}">
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
@@ -29,114 +16,67 @@
<Grid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<zxing:ZXingScannerView
x:Name="_zxing"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
AutomationId="zxingScannerView"
IsVisible="{Binding ShowScanner}"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="3"
OnScanResult="OnScanResult"/>
<StackLayout
VerticalOptions="Center"
OnScanResult="OnScanResult">
</zxing:ZXingScannerView>
<Grid
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
IsVisible="{Binding ShowScanner}"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Margin="30,0">
AutomationId="zxingDefaultOverlay">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<forms:SKCanvasView
x:Name="SkCanvasView"
Margin="0,50,0,0"
WidthRequest="250"
HeightRequest="250"
IsVisible="{Binding ShowScanner}"
VerticalOptions="Center"
HorizontalOptions="Center"
PaintSurface="OnCanvasViewPaintSurface"/>
<controls:IconButton
x:Name="_checkIcon"
IsVisible="{Binding ShowScanner}"
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
HorizontalOptions="Center"
VerticalOptions="Start"
FontSize="Title"
TextColor="Transparent"/>
</StackLayout>
<BoxView
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
IsVisible="{Binding ShowScanner, Converter={StaticResource inverseBool}}"
BackgroundColor="{DynamicResource BackgroundColor}"/>
<StackLayout
VerticalOptions="Center"
HorizontalOptions="FillAndExpand"
IsVisible="{Binding ShowScanner, Converter={StaticResource inverseBool}}"
<BoxView
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Margin="30,0">
VerticalOptions="Fill"
HorizontalOptions="FillAndExpand"
BackgroundColor="Black"
Opacity="0.7" />
<Label
Text="{u:I18n EnterKeyManually}"
FontSize="Title" />
<Label
Text="{u:I18n AuthenticatorKeyScanner}"
StyleClass="box-label" />
<controls:MonoEntry
x:Name="_authenticationKeyEntry"
Text="{Binding TotpAuthenticationKey}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
StyleClass="box-value" />
<Button
Text="{u:I18n AddTotp}"
StyleClass="box-button-row"
Clicked="AddAuthenticationKey_OnClicked"/>
</StackLayout>
<BoxView
Text="{u:I18n CameraInstructionTop}"
AutomationId="zxingDefaultOverlay_TopTextLabel"
Grid.Column="0"
Grid.Row="0"
VerticalOptions="Center"
HorizontalOptions="Center"
TextColor="White" />
<BoxView
Grid.Column="0"
Grid.Row="1"
VerticalOptions="Fill"
HorizontalOptions="FillAndExpand"
BackgroundColor="Transparent" />
<BoxView
Grid.Column="0"
Grid.Row="2"
VerticalOptions="Fill"
HorizontalOptions="FillAndExpand"
BackgroundColor="Black"
Opacity="0.7" />
<StackLayout
VerticalOptions="Start"
HorizontalOptions="Center"
Grid.Column="0"
Grid.Row="2">
<Label
Text="{Binding CameraInstructionTop}"
AutomationId="zxingDefaultOverlay_TopTextLabel"
Margin="30,15,30,0"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
StyleClass="text-sm"
TextColor="White" />
</StackLayout>
<Label
FormattedText="{Binding ToggleScanModeLabel}"
Text="{u:I18n CameraInstructionBottom}"
AutomationId="zxingDefaultOverlay_BottomTextLabel"
Grid.Column="0"
Grid.Row="2"
Margin="0,15"
StyleClass="text-sm"
FontAttributes="Bold"
VerticalOptions="End"
HorizontalOptions="Center" >
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="ToggleScanMode_OnTapped" />
</Label.GestureRecognizers>
</Label>
VerticalOptions="Center"
HorizontalOptions="Center"
TextColor="White" />
</Grid>
</Grid>
</pages:BaseContentPage>
</pages:BaseContentPage>

View File

@@ -1,31 +1,19 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class ScanPage : BaseContentPage
{
private ScanPageViewModel ViewModel => BindingContext as ScanPageViewModel;
private readonly Action<string> _callback;
private CancellationTokenSource _autofocusCts;
private Task _continuousAutofocusTask;
private readonly Color _greenColor;
private readonly SKColor _blueSKColor;
private readonly SKColor _greenSKColor;
private readonly Stopwatch _stopwatch;
private bool _pageIsActive;
private bool _qrcodeFound;
private float _scale;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
@@ -44,12 +32,6 @@ namespace Bit.App.Pages
{
ToolbarItems.RemoveAt(0);
}
_greenColor = ThemeManager.GetResourceColor("SuccessColor");
_greenSKColor = _greenColor.ToSKColor();
_blueSKColor = ThemeManager.GetResourceColor("PrimaryColor").ToSKColor();
_stopwatch = new Stopwatch();
_qrcodeFound = false;
}
protected override void OnAppearing()
@@ -76,14 +58,7 @@ namespace Bit.App.Pages
{
if (!autofocusCts.IsCancellationRequested)
{
try
{
_zxing.AutoFocus();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
_zxing.AutoFocus();
}
});
}
@@ -94,70 +69,49 @@ namespace Bit.App.Pages
_logger.Value.Exception(ex);
}
}, autofocusCts.Token);
_pageIsActive = true;
AnimationLoopAsync();
}
protected override async void OnDisappearing()
{
_autofocusCts?.Cancel();
if (_continuousAutofocusTask != null)
{
await _continuousAutofocusTask;
}
_zxing.IsScanning = false;
_pageIsActive = false;
base.OnDisappearing();
}
private async void OnScanResult(ZXing.Result result)
private void OnScanResult(ZXing.Result result)
{
try
// Stop analysis until we navigate away so we don't keep reading barcodes
_zxing.IsAnalyzing = false;
_zxing.IsScanning = false;
var text = result?.Text;
if (!string.IsNullOrWhiteSpace(text))
{
// Stop analysis until we navigate away so we don't keep reading barcodes
_zxing.IsAnalyzing = false;
var text = result?.Text;
if (!string.IsNullOrWhiteSpace(text))
if (text.StartsWith("otpauth://totp"))
{
if (text.StartsWith("otpauth://totp"))
_callback(text);
return;
}
else if (Uri.TryCreate(text, UriKind.Absolute, out Uri uri) &&
!string.IsNullOrWhiteSpace(uri?.Query))
{
var queryParts = uri.Query.Substring(1).ToLowerInvariant().Split('&');
foreach (var part in queryParts)
{
await QrCodeFoundAsync();
_callback(text);
return;
}
else if (Uri.TryCreate(text, UriKind.Absolute, out Uri uri) &&
!string.IsNullOrWhiteSpace(uri?.Query))
{
var queryParts = uri.Query.Substring(1).ToLowerInvariant().Split('&');
foreach (var part in queryParts)
if (part.StartsWith("secret="))
{
if (part.StartsWith("secret="))
{
await QrCodeFoundAsync();
var subResult = part.Substring(7);
if (!string.IsNullOrEmpty(subResult))
{
_callback(subResult.ToUpperInvariant());
}
return;
}
_callback(part.Substring(7)?.ToUpperInvariant());
return;
}
}
}
_callback(null);
}
catch (Exception ex)
{
_logger?.Value?.Exception(ex);
}
}
private async Task QrCodeFoundAsync()
{
_qrcodeFound = true;
Vibration.Vibrate();
await Task.Delay(1000);
_zxing.IsScanning = false;
_callback(null);
}
private async void Close_Clicked(object sender, System.EventArgs e)
@@ -167,89 +121,5 @@ namespace Bit.App.Pages
await Navigation.PopModalAsync();
}
}
private void AddAuthenticationKey_OnClicked(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(ViewModel.TotpAuthenticationKey))
{
_callback(ViewModel.TotpAuthenticationKey);
return;
}
_callback(null);
}
private void ToggleScanMode_OnTapped(object sender, EventArgs e)
{
ViewModel.ToggleScanModeCommand.Execute(null);
if (!ViewModel.ShowScanner)
{
_authenticationKeyEntry.Focus();
}
}
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
var info = args.Info;
var surface = args.Surface;
var canvas = surface.Canvas;
var margins = 20;
var maxSquareSize = (Math.Min(info.Height, info.Width) * 0.9f - margins) * _scale;
var squareSize = maxSquareSize;
var lineSize = squareSize * 0.15f;
var startXPoint = (info.Width / 2) - (squareSize / 2);
var startYPoint = (info.Height / 2) - (squareSize / 2);
canvas.Clear(SKColors.Transparent);
using (var strokePaint = new SKPaint
{
Color = _qrcodeFound ? _greenSKColor : _blueSKColor,
StrokeWidth = 9 * _scale,
StrokeCap = SKStrokeCap.Round,
})
{
canvas.Scale(1, 1);
//top left
canvas.DrawLine(startXPoint, startYPoint, startXPoint, startYPoint + lineSize, strokePaint);
canvas.DrawLine(startXPoint, startYPoint, startXPoint + lineSize, startYPoint, strokePaint);
//bot left
canvas.DrawLine(startXPoint, startYPoint + squareSize, startXPoint, startYPoint + squareSize - lineSize, strokePaint);
canvas.DrawLine(startXPoint, startYPoint + squareSize, startXPoint + lineSize, startYPoint + squareSize, strokePaint);
//top right
canvas.DrawLine(startXPoint + squareSize, startYPoint, startXPoint + squareSize - lineSize, startYPoint, strokePaint);
canvas.DrawLine(startXPoint + squareSize, startYPoint, startXPoint + squareSize, startYPoint + lineSize, strokePaint);
//bot right
canvas.DrawLine(startXPoint + squareSize, startYPoint + squareSize, startXPoint + squareSize - lineSize, startYPoint + squareSize, strokePaint);
canvas.DrawLine(startXPoint + squareSize, startYPoint + squareSize, startXPoint + squareSize, startYPoint + squareSize - lineSize, strokePaint);
}
}
async Task AnimationLoopAsync()
{
try
{
_stopwatch.Start();
while (_pageIsActive)
{
var t = _stopwatch.Elapsed.TotalSeconds % 2 / 2;
_scale = (20 - (1 - (float)Math.Sin(4 * Math.PI * t))) / 20;
SkCanvasView.InvalidateSurface();
await Task.Delay(TimeSpan.FromSeconds(1.0 / 30));
if (_qrcodeFound && _scale > 0.98f)
{
_checkIcon.TextColor = _greenColor;
SkCanvasView.InvalidateSurface();
break;
}
}
}
catch (Exception ex)
{
_logger?.Value?.Exception(ex);
}
finally
{
_stopwatch?.Stop();
}
}
}
}

View File

@@ -1,61 +0,0 @@
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class ScanPageViewModel : BaseViewModel
{
private bool _showScanner = true;
private string _totpAuthenticationKey;
public ScanPageViewModel()
{
ToggleScanModeCommand = new Command(() => ShowScanner = !ShowScanner);
}
public Command ToggleScanModeCommand { get; set; }
public string ScanQrPageTitle => ShowScanner ? AppResources.ScanQrTitle : AppResources.AuthenticatorKeyScanner;
public string CameraInstructionTop => ShowScanner ? AppResources.PointYourCameraAtTheQRCode : AppResources.OnceTheKeyIsSuccessfullyEntered;
public string TotpAuthenticationKey
{
get => _totpAuthenticationKey;
set => SetProperty(ref _totpAuthenticationKey, value,
additionalPropertyNames: new string[]
{
nameof(ToggleScanModeLabel)
});
}
public bool ShowScanner
{
get => _showScanner;
set => SetProperty(ref _showScanner, value,
additionalPropertyNames: new string[]
{
nameof(ToggleScanModeLabel),
nameof(ScanQrPageTitle),
nameof(CameraInstructionTop)
});
}
public FormattedString ToggleScanModeLabel
{
get
{
var fs = new FormattedString();
fs.Spans.Add(new Span
{
Text = ShowScanner ? AppResources.CannotScanQRCode : AppResources.CannotAddAuthenticatorKey,
TextColor = ThemeManager.GetResourceColor("TitleTextColor")
});
fs.Spans.Add(new Span
{
Text = ShowScanner ? AppResources.EnterKeyManually : AppResources.ScanQRCode,
TextColor = ThemeManager.GetResourceColor("ScanningToggleModeTextColor")
});
return fs;
}
}
}
}

View File

@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<pages:BaseContentPage
<?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.CipherDetailsPage"
x:Class="Bit.App.Pages.ViewPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:views="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:CipherDetailsPageViewModel"
x:DataType="pages:ViewPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:CipherDetailsPageViewModel />
<pages:ViewPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
@@ -22,15 +22,15 @@
<u:StringHasValueConverter x:Key="stringHasValue" />
<u:IsNotNullConverter x:Key="notNull" />
<ToolbarItem Text="{u:I18n Collections}"
x:Key="collectionsItem"
x:Name="_collectionsItem"
Clicked="Collections_Clicked"
Order="Secondary" />
x:Key="collectionsItem"
x:Name="_collectionsItem"
Clicked="Collections_Clicked"
Order="Secondary" />
<ToolbarItem Text="{u:I18n MoveToOrganization}"
x:Key="shareItem"
x:Name="_shareItem"
Clicked="Share_Clicked"
Order="Secondary" />
x:Key="shareItem"
x:Name="_shareItem"
Clicked="Share_Clicked"
Order="Secondary" />
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Name="_closeItem" x:Key="closeItem" />
<ToolbarItem Clicked="EditToolbarItem_Clicked" Order="Primary"
@@ -83,7 +83,7 @@
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
@@ -96,7 +96,7 @@
</Grid>
<BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Login.Username, Converter={StaticResource stringHasValue}}" />
<Grid StyleClass="box-row"
<Grid StyleClass="box-row"
IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -126,7 +126,7 @@
Grid.Column="0"
LineBreakMode="CharacterWrap"
IsVisible="{Binding ShowPassword}" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
Command="{Binding CheckPasswordCommand}"
@@ -136,7 +136,7 @@
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CheckPassword}"
IsVisible="{Binding Cipher.ViewPassword}" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
@@ -147,7 +147,7 @@
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding Cipher.ViewPassword}" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
@@ -165,11 +165,10 @@
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
@@ -179,49 +178,29 @@
Grid.Column="0" />
<controls:MonoLabel
Text="{Binding TotpCodeFormatted, Mode=OneWay}"
IsVisible="{Binding ShowUpgradePremiumTotpText, Converter={StaticResource inverseBool}}"
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
VerticalTextAlignment="Start"
VerticalOptions="Start" />
<controls:CircularProgressbarView
Progress="{Binding TotpProgress}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
Grid.Column="0" />
<Label
Text="{Binding TotpSec, Mode=OneWay}"
Style="{DynamicResource textTotp}"
Margin="0, 0, 10, 0"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
StyleClass="text-sm"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand" />
<controls:IconButton
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
IsVisible="{Binding CanAccessPremium}"
CommandParameter="LoginTotp"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyTotp}" />
<Label
Text="{u:I18n PremiumSubscriptionRequired}"
StyleClass="box-footer-label"
IsVisible="{Binding ShowUpgradePremiumTotpText}"
Margin="0,5,0,2"
Grid.Column="0"
Grid.Row="1"
HorizontalOptions="FillAndExpand" />
</Grid>
<BoxView StyleClass="box-row-separator" IsVisible="{Binding ShowTotp}" />
</StackLayout>
@@ -265,7 +244,7 @@
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding ShowCardNumber}" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowCardNumberIcon}"
Command="{Binding ToggleCardNumberCommand}"
@@ -274,7 +253,7 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
@@ -337,7 +316,7 @@
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding ShowCardCode}" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowCardCodeIcon}"
Command="{Binding ToggleCardCodeCommand}"
@@ -346,7 +325,7 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
@@ -550,7 +529,7 @@
<StackLayout StyleClass="box-row">
<controls:SelectableLabel
Text="{Binding Cipher.Notes, Mode=OneWay}"
StyleClass="box-value" />
StyleClass="box-value"/>
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
@@ -561,7 +540,7 @@
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Fields}">
<controls:RepeaterView.ItemTemplate>
<DataTemplate x:DataType="pages:CipherDetailsPageFieldViewModel">
<DataTemplate x:DataType="pages:ViewPageFieldViewModel">
<StackLayout Spacing="0" Padding="0">
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
@@ -592,8 +571,6 @@
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"
@@ -611,7 +588,7 @@
StyleClass="box-value"
IsVisible="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}" />
</StackLayout>
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowHiddenValueIcon}"
Command="{Binding ToggleHiddenValueCommand}"
@@ -659,7 +636,7 @@
StyleClass="box-sub-label"
HorizontalTextAlignment="End"
VerticalTextAlignment="Center" />
<controls:IconButton
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Download}}"
Command="{Binding BindingContext.DownloadAttachmentCommand, Source={x:Reference _page}}"
@@ -723,4 +700,4 @@
</Button>
</AbsoluteLayout>
</pages:BaseContentPage>
</pages:BaseContentPage>

View File

@@ -3,24 +3,23 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class CipherDetailsPage : BaseContentPage
public partial class ViewPage : BaseContentPage
{
private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService;
private CipherDetailsPageViewModel _vm;
private ViewPageViewModel _vm;
public CipherDetailsPage(string cipherId)
public ViewPage(string cipherId)
{
InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_vm = BindingContext as CipherDetailsPageViewModel;
_vm = BindingContext as ViewPageViewModel;
_vm.Page = this;
_vm.CipherId = cipherId;
SetActivityIndicator(_mainContent);
@@ -40,7 +39,7 @@ namespace Bit.App.Pages
}
}
public CipherDetailsPageViewModel ViewModel => _vm;
public ViewPageViewModel ViewModel => _vm;
public void UpdateCipherId(string cipherId)
{
@@ -55,46 +54,39 @@ namespace Bit.App.Pages
IsBusy = true;
}
_broadcasterService.Subscribe(nameof(CipherDetailsPage), async (message) =>
_broadcasterService.Subscribe(nameof(ViewPage), async (message) =>
{
try
if (message.Command == "syncStarted")
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted")
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
{
var success = data["successfully"] as bool?;
if (success.GetValueOrDefault())
{
var task = _vm.LoadAsync(() => AdjustToolbar());
}
}
});
}
else if (message.Command == "selectSaveFileResult")
{
Device.BeginInvokeOnMainThread(() =>
{
var data = message.Data as Tuple<string, string>;
if (data == null)
{
return;
}
_vm.SaveFileSelected(data.Item1, data.Item2);
});
}
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
catch (Exception ex)
else if (message.Command == "syncCompleted")
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
{
var success = data["successfully"] as bool?;
if (success.GetValueOrDefault())
{
var task = _vm.LoadAsync(() => AdjustToolbar());
}
}
});
}
else if (message.Command == "selectSaveFileResult")
{
Device.BeginInvokeOnMainThread(() =>
{
var data = message.Data as Tuple<string, string>;
if (data == null)
{
return;
}
_vm.SaveFileSelected(data.Item1, data.Item2);
});
}
});
await LoadOnAppearedAsync(_scrollView, true, async () =>
@@ -111,8 +103,8 @@ namespace Bit.App.Pages
{
base.OnDisappearing();
IsBusy = false;
_vm.StopCiphersTotpTick().FireAndForget();
_broadcasterService.Unsubscribe(nameof(CipherDetailsPage));
_broadcasterService.Unsubscribe(nameof(ViewPage));
_vm.CleanUp();
}
private async void PasswordHistory_Tapped(object sender, System.EventArgs e)
@@ -140,7 +132,7 @@ namespace Bit.App.Pages
{
return;
}
await Navigation.PushModalAsync(new NavigationPage(new CipherAddEditPage(_vm.CipherId)));
await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_vm.CipherId)));
}
}
}
@@ -212,7 +204,7 @@ namespace Bit.App.Pages
{
return;
}
var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this);
var page = new AddEditPage(_vm.CipherId, cloneMode: true, viewPage: this);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
@@ -267,7 +259,7 @@ namespace Bit.App.Pages
}
else if (selection == AppResources.Clone)
{
var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this);
var page = new AddEditPage(_vm.CipherId, cloneMode: true, viewPage: this);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}

View File

@@ -1,9 +1,7 @@
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;
@@ -13,24 +11,26 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class CipherDetailsPageViewModel : BaseCipherViewModel
public class ViewPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly IStateService _stateService;
private readonly IAuditService _auditService;
private readonly ITotpService _totpService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService;
private readonly IEventService _eventService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly ILocalizeService _localizeService;
private readonly IClipboardService _clipboardService;
private List<CipherDetailsPageFieldViewModel> _fields;
private CipherView _cipher;
private List<ViewPageFieldViewModel> _fields;
private bool _canAccessPremium;
private bool _showPassword;
private bool _showCardNumber;
@@ -44,62 +44,67 @@ namespace Bit.App.Pages
private byte[] _attachmentData;
private string _attachmentFilename;
private bool _passwordReprompted;
private TotpHelper _totpTickHelper;
private CancellationTokenSource _totpTickCancellationToken;
private Task _totpTickTask;
public CipherDetailsPageViewModel()
public ViewPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyFieldCommand = new AsyncCommand<FieldView>(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyCommand = new Command<string>((id) => CopyAsync(id, null));
CopyUriCommand = new Command<LoginUriView>(CopyUri);
CopyFieldCommand = new Command<FieldView>(CopyField);
LaunchUriCommand = new Command<LoginUriView>(LaunchUri);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode);
DownloadAttachmentCommand = new AsyncCommand<AttachmentView>(DownloadAttachmentAsync, allowsMultipleExecutions: false);
CheckPasswordCommand = new Command(CheckPasswordAsync);
DownloadAttachmentCommand = new Command<AttachmentView>(DownloadAttachmentAsync);
PageTitle = AppResources.ViewItem;
}
public ICommand CopyCommand { get; set; }
public ICommand CopyUriCommand { get; set; }
public ICommand CopyFieldCommand { get; set; }
public Command CopyCommand { get; set; }
public Command CopyUriCommand { get; set; }
public Command CopyFieldCommand { get; set; }
public Command LaunchUriCommand { get; set; }
public Command TogglePasswordCommand { get; set; }
public Command ToggleCardNumberCommand { get; set; }
public Command ToggleCardCodeCommand { get; set; }
public AsyncCommand<AttachmentView> DownloadAttachmentCommand { get; set; }
public Command CheckPasswordCommand { get; set; }
public Command DownloadAttachmentCommand { get; set; }
public string CipherId { get; set; }
protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[]
public CipherView Cipher
{
nameof(IsLogin),
nameof(IsIdentity),
nameof(IsCard),
nameof(IsSecureNote),
nameof(ShowUris),
nameof(ShowAttachments),
nameof(ShowTotp),
nameof(ColoredPassword),
nameof(UpdatedText),
nameof(PasswordUpdatedText),
nameof(PasswordHistoryText),
nameof(ShowIdentityAddress),
nameof(IsDeleted),
nameof(CanEdit),
nameof(ShowUpgradePremiumTotpText)
};
public List<CipherDetailsPageFieldViewModel> Fields
get => _cipher;
set => SetProperty(ref _cipher, value,
additionalPropertyNames: new string[]
{
nameof(IsLogin),
nameof(IsIdentity),
nameof(IsCard),
nameof(IsSecureNote),
nameof(ShowUris),
nameof(ShowAttachments),
nameof(ShowTotp),
nameof(ColoredPassword),
nameof(UpdatedText),
nameof(PasswordUpdatedText),
nameof(PasswordHistoryText),
nameof(ShowIdentityAddress),
nameof(IsDeleted),
nameof(CanEdit),
});
}
public List<ViewPageFieldViewModel> Fields
{
get => _fields;
set => SetProperty(ref _fields, value);
@@ -198,22 +203,21 @@ namespace Bit.App.Pages
return fs;
}
}
public bool ShowUpgradePremiumTotpText => !CanAccessPremium && ShowTotp;
public bool ShowUris => IsLogin && Cipher.Login.HasUris;
public bool ShowIdentityAddress => IsIdentity && (
!string.IsNullOrWhiteSpace(Cipher.Identity.Address1) ||
!string.IsNullOrWhiteSpace(Cipher.Identity.City) ||
!string.IsNullOrWhiteSpace(Cipher.Identity.Country));
public bool ShowAttachments => Cipher.HasAttachments && (CanAccessPremium || Cipher.OrganizationId != null);
public bool ShowTotp => IsLogin && !string.IsNullOrWhiteSpace(Cipher.Login.Totp);
public bool ShowTotp => IsLogin && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
!string.IsNullOrWhiteSpace(TotpCodeFormatted);
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string TotpCodeFormatted
{
get => _canAccessPremium ? _totpCodeFormatted : string.Empty;
get => _totpCodeFormatted;
set => SetProperty(ref _totpCodeFormatted, value,
additionalPropertyNames: new string[]
{
@@ -223,11 +227,7 @@ namespace Bit.App.Pages
public string TotpSec
{
get => _totpSec;
set => SetProperty(ref _totpSec, value,
additionalPropertyNames: new string[]
{
nameof(TotpProgress)
});
set => SetProperty(ref _totpSec, value);
}
public bool TotpLow
{
@@ -238,12 +238,12 @@ namespace Bit.App.Pages
Page.Resources["textTotp"] = ThemeManager.Resources()[value ? "text-danger" : "text-default"];
}
}
public double TotpProgress => string.IsNullOrEmpty(TotpSec) ? 0 : double.Parse(TotpSec) * 100 / 30;
public bool IsDeleted => Cipher.IsDeleted;
public bool CanEdit => !Cipher.IsDeleted;
public async Task<bool> LoadAsync(Action finishedLoadingAction = null)
{
CleanUp();
var cipher = await _cipherService.GetAsync(CipherId);
if (cipher == null)
{
@@ -252,15 +252,24 @@ namespace Bit.App.Pages
}
Cipher = await cipher.DecryptAsync();
CanAccessPremium = await _stateService.CanAccessPremiumAsync();
Fields = Cipher.Fields?.Select(f => new CipherDetailsPageFieldViewModel(this, Cipher, f)).ToList();
Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(this, Cipher, f)).ToList();
if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
(Cipher.OrganizationUseTotp || CanAccessPremium))
{
_totpTickHelper = new TotpHelper(Cipher);
_totpTickCancellationToken?.Cancel();
_totpTickCancellationToken = new CancellationTokenSource();
_totpTickTask = new TimerTask(_logger, StartCiphersTotpTick, _totpTickCancellationToken).RunPeriodic();
await TotpUpdateCodeAsync();
var interval = _totpService.GetTimeInterval(Cipher.Login.Totp);
await TotpTickAsync(interval);
_totpInterval = DateTime.UtcNow;
Device.StartTimer(new TimeSpan(0, 0, 1), () =>
{
if (_totpInterval == null)
{
return false;
}
var task = TotpTickAsync(interval);
return true;
});
}
if (_previousCipherId != CipherId)
{
@@ -271,27 +280,9 @@ namespace Bit.App.Pages
return true;
}
private async void StartCiphersTotpTick()
public void CleanUp()
{
try
{
await _totpTickHelper.GenerateNewTotpValues();
TotpSec = _totpTickHelper.TotpSec;
TotpCodeFormatted = _totpTickHelper.TotpCodeFormatted;
}
catch (Exception ex)
{
_logger.Exception(ex);
}
}
public async Task StopCiphersTotpTick()
{
_totpTickCancellationToken?.Cancel();
if (_totpTickTask != null)
{
await _totpTickTask;
}
_totpInterval = null;
}
public async void TogglePassword()
@@ -460,52 +451,86 @@ namespace Bit.App.Pages
}
}
private async Task DownloadAttachmentAsync(AttachmentView attachment)
private async void CheckPasswordAsync()
{
if (!(Page as BaseContentPage).DoOnce())
{
return;
}
if (string.IsNullOrWhiteSpace(Cipher.Login?.Password))
{
return;
}
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.CheckingPassword);
var matches = await _auditService.PasswordLeakedAsync(Cipher.Login.Password);
await _deviceActionService.HideLoadingAsync();
if (matches > 0)
{
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.PasswordExposed,
matches.ToString("N0")));
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.PasswordSafe);
}
}
private async void DownloadAttachmentAsync(AttachmentView attachment)
{
if (!(Page as BaseContentPage).DoOnce())
{
return;
}
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return;
}
if (Cipher.OrganizationId == null && !CanAccessPremium)
{
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
return;
}
if (attachment.FileSize >= 10485760) // 10 MB
{
var confirmed = await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.AttachmentLargeWarning, attachment.SizeName), null,
AppResources.Yes, AppResources.No);
if (!confirmed)
{
return;
}
}
var canOpenFile = true;
if (!_deviceActionService.CanOpenFile(attachment.FileName))
{
if (Device.RuntimePlatform == Device.iOS)
{
// iOS is currently hardcoded to always return CanOpenFile == true, but should it ever return false
// for any reason we want to be sure to catch it here.
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToOpenFile);
return;
}
canOpenFile = false;
}
if (!await PromptPasswordAsync())
{
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
try
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle);
return;
}
if (Cipher.OrganizationId == null && !CanAccessPremium)
{
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
return;
}
if (attachment.FileSize >= 10485760) // 10 MB
{
var confirmed = await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.AttachmentLargeWarning, attachment.SizeName), null,
AppResources.Yes, AppResources.No);
if (!confirmed)
{
return;
}
}
var canOpenFile = true;
if (!_deviceActionService.CanOpenFile(attachment.FileName))
{
if (Device.RuntimePlatform == Device.iOS)
{
// iOS is currently hardcoded to always return CanOpenFile == true, but should it ever return false
// for any reason we want to be sure to catch it here.
await _platformUtilsService.ShowDialogAsync(AppResources.UnableToOpenFile);
return;
}
canOpenFile = false;
}
if (!await PromptPasswordAsync())
{
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId);
await _deviceActionService.HideLoadingAsync();
if (data == null)
@@ -532,11 +557,9 @@ namespace Bit.App.Pages
OpenAttachment(data, attachment);
}
}
catch (Exception ex)
catch
{
_logger.Exception(ex);
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
}
}
@@ -593,7 +616,7 @@ namespace Bit.App.Pages
_attachmentFilename = null;
}
private async Task CopyAsync(string id, string text = null)
private async void CopyAsync(string id, string text = null)
{
if (_passwordRepromptService.ProtectedFields.Contains(id) && !await PromptPasswordAsync())
{
@@ -613,7 +636,7 @@ namespace Bit.App.Pages
}
else if (id == "LoginTotp")
{
text = TotpCodeFormatted.Replace(" ", string.Empty);
text = _totpCode;
name = AppResources.VerificationCodeTotp;
}
else if (id == "LoginUri")
@@ -657,6 +680,16 @@ namespace Bit.App.Pages
}
}
private void CopyUri(LoginUriView uri)
{
CopyAsync("LoginUri", uri.Uri);
}
private void CopyField(FieldView field)
{
CopyAsync(field.Type == Core.Enums.FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value);
}
private void LaunchUri(LoginUriView uri)
{
if (uri.CanLaunch && (Page as BaseContentPage).DoOnce())
@@ -676,15 +709,15 @@ namespace Bit.App.Pages
}
}
public class CipherDetailsPageFieldViewModel : ExtendedViewModel
public class ViewPageFieldViewModel : ExtendedViewModel
{
private II18nService _i18nService;
private CipherDetailsPageViewModel _vm;
private ViewPageViewModel _vm;
private FieldView _field;
private CipherView _cipher;
private bool _showHiddenValue;
public CipherDetailsPageFieldViewModel(CipherDetailsPageViewModel vm, CipherView cipher, FieldView field)
public ViewPageFieldViewModel(ViewPageViewModel vm, CipherView cipher, FieldView field)
{
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_vm = vm;
@@ -700,7 +733,6 @@ namespace Bit.App.Pages
additionalPropertyNames: new string[]
{
nameof(ValueText),
nameof(ValueAccessibilityText),
nameof(IsBooleanType),
nameof(IsHiddenType),
nameof(IsTextType),
@@ -724,7 +756,7 @@ namespace Bit.App.Pages
{
if (IsBooleanType)
{
return _field.BoolValue ? BitwardenIcons.CheckSquare : BitwardenIcons.Square;
return _field.Value == "true" ? BitwardenIcons.Square : BitwardenIcons.CheckSquare;
}
else if (IsLinkedType)
{
@@ -738,19 +770,6 @@ namespace Bit.App.Pages
}
}
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; }

File diff suppressed because it is too large Load Diff

View File

@@ -276,16 +276,16 @@
<value>Is u seker u wil uitteken?</value>
</data>
<data name="RemoveAccount" xml:space="preserve">
<value>Verwyder rekening</value>
<value>Remove Account</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Is u seker u wil hierdie rekening verwyder?</value>
<value>Are you sure you want to remove this account?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Rekening reeds toegevoeg</value>
<value>Account Already Added</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Wil u nou daarheen wissel?</value>
<value>Would you like to switch to it now?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Hoofwagwoord</value>
@@ -299,10 +299,6 @@
<value>My kluis</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Naam</value>
<comment>Label for an entity name.</comment>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Geaktiveer</value>
</data>
<data name="Off" xml:space="preserve">
<value>Af</value>
</data>
<data name="On" xml:space="preserve">
<value>Aan</value>
</data>
<data name="Status" xml:space="preserve">
<value>Status</value>
</data>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Kan nie waarmerksleutel lees nie.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Point your camera at the QR Code.
Scanning will happen automatically.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Skandering gebeur outomaties.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Rig u kamera op die QR-kode.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>Skandeer QR-kode</value>
@@ -915,11 +907,11 @@ Scanning will happen automatically.</value>
<data name="CopyTotp" xml:space="preserve">
<value>Kopieer TOTP</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<value>Indien n aantekening n waarmerksleutel het, kopieer die TOTP-bevestigingskope na u knipbord wanneer u die aantekening outo-invul.</value>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Indien u aantekening aan n bevestigingskode gekoppel is, word die TOTP-bevestigingskode outomaties na die knipbord gekopieer by die aantekening se outovul.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>Kopieer TOTP outomaties</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Deaktiveer outomatiese kopieëring van TOTP</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>n Premie-lidmaatskap is nodig om hierdie funksie te gebruik.</value>
@@ -1136,11 +1128,11 @@ Scanning will happen automatically.</value>
<data name="Expiration" xml:space="preserve">
<value>Vervaldatum</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Toon webwerfikone</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Deaktiveer webwerfikone</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Toon n herkenbare prent langs elke aantekening.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Webwerfikone verskaf n herkenbare beeld langs elke aantekenitem in u kluis.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>Ikoonbedienerbronadres</value>
@@ -1380,15 +1372,11 @@ Scanning will happen automatically.</value>
<data name="SearchCollection" xml:space="preserve">
<value>Deursoek versameling</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Deursoek lêer-Sends</value>
<data name="SearchFolder" xml:space="preserve">
<value>Deursoek vouer</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Deursoek teks-Sends</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Soek {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Deursoek tipe</value>
</data>
<data name="Type" xml:space="preserve">
<value>Tipe</value>
@@ -1549,12 +1537,6 @@ Scanning will happen automatically.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>Verstek (Stelsel)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Verstekdonkertema</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Kies welke donkertema om te gebruik as stelseltema wanneer u toestel se donkertema geaktiveer is.</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Kopieer notas</value>
</data>
@@ -1571,21 +1553,17 @@ Scanning will happen automatically.</value>
<value>Swart</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>Versperde URIs</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Versperde URIs vir outovul</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>URIs op die swartlys word nie outomaties ingevul nie. Die lys moet met kommas geskei wees. Bv. “https://twitter.com, androidapp://com.twitter.android”.</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Outovul sal nie vir versperde URIs gebied word nie. Skei meerdere URIs met n komma. Byvoorbeeld: “https://twitter.com, androidapp://com.twitter.android”.</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Deaktiveer bewaarpor</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Vra om aantekening toe te voeg</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Vra om n item toe te voeg indien een nie in u kluis gevind word nie.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>Die “bewaarpor” vra u outomaties om nuwe items in u kluis te bewaar wanneer u dit vir die eerste maal invoer.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>Nadat toep herbegin is</value>
@@ -1878,9 +1856,6 @@ Scanning will happen automatically.</value>
<value>n Vriendelike naam om hierdie Send te beskryf.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Teks</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Teks</value>
</data>
@@ -1897,18 +1872,6 @@ Scanning will happen automatically.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>Die lêer wat u wil verstuur.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>Lêertipe is gekies.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>Lêertipe is nie gekies nie, tik om te kies.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Tekstipe is gekies.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Tekstipe is nie gekies nie, tik om te kies.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Skrapdatum</value>
</data>
@@ -2142,28 +2105,28 @@ Scanning will happen automatically.</value>
<value>Een of meer organisasiebeleide verhoed u om u persoonlike kluis uit te stuur.</value>
</data>
<data name="AddAccount" xml:space="preserve">
<value>Voeg rekening toe</value>
<value>Add Account</value>
</data>
<data name="AccountUnlocked" xml:space="preserve">
<value>Ontgrendel</value>
<value>Unlocked</value>
</data>
<data name="AccountLocked" xml:space="preserve">
<value>Vergrendel</value>
<value>Locked</value>
</data>
<data name="AccountLoggedOut" xml:space="preserve">
<value>Uitgeteken</value>
<value>Logged Out</value>
</data>
<data name="AccountSwitchedAutomatically" xml:space="preserve">
<value>Oorgeskakel na volgende beskikbare rekening</value>
<value>Switched to next available account</value>
</data>
<data name="AccountLockedSuccessfully" xml:space="preserve">
<value>Rekening is vergrendel</value>
<value>Account Locked</value>
</data>
<data name="AccountLoggedOutSuccessfully" xml:space="preserve">
<value>Rekening suksesvol uitgeteken</value>
<value>Account logged out successfully</value>
</data>
<data name="AccountRemovedSuccessfully" xml:space="preserve">
<value>Rekening suksesvol verwyder</value>
<value>Account removed successfully</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Skrap rekening</value>
@@ -2184,7 +2147,7 @@ Scanning will happen automatically.</value>
<value>Ongeldige bevestigingskode.</value>
</data>
<data name="RequestOTP" xml:space="preserve">
<value>Versoek eenmalige wagwoord</value>
<value>Request one-time password</value>
</data>
<data name="SendCode" xml:space="preserve">
<value>Verstuur kode</value>
@@ -2193,124 +2156,24 @@ Scanning will happen automatically.</value>
<value>Verstuur</value>
</data>
<data name="CopySendLinkOnSave" xml:space="preserve">
<value>Kopieer Send-skakel by bewaar</value>
<value>Copy Send link on save</value>
</data>
<data name="SendingCode" xml:space="preserve">
<value>Verstuur tans kode</value>
<value>Sending code</value>
</data>
<data name="Verifying" xml:space="preserve">
<value>Bevestiging</value>
<value>Verifying</value>
</data>
<data name="ResendCode" xml:space="preserve">
<value>Stuur kode weer</value>
<value>Resend Code</value>
</data>
<data name="AVerificationCodeWasSentToYourEmail" xml:space="preserve">
<value>n Bevestigingskakel is na u e-pos gestuur</value>
<value>A verification code was sent to your email</value>
</data>
<data name="AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain" xml:space="preserve">
<value>n fout het voorgekom toe n bevestigingskakel na u e-pos gestuur is. Probeer asb. weer</value>
<value>An error occurred while sending a verification code to your email. Please try again</value>
</data>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Voer die bevestigingskakel wat na u e-pos gestuur is, in</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Dien uitbomverslae in</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Help Bitwarden om toepstabiliteit te verbeter deur uitbomverslae in te dien.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Opsies is uitgevou, tik om in te vou.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Opsies is ingevou, tik om uit te vou.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Hoofletters (A tot Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Kleinletters (A tot Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Syfers (0 tot 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Spesiale karakters (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Tik om terug te gaan</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Wagwoord is sigbaar, tik om te verberg.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Wagwoord is nie sigbaar, tik om te toon.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Filter items per kluis</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>Alle kluise</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Kluise</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Kluis: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Verification Codes</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Premium subscription required</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Cannot add authenticator key? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>Scan QR Code</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>Cannot scan QR Code? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Authenticator Key</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Enter Key Manually</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>Add TOTP</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>Set up TOTP</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Once the key is successfully entered,
select Add TOTP to store the key safely</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Deur u vergrendelopsies na “Nooit” te stel, is u kluis beskikbaar aan enigeen met toegang tot u toestel. Indien u hierdie opsie gebruik moet u seker maak dat u u toestel voldoende beskerm.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>Een of meer ingevoerde bronadresse is ongeldig. Hersien dit asb. en probeer weer bewaar.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>Ons kon nie u versoek verwerk nie. Probeer asb. weer of kontak ons.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Laat skermopname toe</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Is u seker u wil skermopname aktiveer?</value>
<value>Enter the verification code that was sent to your email</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

View File

@@ -299,10 +299,6 @@
<value>Anbarım</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Kimlik təsdiqləyici</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Ad</value>
<comment>Label for an entity name.</comment>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Fəallaşdırıldı</value>
</data>
<data name="Off" xml:space="preserve">
<value>Bağlı</value>
</data>
<data name="On" xml:space="preserve">
<value>Açıq</value>
</data>
<data name="Status" xml:space="preserve">
<value>Vəziyyət</value>
</data>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Kimlik təsdiqləyici açarı oxuna bilmir.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Kameranızı QR koduna yönəldin.
Skan prosesi avtomatik baş tutacaq.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Skan avtomatik olaraq icra ediləcək.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Kameranı QR koduna yönləndirin.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>QR kodu skan edin</value>
@@ -915,11 +907,11 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="CopyTotp" xml:space="preserve">
<value>TOTP-ni kopyala</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<value>Bir girişin, kimlik təsdiqləyici açarı varsa, giriş məlumatları avto-doldurulanda TOTP təsdiqləmə kodunu kopyalayın.</value>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Hesabınıza əlavə edilən kimlik təsdiqləyici açarı varsa, giriş məlumatları avto-doldurulanda TOTP təsdiqləmə kodu da avtomatik olaraq lövhəyə kopyalanacaq.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>TOTP-ni avtomatik kopyala</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Avtomatik TOTP kopyalamasını sıradan çıxart</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>Bu özəlliyi istifadə etmək üçün premium üzvlük lazımdır.</value>
@@ -1136,11 +1128,11 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="Expiration" xml:space="preserve">
<value>Bitmə vaxtı</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Veb sayt nişanlarını göstər</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Veb sayt nişanlarını sıradan çıxart</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Hər girişin yanında tanına bilən təsvir göstər.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Veb sayt nişanları, anbarınızda hər bir giriş elementinin yanında tanımağınıza kömək edən bir təsvir təqdim edir.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>Nişan server URL-si</value>
@@ -1380,15 +1372,11 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="SearchCollection" xml:space="preserve">
<value>Kolleksiya axtar</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Fayl "Send"lərini axtar</value>
<data name="SearchFolder" xml:space="preserve">
<value>Qovluq axtar</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Mətn "Send"lərini axtar</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Axtar: {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Axtarış növü</value>
</data>
<data name="Type" xml:space="preserve">
<value>Növ</value>
@@ -1549,12 +1537,6 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>İlkin (Sistem)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>İlkin tünd tema</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Cihazınızda tünd rejim fəal olanda İlkin (Sistem) temanı istifadə edərkən istifadə ediləcək tünd temanı seçin</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Qeydləri kopyala</value>
</data>
@@ -1571,21 +1553,17 @@ Skan prosesi avtomatik baş tutacaq.</value>
<value>Qara</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>Qara siyahıdakı URI-lər</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Əngəllənən URI-lərin avto-doldurulması</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>Qara siyahıdakı URI-lərə avto-doldurma təklif edilməyəcək. Siyahı vergüllə ayrılmalıdır. Məs. "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Əngəllənən URI-lər üçün avto-doldurma təklif edilmir. Çoxlu URI-ni vergüllə ayırır. Nümunə: "https://twitter.com, androidapp://com.twitter.android".</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Saxlama istəyini sıradan çıxart</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Giriş əlavə etmək üçün soruş</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Anbarınızda yoxdursa, bir element əlavə etməyi soruşun.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>"Saxlama istəyi", ilk dəfə istifadə etdiyiniz məlumatları anbarda saxlamaq istəyib-istəmədiyinizi avtomatik olaraq soruşur.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>Tətbiq yenidən başladılanda</value>
@@ -1878,9 +1856,6 @@ Skan prosesi avtomatik baş tutacaq.</value>
<value>Bu "Send"i açıqlayan bir ad.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Mətn</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Mətn</value>
</data>
@@ -1897,18 +1872,6 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>Göndərmək istədiyiniz fayl.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>Fayl növü seçildi.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>Fayl növü seçilmədi, seçmək üçün toxunun.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Mətn növü seçildi.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Mətn növü seçilmədi, seçmək üçün toxunun.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Silinmə tarixi</value>
</data>
@@ -2213,103 +2176,4 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>E-poçtunuza göndərilmiş təsdiqləmə kodunu daxil edin</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Çökmə jurnallarını göndər</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Çökmə hesabatlarını göndərərək Bitwarden-in tətbiq stabilliyini yaxşılaşdırmasına kömək edin.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Seçimlər genişləndirildi, yığcamlaşdırmaq üçün toxunun.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Seçimlər yığcamlaşdırıldı, genişləndirmək üçün toxunun.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Böyük hərf (A-Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Kiçik hərf (Z-A)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Rəqəmlər (0-9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Xüsusi simvollar (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Geriyə getmək üçün toxun</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Parol görünür, gizlətmək üçün toxunun.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Parol görünmür, göstərmək üçün toxunun.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Elementləri anbara görə filtrlə</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>Bütün anbarlar</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Anbarlar</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Anbar: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>Hamısı</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Təsdiqləmə kodları</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Premium abunəlik tələb olunur</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Kimlik təsdiqləyici açarı oxuna bilmir? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>QR kodu skan edin</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>QR kodunu skan edə bilmədiniz? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Kimlik təsdiqləyici açarı</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Kodu əllə daxil et</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>TOTP əlavə et</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>TOTP quraşdır</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Açar uğurla daxil edildikdən sonra, açarı güvənli şəkildə saxlamaq üçün "TOTP əlavə et"i seçin</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Kilid seçimlərini "Heç vaxt" olaraq tənzimləmək, anbarınızı cihazınıza müraciəti olan hər kəsə əlçatan edir. Bu seçimi istifadə etsəniz, cihazınızı düzgün qoruduğunuza əmin olmalısınız.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>Daxil edilən bir və ya daha çox URL yararsızdır. Zəhmət olmasa nəzər salın və yenidən saxlamağa çalışın.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>Tələbinizi emal edə bilmədik. Zəhmət olmasa yenidən sınayın və ya bizimlə əlaqə saxlayın.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Ekranı çəkməyə icazə ver</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Ekranın çəkilməsini fəallaşdırmaq istədiyinizə əminsiniz?</value>
</data>
</root>

View File

@@ -276,16 +276,16 @@
<value>Вы ўпэўнены, што хочаце выйсці?</value>
</data>
<data name="RemoveAccount" xml:space="preserve">
<value>Выдаліць уліковы запіс</value>
<value>Remove Account</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Вы ўпэўнены, што хочаце выдаліць уліковы запіс?</value>
<value>Are you sure you want to remove this account?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Уліковы запіс ужо дададзены</value>
<value>Account Already Added</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Хочаце пераключыцца на яго зараз?</value>
<value>Would you like to switch to it now?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Асноўны пароль</value>
@@ -299,10 +299,6 @@
<value>Маё сховішча</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Назва</value>
<comment>Label for an entity name.</comment>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Уключана</value>
</data>
<data name="Off" xml:space="preserve">
<value>Off</value>
</data>
<data name="On" xml:space="preserve">
<value>On</value>
</data>
<data name="Status" xml:space="preserve">
<value>Стан</value>
</data>
@@ -808,7 +798,7 @@
<value>Вы шукаеце элемент аўтазапаўнення для "{0}".</value>
</data>
<data name="LearnOrg" xml:space="preserve">
<value>Learn about organizations</value>
<value>Learn About Organizations</value>
</data>
<data name="CannotOpenApp" xml:space="preserve">
<value>Немагчыма адкрыць праграму "{0}".</value>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Немагчыма прачытаць ключ праверкі аутэнтычнасці.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Point your camera at the QR Code.
Scanning will happen automatically.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Сканіраванне будзе адбывацца аўтаматычна.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Навядзіце камеру на QR-код.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>Сканаваць QR -код</value>
@@ -915,11 +907,11 @@ Scanning will happen automatically.</value>
<data name="CopyTotp" xml:space="preserve">
<value>Капіяваць TOTP</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<value>If a login has an authenticator key, copy the TOTP verification code to your clip-board when you auto-fill the login.</value>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Калі да вашых уліковых даных прымацаваны ключ праверкі сапраўднасці, то код пацвярджэння TOTP будзе скапіяваны пры аўтазапаўненні ўліковых даных.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>Copy TOTP automatically</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Адключыць аўтаматычнае капіяванне TOTP</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>Для выкарыстання гэтай функцыі патрабуецца прэміяльны статус.</value>
@@ -1136,11 +1128,11 @@ Scanning will happen automatically.</value>
<data name="Expiration" xml:space="preserve">
<value>Тэрмін дзеяння</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Show website icons</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Адключыць значкі вэб-сайтаў</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Show a recognizable image next to each login.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Значкі вэб-сайтаў паказваюцца з кожным элементам у вашым сховішчы.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>URL-адрас сервера значкоў</value>
@@ -1380,15 +1372,11 @@ Scanning will happen automatically.</value>
<data name="SearchCollection" xml:space="preserve">
<value>Пошук у калекцыі</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Search File Sends</value>
<data name="SearchFolder" xml:space="preserve">
<value>Пошук у папцы</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Search Text Sends</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Search {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Пошук па тыпу</value>
</data>
<data name="Type" xml:space="preserve">
<value>Тып</value>
@@ -1549,12 +1537,6 @@ Scanning will happen automatically.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>Default (System)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Default dark theme</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Choose the dark theme to use when using Default (System) theme while your device's dark mode is enabled.</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Капіяваць нататк</value>
</data>
@@ -1571,21 +1553,17 @@ Scanning will happen automatically.</value>
<value>Чорная</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>URI у чорным спісе</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Auto-fill blocked URIs</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>Аўтазапаўненне не будзе прапаноўвацца для URI з чорнага спіса. Элементы гэтага спіса трэба падзяляць коскай. Напрыклад: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Выключыць запыт на захаванне.</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Ask to add login</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Ask to add an item if one isn't found in your vault.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>Апавяшчэнне аб захаванні аўтаматычна прапануе вам захаваць новыя элементы ў сховішчы пасля іх дадання.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>Пры перазапуску</value>
@@ -1878,9 +1856,6 @@ Scanning will happen automatically.</value>
<value>Зразумелая назва для апісання адпраўлення</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Тэкст</value>
</data>
@@ -1897,18 +1872,6 @@ Scanning will happen automatically.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>Файл, які вы хочаце адправіць.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>File type is selected.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>File type is not selected, tap to select.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Text type is selected.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Text type is not selected, tap to select.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Дата выдалення</value>
</data>
@@ -2166,13 +2129,13 @@ Scanning will happen automatically.</value>
<value>Account removed successfully</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Delete account</value>
<value>Delete Account</value>
</data>
<data name="DeletingYourAccountIsPermanent" xml:space="preserve">
<value>Deleting your account is permanent</value>
</data>
<data name="DeleteAccountExplanation" xml:space="preserve">
<value>Your account and all vault data will be erased and unrecoverable. Are you sure you want to continue?</value>
<value>Your account and all associated data will be erased and unrecoverable. Are you sure you want to continue?</value>
</data>
<data name="DeletingYourAccount" xml:space="preserve">
<value>Deleting your account</value>
@@ -2213,104 +2176,4 @@ Scanning will happen automatically.</value>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Enter the verification code that was sent to your email</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Submit crash logs</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Help Bitwarden improve app stability by submitting crash reports.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Options are expanded, tap to collapse.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Options are collapsed, tap to expand.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Uppercase (A to Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Lowercase (A to Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Numbers (0 to 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Special Characters (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Tap to go back</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Password is visible, tap to hide.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Password is not visible, tap to show.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Filter items by vault</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>All Vaults</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Vaults</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Vault: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>All</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Verification Codes</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Premium subscription required</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Cannot add authenticator key? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>Scan QR Code</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>Cannot scan QR Code? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Authenticator Key</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Enter Key Manually</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>Add TOTP</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>Set up TOTP</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Once the key is successfully entered,
select Add TOTP to store the key safely</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>One or more of the URLs entered are invalid. Please revise it and try to save again.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>We were unable to process your request. Please try again or contact us.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Allow screen capture</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Are you sure you want to enable Screen Capture?</value>
</data>
</root>

View File

@@ -261,11 +261,11 @@
<comment>The button text that allows user to launch the website to their web browser.</comment>
</data>
<data name="LogIn" xml:space="preserve">
<value>Вход</value>
<value>Вписване</value>
<comment>The login button text (verb).</comment>
</data>
<data name="LogInNoun" xml:space="preserve">
<value>Вход</value>
<value>Вписване</value>
<comment>Title for login page. (noun)</comment>
</data>
<data name="LogOut" xml:space="preserve">
@@ -299,10 +299,6 @@
<value>Моят трезор</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Име</value>
<comment>Label for an entity name.</comment>
@@ -397,7 +393,7 @@
<value>Посетете нашия сайт</value>
</data>
<data name="VisitOurWebsiteDescription" xml:space="preserve">
<value>Посетете сайта ни за помощ, новини, начини да се свържете с нас и как да ползвате Bitwarden.</value>
<value>Посетете сайта ни за помощ, новини, начини да се свържете с нас и как да ползвате Битуорден.</value>
</data>
<data name="Website" xml:space="preserve">
<value>Сайт</value>
@@ -419,7 +415,7 @@
<value>Разширение</value>
</data>
<data name="AutofillAccessibilityDescription" xml:space="preserve">
<value>Автоматично дописване на полетата във формулярите в приложенията и уеб чрез услугата за достъпност на Bitwarden.</value>
<value>Автоматично дописване на полетата във формулярите в приложенията и уеб чрез услугата за достъпност на Битуорден.</value>
</data>
<data name="AutofillService" xml:space="preserve">
<value>Услуга за автоматично дописване</value>
@@ -428,7 +424,7 @@
<value>Без нееднозначни знаци</value>
</data>
<data name="BitwardenAppExtension" xml:space="preserve">
<value>Разширение за програмите на Bitwarden</value>
<value>Разширение за програмите на Битуорден</value>
</data>
<data name="BitwardenAppExtensionAlert2" xml:space="preserve">
<value>Най-лесният начин да добавяте нови записи в трезора е чрез разширението за програмите на Битуорден. Може да научите повече за него в екрана за настройките.</value>
@@ -443,7 +439,7 @@
<value>Автоматично дописване на формулярите за вписване с услугата за достъпност на Битуорден.</value>
</data>
<data name="ChangeEmail" xml:space="preserve">
<value>Промяна на имейла</value>
<value>Промяна на адреса за е-поща</value>
</data>
<data name="ChangeEmailConfirmation" xml:space="preserve">
<value>Адресът ви за е-поща може да се промени чрез сайта bitwarden.com. Искате ли да го посетите?</value>
@@ -461,7 +457,7 @@
<value>Продължаване</value>
</data>
<data name="CreateAccount" xml:space="preserve">
<value>Регистрация</value>
<value>Създаване на абонамент</value>
</data>
<data name="CreatingAccount" xml:space="preserve">
<value>Създаване на сметката...</value>
@@ -514,7 +510,7 @@
<value>Отпечатък</value>
</data>
<data name="GeneratePassword" xml:space="preserve">
<value>Генериране на парола</value>
<value>Нова парола</value>
</data>
<data name="GetPasswordHint" xml:space="preserve">
<value>Получаване на подсказка за главната парола</value>
@@ -566,7 +562,7 @@
<comment>Message shown when interacting with the server</comment>
</data>
<data name="LoginOrCreateNewAccount" xml:space="preserve">
<value>Влезте или се регистрирайте, за да имате достъп до вашия защитен трезор.</value>
<value>Впишете се или създайте нов абонамент, за да достъпите защитен трезор.</value>
</data>
<data name="Manage" xml:space="preserve">
<value>Управление</value>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Включено</value>
</data>
<data name="Off" xml:space="preserve">
<value>Изключено</value>
</data>
<data name="On" xml:space="preserve">
<value>Включено</value>
</data>
<data name="Status" xml:space="preserve">
<value>Състояние</value>
</data>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Удостоверителният ключ не може да се прочете.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Насочете камерата към QR кода.
Сканирането ще се извърши автоматично.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Сканирането става автоматично.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Насочете камерата към QR кода.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>Сканиране на QR код</value>
@@ -915,11 +907,11 @@
<data name="CopyTotp" xml:space="preserve">
<value>Копиране на кода за потвърждаване</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Ако към вписването има и удостоверителен ключ, кодът за потвърждаване се копира автоматично в буфера при автоматично дописване.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>Автоматично копиране на TOTP</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Без автоматично копиране на временни, еднократни пароли</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>За да се възползвате от тази възможност, трябва да ползвате платен абонамент.</value>
@@ -1065,7 +1057,7 @@
<value>февруари</value>
</data>
<data name="FirstName" xml:space="preserve">
<value>Име</value>
<value>Собствено име</value>
</data>
<data name="January" xml:space="preserve">
<value>януари</value>
@@ -1077,7 +1069,7 @@
<value>юни</value>
</data>
<data name="LastName" xml:space="preserve">
<value>Фамилия</value>
<value>Фамилно име</value>
</data>
<data name="FullName" xml:space="preserve">
<value>Пълно име</value>
@@ -1136,17 +1128,17 @@
<data name="Expiration" xml:space="preserve">
<value>Изтичане</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Показване на иконките на уеб сайтовете</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Изключване на иконките на сайтовете</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Показване на разпознаваемо изображение до всеки запис.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Иконките на сайтовете са разпознаваемо изображение за всеки запис в трезора.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>Адрес на сървъра с иконки</value>
</data>
<data name="AutofillWithBitwarden" xml:space="preserve">
<value>Автоматично дописване с Bitwarden</value>
<value>Автоматично дописване с Битуорден</value>
</data>
<data name="VaultIsLocked" xml:space="preserve">
<value>Трезорът е заключен</value>
@@ -1380,15 +1372,11 @@
<data name="SearchCollection" xml:space="preserve">
<value>Търсене в колекцията</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Търсене в изпратените файлове</value>
<data name="SearchFolder" xml:space="preserve">
<value>Търсене в папката</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Търсене в изпратените текстове</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Търсене на {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Търсене по вид</value>
</data>
<data name="Type" xml:space="preserve">
<value>Вид</value>
@@ -1549,12 +1537,6 @@
<data name="ThemeDefault" xml:space="preserve">
<value>По подразбиране (от системата)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Стандартен тъмен облик</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Изберете тъмния облик, който да се ползва, когато е избран стандартният (от системата) облик и тъмният режим на устройството Ви е включен.</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Копиране на бележките</value>
</data>
@@ -1571,21 +1553,17 @@
<value>Запрет</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Норд</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>Адреси под запрет</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Блокирани за авт. попълване адреси</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>Няма да се предлага автоматично дописване във формулярите с адреси под запрет. За разделител в списъка ползвайте запетая. Напр: „https://twitter.com, androidapp://com.twitter.android“.</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>За посочените блокирани адреси няма да се прави автоматично попълване. Разделяйте отделните адреси със запетая. Пример: „https://twitter.com, androidapp://com.twitter.android“.</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Без напомняне за запомняне</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Питане за добавяне на запис</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Питане за добавяне на запис, ако такъв не бъде намерен в трезора Ви.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>Дали да бъдете автоматично подканени да запазите в трезора данни за вписване, които въвеждате за първи път.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>При рестартиране на приложението</value>
@@ -1878,9 +1856,6 @@
<value>Описателно име за това изпращане.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Текст</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Текст</value>
</data>
@@ -1897,18 +1872,6 @@
<data name="TypeFileInfo" xml:space="preserve">
<value>Файл за изпращане.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>Избран е файлов тип.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>Не е избран файлов тип. Докоснете за избор.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Избран е текстов тип.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Не е избран текстов тип. Докоснете за избор.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Дата на изтриване</value>
</data>
@@ -2214,104 +2177,4 @@
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Въведете кода за потвърждаване, който беше изпратен на Вашата е-поща</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Изпращане на доклади за сривовете</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Помогнете на Битуордън в подобряването на стабилността на приложението, като изпращате доклади относно сривовете.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Възможностите са разгънати. Докоснете за свиване.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Възможностите са свити. Докоснете за разгъване.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Главни букви (A-Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Малки букви (a-z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Числа (0-9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Специални знаци (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Досокнете за връщане назад</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Паролата е видима. Докоснете за скриване.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Паролата не е видима. Докоснете за показване.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Филтриране на записите по трезор</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>Всички трезори</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Трезори</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Трезор: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>Всички</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Кодове за потвърждаване</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Изисква се платен абонамент</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Не можете да добавите удостоверителен ключ? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>Сканиране на QR код</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>Не можете да сканирате QR кода? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Ключ за удостоверяване</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Ръчно въвеждане на кода</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>Add TOTP</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>Set up TOTP</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Once the key is successfully entered,
select Add TOTP to store the key safely</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Ако изберете „Никога“ като настройка за заключването, трезорът Ви ще бъде достъпен за всеки, който има досег с устройството. Ако използвате тази настройка, трябва да се уверите, че устройството Ви е удачно защитено.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>Един или повече от въведените адреси са неправилни. Прегледайте ги и ги поправете, а след това опитайте да запазите отново.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>Не може да обработим заявката ви. Моля опитайте отново или се свържете с нас.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Позволяване на заснемане на екрана</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Наистина ли искате да разрешите заснемането на екрана?</value>
</data>
</root>

View File

@@ -299,10 +299,6 @@
<value>আমার ভল্ট</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>নাম</value>
<comment>Label for an entity name.</comment>
@@ -416,13 +412,13 @@
<value>Add an Item</value>
</data>
<data name="AppExtension" xml:space="preserve">
<value>App extension</value>
<value>App Extension</value>
</data>
<data name="AutofillAccessibilityDescription" xml:space="preserve">
<value>Use the Bitwarden accessibility service to auto-fill your logins across apps and the web.</value>
</data>
<data name="AutofillService" xml:space="preserve">
<value>Auto-fill service</value>
<value>Auto-fill Service</value>
</data>
<data name="AvoidAmbiguousCharacters" xml:space="preserve">
<value>Avoid Ambiguous Characters</value>
@@ -477,13 +473,13 @@
<value>আপনার প্রধান পাসওয়ার্ডের ইঙ্গিতটি পেতে আপনার অ্যাকাউন্টের ইমেল ঠিকানা প্রবেশ করুন।</value>
</data>
<data name="ExntesionReenable" xml:space="preserve">
<value>Re-enable app extension</value>
<value>Re-enable App Extension</value>
</data>
<data name="ExtensionAlmostDone" xml:space="preserve">
<value>Almost done!</value>
</data>
<data name="ExtensionEnable" xml:space="preserve">
<value>Enable app extension</value>
<value>Enable App Extension</value>
</data>
<data name="ExtensionInSafari" xml:space="preserve">
<value>In Safari, find Bitwarden using the share icon (hint: scroll to the right on the bottom row of the menu).</value>
@@ -651,7 +647,7 @@
<comment>Push notifications for apple products</comment>
</data>
<data name="RateTheApp" xml:space="preserve">
<value>Rate the app</value>
<value>Rate the App</value>
</data>
<data name="RateTheAppDescription" xml:space="preserve">
<value>দয়া করে একটি ভাল পর্যালোচনার মাধ্যমে সাহায্য করতে আমাদের বিবেচনা করুন!</value>
@@ -698,14 +694,14 @@
<value>Syncing failed.</value>
</data>
<data name="SyncVaultNow" xml:space="preserve">
<value>Sync vault now</value>
<value>Sync Vault Now</value>
</data>
<data name="TouchID" xml:space="preserve">
<value>Touch ID</value>
<comment>What Apple calls their fingerprint reader.</comment>
</data>
<data name="TwoStepLogin" xml:space="preserve">
<value>Two-step login</value>
<value>Two-step Login</value>
</data>
<data name="TwoStepLoginConfirmation" xml:space="preserve">
<value>Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?</value>
@@ -714,7 +710,7 @@
<value>Unlock with {0}</value>
</data>
<data name="UnlockWithPIN" xml:space="preserve">
<value>Unlock with PIN code</value>
<value>Unlock with PIN Code</value>
</data>
<data name="Validating" xml:space="preserve">
<value>Validating</value>
@@ -727,7 +723,7 @@
<value>View Item</value>
</data>
<data name="WebVault" xml:space="preserve">
<value>Bitwarden web vault</value>
<value>Bitwarden Web Vault</value>
</data>
<data name="Lost2FAApp" xml:space="preserve">
<value>Lost authenticator app?</value>
@@ -737,7 +733,7 @@
<comment>Screen title</comment>
</data>
<data name="ExtensionActivated" xml:space="preserve">
<value>Extension activated!</value>
<value>Extension Activated!</value>
</data>
<data name="Icons" xml:space="preserve">
<value>Icons</value>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Off" xml:space="preserve">
<value>Off</value>
</data>
<data name="On" xml:space="preserve">
<value>On</value>
</data>
<data name="Status" xml:space="preserve">
<value>Status</value>
</data>
@@ -808,7 +798,7 @@
<value>You are searching for an auto-fill item for "{0}".</value>
</data>
<data name="LearnOrg" xml:space="preserve">
<value>Learn about organizations</value>
<value>Learn About Organizations</value>
</data>
<data name="CannotOpenApp" xml:space="preserve">
<value>Cannot open the app "{0}".</value>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Cannot read authenticator key.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Point your camera at the QR Code.
Scanning will happen automatically.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Scanning will happen automatically.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Point your camera at the QR code.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>Scan QR Code</value>
@@ -915,11 +907,11 @@ Scanning will happen automatically.</value>
<data name="CopyTotp" xml:space="preserve">
<value>Copy TOTP</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<value>If a login has an authenticator key, copy the TOTP verification code to your clip-board when you auto-fill the login.</value>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>If your login has an authenticator key attached to it, the TOTP verification code is automatically copied to your clipboard whenever you auto-fill the login.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>Copy TOTP automatically</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Disable Automatic TOTP Copy</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>A premium membership is required to use this feature.</value>
@@ -1136,11 +1128,11 @@ Scanning will happen automatically.</value>
<data name="Expiration" xml:space="preserve">
<value>Expiration</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Show website icons</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Disable Website Icons</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Show a recognizable image next to each login.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Website Icons provide a recognizable image next to each login item in your vault.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>Icons Server URL</value>
@@ -1319,7 +1311,7 @@ Scanning will happen automatically.</value>
<value>5. Select "Bitwarden"</value>
</data>
<data name="PasswordAutofill" xml:space="preserve">
<value>Password auto-fill</value>
<value>Password AutoFill</value>
</data>
<data name="BitwardenAutofillAlert2" xml:space="preserve">
<value>The easiest way to add new logins to your vault is by using the Bitwarden Password AutoFill extension. Learn more about using the Bitwarden Password AutoFill extension by navigating to the "Settings" screen.</value>
@@ -1380,15 +1372,11 @@ Scanning will happen automatically.</value>
<data name="SearchCollection" xml:space="preserve">
<value>সংগ্রহ অনুসন্ধান</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Search File Sends</value>
<data name="SearchFolder" xml:space="preserve">
<value>ফোল্ডার অনুসন্ধান</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Search Text Sends</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Search {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>অনুসন্ধানের ধরন</value>
</data>
<data name="Type" xml:space="preserve">
<value>ধরন</value>
@@ -1457,7 +1445,7 @@ Scanning will happen automatically.</value>
<value>There are no folders to list.</value>
</data>
<data name="FingerprintPhrase" xml:space="preserve">
<value>Fingerprint phrase</value>
<value>Fingerprint Phrase</value>
<comment>A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing.</comment>
</data>
<data name="YourAccountsFingerprint" xml:space="preserve">
@@ -1468,10 +1456,10 @@ Scanning will happen automatically.</value>
<value>Bitwarden allows you to share your vault items with others by using an organization account. Would you like to visit the bitwarden.com website to learn more?</value>
</data>
<data name="ExportVault" xml:space="preserve">
<value>Export vault</value>
<value>Export Vault</value>
</data>
<data name="LockNow" xml:space="preserve">
<value>Lock now</value>
<value>Lock Now</value>
</data>
<data name="PIN" xml:space="preserve">
<value>PIN</value>
@@ -1549,14 +1537,8 @@ Scanning will happen automatically.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>Default (System)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Default dark theme</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Choose the dark theme to use when using Default (System) theme while your device's dark mode is enabled.</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Copy Note</value>
<value>Copy Notes</value>
</data>
<data name="Exit" xml:space="preserve">
<value>প্রস্থান</value>
@@ -1571,24 +1553,20 @@ Scanning will happen automatically.</value>
<value>কালো</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>Blacklisted URIs</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Auto-fill blocked URIs</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>URIs that are blacklisted will not offer auto-fill. The list should be comma separated. Ex: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Disable Save Prompt</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Ask to add login</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Ask to add an item if one isn't found in your vault.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>The "Save Prompt" automatically prompts you to save new items to your vault whenever you enter them for the first time.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>On app restart</value>
<value>On App Restart</value>
</data>
<data name="AutofillServiceNotEnabled" xml:space="preserve">
<value>Auto-fill makes it easy to securely access your Bitwarden vault from other websites and apps. It looks like you have not enabled an auto-fill service for Bitwarden. Enable auto-fill for Bitwarden from the "Settings" screen.</value>
@@ -1822,16 +1800,16 @@ Scanning will happen automatically.</value>
<value>Bitwarden needs attention - Enable "Draw-Over" in "Auto-fill Services" from Bitwarden Settings</value>
</data>
<data name="AutofillServices" xml:space="preserve">
<value>Auto-fill services</value>
<value>Auto-fill Services</value>
</data>
<data name="InlineAutofill" xml:space="preserve">
<value>Use inline autofill</value>
<value>Use Inline Autofill</value>
</data>
<data name="InlineAutofillDescription" xml:space="preserve">
<value>Use inline autofill if your selected IME (keyboard) supports it. If your configuration is not supported (or this option is disabled), the default Autofill overlay will be used.</value>
</data>
<data name="Accessibility" xml:space="preserve">
<value>Use accessibility</value>
<value>Use Accessibility</value>
</data>
<data name="AccessibilityDescription" xml:space="preserve">
<value>Use the Bitwarden Accessibility Service to auto-fill your logins across apps and the web. When enabled, we'll display a popup when login fields are selected.</value>
@@ -1846,7 +1824,7 @@ Scanning will happen automatically.</value>
<value>Required to use the Autofill Quick-Action Tile, or to augment the Autofill Service by using Draw-Over (if enabled).</value>
</data>
<data name="DrawOver" xml:space="preserve">
<value>Use draw-over</value>
<value>Use Draw-Over</value>
</data>
<data name="DrawOverDescription" xml:space="preserve">
<value>When enabled, allows the Bitwarden Accessibility Service to display a popup when login fields are selected.</value>
@@ -1879,9 +1857,6 @@ Scanning will happen automatically.</value>
<value>A friendly name to describe this Send.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Text</value>
</data>
@@ -1898,18 +1873,6 @@ Scanning will happen automatically.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>The file you want to send.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>File type is selected.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>File type is not selected, tap to select.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Text type is selected.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Text type is not selected, tap to select.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Deletion Date</value>
</data>
@@ -2167,13 +2130,13 @@ Scanning will happen automatically.</value>
<value>Account removed successfully</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Delete account</value>
<value>Delete Account</value>
</data>
<data name="DeletingYourAccountIsPermanent" xml:space="preserve">
<value>Deleting your account is permanent</value>
</data>
<data name="DeleteAccountExplanation" xml:space="preserve">
<value>Your account and all vault data will be erased and unrecoverable. Are you sure you want to continue?</value>
<value>Your account and all associated data will be erased and unrecoverable. Are you sure you want to continue?</value>
</data>
<data name="DeletingYourAccount" xml:space="preserve">
<value>Deleting your account</value>
@@ -2214,104 +2177,4 @@ Scanning will happen automatically.</value>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Enter the verification code that was sent to your email</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Submit crash logs</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Help Bitwarden improve app stability by submitting crash reports.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Options are expanded, tap to collapse.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Options are collapsed, tap to expand.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Uppercase (A to Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Lowercase (A to Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Numbers (0 to 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Special Characters (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Tap to go back</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Password is visible, tap to hide.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Password is not visible, tap to show.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Filter items by vault</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>All Vaults</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Vaults</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Vault: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>All</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Verification Codes</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Premium subscription required</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Cannot add authenticator key? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>Scan QR Code</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>Cannot scan QR Code? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Authenticator Key</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Enter Key Manually</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>Add TOTP</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>Set up TOTP</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Once the key is successfully entered,
select Add TOTP to store the key safely</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>One or more of the URLs entered are invalid. Please revise it and try to save again.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>We were unable to process your request. Please try again or contact us.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Allow screen capture</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Are you sure you want to enable Screen Capture?</value>
</data>
</root>

View File

@@ -299,10 +299,6 @@
<value>Moj trezor</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Ime</value>
<comment>Label for an entity name.</comment>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Omogućeno</value>
</data>
<data name="Off" xml:space="preserve">
<value>Off</value>
</data>
<data name="On" xml:space="preserve">
<value>On</value>
</data>
<data name="Status" xml:space="preserve">
<value>Status</value>
</data>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Ne može se pročitati ključ autentifikatora.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Point your camera at the QR Code.
Scanning will happen automatically.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Skeniranje će se odviti automatski.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Usmerite kameru prema QR kodu.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>Skenirajte QR kod</value>
@@ -915,11 +907,11 @@ Scanning will happen automatically.</value>
<data name="CopyTotp" xml:space="preserve">
<value>Iskopirajte TOTP</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<value>If a login has an authenticator key, copy the TOTP verification code to your clip-board when you auto-fill the login.</value>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Ako je za Vašu prijavu priložen autentifikacioni ključ, TOTP verifikacioni kod se automatski kopira u Vašu memoriju kad god automatski popunite prijavu.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>Copy TOTP automatically</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Onemogućite automatsko TOTP kopiranje</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>Za korišćenje ove funkcije potrebno je premium članstvo.</value>
@@ -1136,11 +1128,11 @@ Scanning will happen automatically.</value>
<data name="Expiration" xml:space="preserve">
<value>Rok upotrebe</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Show website icons</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Onemogućite ikone veb lokacije</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Show a recognizable image next to each login.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Ikone veb lokacije pružaju prepoznatljivu sliku pored svake stavke za prijavu u Vaš trezor.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>URL adresa servera ikona</value>
@@ -1380,15 +1372,11 @@ Scanning will happen automatically.</value>
<data name="SearchCollection" xml:space="preserve">
<value>Pretraži kolekciju</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Pretraži datoteke za slanje</value>
<data name="SearchFolder" xml:space="preserve">
<value>Pretraži folder</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Pretraži tekstove za slanje</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Pretraži {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Tip pretrage</value>
</data>
<data name="Type" xml:space="preserve">
<value>Vrsta</value>
@@ -1549,12 +1537,6 @@ Scanning will happen automatically.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>Zadano (Sistem)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Zadana tamna tema</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Choose the dark theme to use when using Default (System) theme while your device's dark mode is enabled.</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Kopiraj bilješke</value>
</data>
@@ -1571,21 +1553,17 @@ Scanning will happen automatically.</value>
<value>Crna</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>Crna lista URI-ja</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Auto-fill blocked URIs</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>URI koji su na crnoj listi neće nuditi auto-ispunu. Popis treba odvojiti zarezima. Npr.: „https://webstranica.com, androidapp://com.aplikcacija.android”.</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Onemogući upit za spremanje</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Ask to add login</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Ask to add an item if one isn't found in your vault.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>Upit za spremanje novih stavki automatski se pojavlju nakon prepoznatog prvog unosa novih stavki.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>Kod ponovnog pokretanja</value>
@@ -1878,9 +1856,6 @@ Scanning will happen automatically.</value>
<value>Nadimak za ovaj Send</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Tekst</value>
</data>
@@ -1897,18 +1872,6 @@ Scanning will happen automatically.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>Datoteka koju želiš poslati</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>File type is selected.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>File type is not selected, tap to select.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Text type is selected.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Text type is not selected, tap to select.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Datum brisanja</value>
</data>
@@ -2213,104 +2176,4 @@ Scanning will happen automatically.</value>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Unesi verifikacijski kod koji je poslan na tvoj E-Mail</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Submit crash logs</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Help Bitwarden improve app stability by submitting crash reports.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Options are expanded, tap to collapse.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Options are collapsed, tap to expand.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Uppercase (A to Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Lowercase (A to Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Numbers (0 to 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Special Characters (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Tap to go back</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Password is visible, tap to hide.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Password is not visible, tap to show.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Filter items by vault</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>All Vaults</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Vaults</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Vault: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>All</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Verification Codes</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Premium subscription required</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Cannot add authenticator key? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>Scan QR Code</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>Cannot scan QR Code? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Authenticator Key</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Enter Key Manually</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>Add TOTP</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>Set up TOTP</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Once the key is successfully entered,
select Add TOTP to store the key safely</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>One or more of the URLs entered are invalid. Please revise it and try to save again.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>We were unable to process your request. Please try again or contact us.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Allow screen capture</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Are you sure you want to enable Screen Capture?</value>
</data>
</root>

View File

@@ -299,10 +299,6 @@
<value>La meua caixa forta</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Autenticador</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Nom</value>
<comment>Label for an entity name.</comment>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Habilitat</value>
</data>
<data name="Off" xml:space="preserve">
<value>Desactivat</value>
</data>
<data name="On" xml:space="preserve">
<value>Activat</value>
</data>
<data name="Status" xml:space="preserve">
<value>Estat</value>
</data>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>No es pot llegir la clau d'autenticació.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Apunteu la càmera cap al codi QR.
L'escaneig es farà automàticament.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>L'escaneig serà automàtic.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Dirigiu la càmera al codi QR.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>Escaneja el codi QR</value>
@@ -915,11 +907,11 @@ L'escaneig es farà automàticament.</value>
<data name="CopyTotp" xml:space="preserve">
<value>Copia TOTP</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Si el vostre inici de sessió té una clau d'autenticació associada, el codi de verificació TOTP es copiarà al vostre porta-retalls quan s'òmpliga automàticament l'inici de sessió.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>Copia TOTP automaticament</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Deshabilita la còpia TOTP automàtica</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>Cal una subscripció premium per utilitzar aquesta característica.</value>
@@ -1136,11 +1128,11 @@ L'escaneig es farà automàticament.</value>
<data name="Expiration" xml:space="preserve">
<value>Caducitat</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Mostra les icones del lloc web</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Deshabilita icones del lloc web</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Mostra una imatge reconeixible al costat de cada inici de sessió.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Les icones del lloc web proporcionen una imatge que es pot reconèixer al costat de cada element d'inici de sessió a la vostra caixa forta.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>URL del servidor d'icones</value>
@@ -1307,7 +1299,7 @@ L'escaneig es farà automàticament.</value>
<value>1. Aneu a l'aplicació "Configuració" de l'iOS</value>
</data>
<data name="AutofillTurnOn2" xml:space="preserve">
<value>2. Toqueu "Contrasenyes"</value>
<value>2. Toqueu "Contrasenyes i comptes"</value>
</data>
<data name="AutofillTurnOn3" xml:space="preserve">
<value>3. Toqueu "Emplenament automàtic de contrasenyes"</value>
@@ -1380,15 +1372,11 @@ L'escaneig es farà automàticament.</value>
<data name="SearchCollection" xml:space="preserve">
<value>Cerca a la col·lecció</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Cerca fitxers Sends</value>
<data name="SearchFolder" xml:space="preserve">
<value>Cerca a la carpeta</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Cerca Sends de text</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Cerca {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Tipus de cerca</value>
</data>
<data name="Type" xml:space="preserve">
<value>Tipus</value>
@@ -1525,7 +1513,7 @@ L'escaneig es farà automàticament.</value>
<value>2 minuts</value>
</data>
<data name="ClearClipboard" xml:space="preserve">
<value>Buida el porta-retalls</value>
<value>Neteja el porta-retalls</value>
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
</data>
<data name="ClearClipboardDescription" xml:space="preserve">
@@ -1549,14 +1537,8 @@ L'escaneig es farà automàticament.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>Per defecte (Sistema)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Tema fosc per defecte</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Tria el tema fosc quan feu servir el tema predeterminat (sistema) mentre el mode fosc del vostre dispositiu està habilitat</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Copia nota</value>
<value>Copia notes</value>
</data>
<data name="Exit" xml:space="preserve">
<value>Tanca</value>
@@ -1571,21 +1553,17 @@ L'escaneig es farà automàticament.</value>
<value>Negre</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>URl a la llista negra</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Emplena automàticament els URI bloquejats</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>Els URI que es mostren a la llista negra no oferiran lemplenament automàtic. La llista ha de estar separada per comes. Ex: "https://twitter.com, androidapp: //com.twitter.android".</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>L'emplenament automàtic no s'oferirà per als URI bloquejats. Separeu diversos URI amb una coma. Per exemple: "https://twitter.com, androidapp://com.twitter.android".</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Desactiva Guarda elements nous</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Sol·licita afegir els inicis de sessió</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Sol·licita afegir un element si no se'n troba cap a la caixa forta.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>"Guarda elements nous" demanarà automàticament que guardeu elements nous al magatzem cada volta que els introduïu per primera vegada.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>En reiniciar l'aplicació</value>
@@ -1878,9 +1856,6 @@ L'escaneig es farà automàticament.</value>
<value>Un nom apropiat per descriure aquest Send.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Text</value>
</data>
@@ -1897,18 +1872,6 @@ L'escaneig es farà automàticament.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>El fitxer que voleu enviar.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>El tipus de fitxer està seleccionat.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>El tipus de fitxer no està seleccionat, toqueu per a seleccionar-lo.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>El tipus de text està seleccionat.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>El tipus de text no està seleccionat, toqueu per a seleccionar-lo.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Data de supressió</value>
</data>
@@ -2213,104 +2176,4 @@ L'escaneig es farà automàticament.</value>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Introduïu el codi de verificació que sha enviat al vostre correu</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Envieu registres d'error</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Ajudeu Bitwarden a millorar l'estabilitat de l'aplicació enviant els registres d'error</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Les opcions estan ampliades, toqueu per contraure.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Les opcions estan reduïdes, toqueu per ampliar.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Majúscules (A a la Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Minúscules (A a la Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Números (0 al 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Caràcters especials (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Toca per a tornar arrere</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>La contrasenya és visible, toqueu per amagar-la.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>La contrasenya no és visible, toqueu per mostrar-la.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Filtra artícless per caixa forta</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>Totes les caixes fortes</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Caixes fortes</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Caixa forta: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>Totes</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Codis de verificació</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Cal una subscripció premium</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>No es pot afegir la clau d'autenticació? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>Escaneja el codi QR</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>No podeu escanejar el codi QR? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Clau autenticadora</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Introdueix la clau manualment</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>Afegeix TOTP</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>Configura TOTP</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Una vegada introduïda la clau correctament,
seleccioneu Afegeix TOTP per emmagatzemar la clau de manera segura</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Si configureu les opcions de bloqueig a "Mai", la vostra caixa forta està disponible per a qualsevol persona amb accés al vostre dispositiu. Si utilitzeu aquesta opció, hauríeu d'assegurar-vos de mantenir el vostre dispositiu correctament protegit.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>Un o més dels URL introduïts no són vàlids. Reviseu-ho i torneu a provar de guardar-ho.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>No hem pogut processar la vostra sol·licitud. Torneu-ho a provar o contacteu amb nosaltres.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Permet capturar la pantalla</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Segur que voleu activar la captura de pantalla?</value>
</data>
</root>

View File

@@ -299,10 +299,6 @@
<value>Můj trezor</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Název</value>
<comment>Label for an entity name.</comment>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Povoleno</value>
</data>
<data name="Off" xml:space="preserve">
<value>Off</value>
</data>
<data name="On" xml:space="preserve">
<value>On</value>
</data>
<data name="Status" xml:space="preserve">
<value>Stav</value>
</data>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Nelze přečíst ověřovací klíč.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Point your camera at the QR Code.
Scanning will happen automatically.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Načtení proběhne automaticky.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Namiřte fotoaparát na QR kód.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>Načíst QR kód</value>
@@ -915,11 +907,11 @@ Scanning will happen automatically.</value>
<data name="CopyTotp" xml:space="preserve">
<value>Zkopírovat ověřovací kód (TOTP)</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<value>If a login has an authenticator key, copy the TOTP verification code to your clip-board when you auto-fill the login.</value>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Pokud mají vaše přihlašovací údaje přidán autentizační klíč pro TOTP, vygenerovaný ověřovací kód (TOTP) se automaticky zkopíruje do schránky při každém automatickém vyplnění přihlašovacích údajů.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>Copy TOTP automatically</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Zakázat automatické kopírování TOTP kódu</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>Pro použití této funkce je potřebné prémiové členství.</value>
@@ -1136,11 +1128,11 @@ Scanning will happen automatically.</value>
<data name="Expiration" xml:space="preserve">
<value>Expirace</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Show website icons</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Zakázat ikonky webových stránek</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Show a recognizable image next to each login.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Ikonky webových stránek zobrazí snadno rozeznatelný obrázek vedle každé položky ve vašem trezoru.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>URL serveru ikonek</value>
@@ -1380,15 +1372,11 @@ Scanning will happen automatically.</value>
<data name="SearchCollection" xml:space="preserve">
<value>Vyledat v kolekci</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Search File Sends</value>
<data name="SearchFolder" xml:space="preserve">
<value>Vyhledat ve složce</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Search Text Sends</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Hledat {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Typ hledání</value>
</data>
<data name="Type" xml:space="preserve">
<value>Typ</value>
@@ -1549,12 +1537,6 @@ Scanning will happen automatically.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>Výchozí (Systémový)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Default dark theme</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Choose the dark theme to use when using Default (System) theme while your device's dark mode is enabled.</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Kopírovat poznámky</value>
</data>
@@ -1571,21 +1553,17 @@ Scanning will happen automatically.</value>
<value>Černá</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>URI na černé listině</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Automatické vyplňování blokovaných URI</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>Pro URI na černé listině nebude nabízeno automatické vyplnění. Záznamy oddělujte čárkou. Např.: „https://twitter.com, androidapp://com.twitter.android“.</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Automatické vyplňování nebude nabídnuto pro blokované URI. Oddělte více URI čárkou. Například: "https://twitter.com, androidapp://com.twitter.android".</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Zakázat výzvu o uložení</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Ask to add login</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Ask to add an item if one isn't found in your vault.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>„Výzva k uložení“ vás automaticky vyzve k uložení nových položek do trezoru, kdykoli je poprvé zadáte.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>Při restartu aplikace</value>
@@ -1878,9 +1856,6 @@ Scanning will happen automatically.</value>
<value>Přátelský název pro popis tohoto Send.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Text</value>
</data>
@@ -1897,18 +1872,6 @@ Scanning will happen automatically.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>Soubor, který chcete odeslat.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>File type is selected.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>File type is not selected, tap to select.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Text type is selected.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Text type is not selected, tap to select.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Datum odstranění</value>
</data>
@@ -2124,7 +2087,7 @@ Scanning will happen automatically.</value>
<value>Authenticate WebAuthn</value>
</data>
<data name="Fido2ReturnToApp" xml:space="preserve">
<value>Zpět do aplikace</value>
<value>Return to App</value>
</data>
<data name="Fido2CheckBrowser" xml:space="preserve">
<value>Please make sure your default browser supports WebAuthn and try again.</value>
@@ -2160,7 +2123,7 @@ Scanning will happen automatically.</value>
<value>Account Locked</value>
</data>
<data name="AccountLoggedOutSuccessfully" xml:space="preserve">
<value>Odhlášení proběhlo úspěšně</value>
<value>Account logged out successfully</value>
</data>
<data name="AccountRemovedSuccessfully" xml:space="preserve">
<value>Account removed successfully</value>
@@ -2208,109 +2171,9 @@ Scanning will happen automatically.</value>
<value>Ověřovací kód byl odeslán na váš e-mail</value>
</data>
<data name="AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain" xml:space="preserve">
<value>Při odesílání ověřovacího kódu na váš e-mail došlo k chybě. Zkuste to prosím znovu</value>
<value>An error occurred while sending a verification code to your email. Please try again</value>
</data>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Zadejte ověřovací kód, který byl odeslán na %@</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Submit crash logs</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Help Bitwarden improve app stability by submitting crash reports.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Options are expanded, tap to collapse.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Options are collapsed, tap to expand.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Uppercase (A to Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Lowercase (A to Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Numbers (0 to 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Special Characters (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Tap to go back</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Heslo je viditelné, klepněte pro skrytí</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Heslo není viditelné, klepněte pro zobrazení.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Filter items by vault</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>All Vaults</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Vaults</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Vault: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>All</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Verification Codes</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Premium subscription required</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Cannot add authenticator key? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>Scan QR Code</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>Cannot scan QR Code? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Authenticator Key</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Enter Key Manually</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>Add TOTP</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>Set up TOTP</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Once the key is successfully entered,
select Add TOTP to store the key safely</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>One or more of the URLs entered are invalid. Please revise it and try to save again.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>We were unable to process your request. Please try again or contact us.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Allow screen capture</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Are you sure you want to enable Screen Capture?</value>
<value>Enter the verification code that was sent to your email</value>
</data>
</root>

View File

@@ -299,10 +299,6 @@
<value>Min boks</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Autentifikator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Navn</value>
<comment>Label for an entity name.</comment>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Aktiveret</value>
</data>
<data name="Off" xml:space="preserve">
<value>Fra</value>
</data>
<data name="On" xml:space="preserve">
<value>Til</value>
</data>
<data name="Status" xml:space="preserve">
<value>Status</value>
</data>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Kan ikke læse autentificeringsnøgle.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Ret kameraet mod QR-koden.
Skanning vil ske automatisk.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Skanning vil ske automatisk.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Peg dit kamera mod QR-koden.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>Skan QR-kode</value>
@@ -915,11 +907,11 @@ Skanning vil ske automatisk.</value>
<data name="CopyTotp" xml:space="preserve">
<value>Kopiér TOTP</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<value>Har et login en godkendelsesnøgle, så kopiér TOTP-bekræftelseskoden til udklipsholderen, når login auto-udfyldes.</value>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Hvis dit login har en autentificeringsnøgle tilknyttet, kopieres TOTP verifikationskoden automatisk til din udklipsholder når du auto-udfylder login.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>Kopiér TOTP automatisk</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Deaktivér automatisk TOTP kopiering</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>Premium-medlemskab kræves for at anvende denne funktion.</value>
@@ -1136,11 +1128,11 @@ Skanning vil ske automatisk.</value>
<data name="Expiration" xml:space="preserve">
<value>Udløb</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Vis webstedsikoner</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Slå webikoner fra</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Vis et genkendeligt billede ud for hvert login.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Webikoner vises som et genkendeligt billede ved siden af hvert loginelement i din boks.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>Ikonserver URL</value>
@@ -1307,7 +1299,7 @@ Skanning vil ske automatisk.</value>
<value>1. Åbn iOS-appen "Indstillinger"</value>
</data>
<data name="AutofillTurnOn2" xml:space="preserve">
<value>2. Tryk på "Adgangskoder"</value>
<value>2. Tryk på "Adgangskoder &amp; konti"</value>
</data>
<data name="AutofillTurnOn3" xml:space="preserve">
<value>3. Tryk på "Autoudfyld adgangskoder"</value>
@@ -1319,7 +1311,7 @@ Skanning vil ske automatisk.</value>
<value>5. Vælg "Bitwarden"</value>
</data>
<data name="PasswordAutofill" xml:space="preserve">
<value>Adgangskode autoudfyld</value>
<value>Adgangskode Autoudfyld</value>
</data>
<data name="BitwardenAutofillAlert2" xml:space="preserve">
<value>Den letteste måde at tilføje nye logins til din boks er ved at bruge Bitwarden adgangskode Autoudfyld udvidelsen. Få mere at vide om brugen af Bitwarden adgangskode Autoudfyld udvidelsen ved at navigere til skærmbilledet "Indstillinger".</value>
@@ -1380,15 +1372,11 @@ Skanning vil ske automatisk.</value>
<data name="SearchCollection" xml:space="preserve">
<value>Søg i samling</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Søg fil Sends</value>
<data name="SearchFolder" xml:space="preserve">
<value>Søg i mappe</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Søg tekst Sends</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Søg {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Søgetype</value>
</data>
<data name="Type" xml:space="preserve">
<value>Type</value>
@@ -1549,14 +1537,8 @@ Skanning vil ske automatisk.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>Standard (system)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Standard mørkt tema</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Vælg det mørke tema, der skal bruges, når Standard (system) temaet bruges, mens din enheds mørke tilstand er aktiveret.</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Kopiér notat</value>
<value>Kopiér notater</value>
</data>
<data name="Exit" xml:space="preserve">
<value>Afslut</value>
@@ -1571,21 +1553,17 @@ Skanning vil ske automatisk.</value>
<value>Sort</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>Sortlistede URI'er</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Autoudfyld blokerede URI'er</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>Sortlistede URI'er tilbyder ikke autoudfyldning. Listen skal være kommasepareret. F.eks: "https://twitter.com, androidapp: //com.twitter.android".</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Autoudfyldning tilbydes ikke for blokerede URI'er. Adskil flere URI'er med komma. F.eks.: "https://twitter.com, androidapp://com.twitter.android".</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Deaktivér gem-forespørgsel</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Spørg om at tilføje login</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Spørg om at tilføje et element, hvis et ikke findes i din boks.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>"Gem"-forespørgslen beder dig automatisk gemme nye emner i din boks, når du angiver dem for første gang.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>Ved app-genstart</value>
@@ -1824,13 +1802,13 @@ Skanning vil ske automatisk.</value>
<value>Autoudfyldtjeneste</value>
</data>
<data name="InlineAutofill" xml:space="preserve">
<value>Brug integreret autoudfyld</value>
<value>Brug indbygget Autoudfyld</value>
</data>
<data name="InlineAutofillDescription" xml:space="preserve">
<value>Brug indbygget autofyld, hvis dit valgte IME (tastatur) understøtter det. Hvis din konfiguration ikke understøttes (eller denne indstilling er deaktiveret), benyttes standard overlejret Autoudfyld.</value>
</data>
<data name="Accessibility" xml:space="preserve">
<value>Brug tilgængelighed</value>
<value>Brug Tilgængelighed</value>
</data>
<data name="AccessibilityDescription" xml:space="preserve">
<value>Brug Bitwarden-tilgængelighedstjenesten til at autoudfylde dine logins i apps og på internettet. Når aktiveret, vises en popup, når login-felter vælges.</value>
@@ -1878,9 +1856,6 @@ Skanning vil ske automatisk.</value>
<value>Et venligt navn til at beskrive denne Send.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Tekst</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Tekst</value>
</data>
@@ -1897,18 +1872,6 @@ Skanning vil ske automatisk.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>Den fil, du vil sende.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>Filtype er valgt.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>Filtype er ikke valgt, tryk for at vælge.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Teksttype er valgt.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Teksttype er ikke valgt, tryk for at vælge.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Sletningsdato</value>
</data>
@@ -2213,104 +2176,4 @@ Skanning vil ske automatisk.</value>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Indtast den bekræftelseskode, der blev sendt til din e-mail</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Indsend nedbrudslogger</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Hjælp Bitwarden med at forbedre app-stabiliteten ved at indsende nedbrudsrapporter.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Indstillinger er udvidet, tryk for at kollapse.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Indstillinger er kollapset, tryk for at udvide.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Store bogstaver (A til Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Små bogstaver (A til Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Tal (0 til 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Specialtegn (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Tryk for at gå tilbage</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Adgangskode er synlig, tryk for at skjule.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Adgangskode er ikke synlig, tryk for at vise.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Filtrér elementer efter boks</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>Alle bokse</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Bokse</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Boks: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Bekræftelseskoder</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Premium-abonnement kræves</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Kan ikke tilføje autentificeringsnøgle?</value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>Skan QR-kode</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>Kan ikke skanne QR-kode? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Autentificeringsnøgle</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Angiv nøgle manuelt</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>Tilføj TOTP</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>Opsæt TOTP</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Når nøglen er angivet,
vælg Tilføj TOTP for at gemme nøglen sikkert</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Sættes låseindstillingen til “Aldrig”, er din boks tilgængelig for alle med adgang til enheden. Bruges denne mulighed, så vær sikker på, at din enhed er ordentligt beskyttet.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>En eller flere af de angivne URL'er er ugyldige. Ret fejlen(e) og prøv at gemme igen.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>Vi kunne ikke behandle din anmodning. Prøv venligst igen eller kontakt os.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Tillad skærmoptagelse</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Er du sikker på, at du vil aktivere skærmoptagelse?</value>
</data>
</root>

View File

@@ -279,13 +279,13 @@
<value>Konto entfernen</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Möchtest du dieses Konto wirklich entfernen?</value>
<value>Möchten Sie das Konto wirklich entfernen?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Konto bereits hinzugefügt</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Möchtest du jetzt darauf umschalten?</value>
<value>Möchten Sie jetzt darauf umschalten?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Masterpasswort</value>
@@ -299,10 +299,6 @@
<value>Mein Tresor</value>
<comment>The title for the vault page.</comment>
</data>
<data name="Authenticator" xml:space="preserve">
<value>Authenticator</value>
<comment>Authenticator TOTP feature</comment>
</data>
<data name="Name" xml:space="preserve">
<value>Name</value>
<comment>Label for an entity name.</comment>
@@ -774,12 +770,6 @@
<data name="Enabled" xml:space="preserve">
<value>Aktiviert</value>
</data>
<data name="Off" xml:space="preserve">
<value>Aus</value>
</data>
<data name="On" xml:space="preserve">
<value>Ein</value>
</data>
<data name="Status" xml:space="preserve">
<value>Status</value>
</data>
@@ -899,9 +889,11 @@
<data name="AuthenticatorKeyReadError" xml:space="preserve">
<value>Authentifizierungsschlüssel kann nicht gelesen werden.</value>
</data>
<data name="PointYourCameraAtTheQRCode" xml:space="preserve">
<value>Richte deine Kamera auf den QR Code.
Das Scannen erfolgt automatisch.</value>
<data name="CameraInstructionBottom" xml:space="preserve">
<value>Der Scan wird automatisch durchgeführt.</value>
</data>
<data name="CameraInstructionTop" xml:space="preserve">
<value>Richte deine Kamera auf den QR Code.</value>
</data>
<data name="ScanQrTitle" xml:space="preserve">
<value>QR Code scannen</value>
@@ -915,11 +907,11 @@ Das Scannen erfolgt automatisch.</value>
<data name="CopyTotp" xml:space="preserve">
<value>TOTP kopieren</value>
</data>
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
<value>Falls Zugangsdaten einen Authentifizierungsschlüssel haben, den TOTP-Verifizierungsscode nach dem automatischen Ausfüllen in die Zwischenablage kopieren.</value>
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
<value>Ist ein Authentifizierungsschlüssel mit deinen Zugangsdaten verknüpft, wird der TOTP Bestätigungscode automatisch in die Zwischenablage kopiert, wenn du die Zugangsdaten einfügen lässt.</value>
</data>
<data name="CopyTotpAutomatically" xml:space="preserve">
<value>TOTP automatisch kopieren</value>
<data name="DisableAutoTotpCopy" xml:space="preserve">
<value>Automatisches Kopieren des TOTP deaktivieren</value>
</data>
<data name="PremiumRequired" xml:space="preserve">
<value>Für diese Funktion benötigst du eine Premiummitgliedschaft.</value>
@@ -1136,11 +1128,11 @@ Das Scannen erfolgt automatisch.</value>
<data name="Expiration" xml:space="preserve">
<value>Gültig bis</value>
</data>
<data name="ShowWebsiteIcons" xml:space="preserve">
<value>Zeige Webseiten-Icons</value>
<data name="DisableWebsiteIcons" xml:space="preserve">
<value>Icons der Website deaktivieren</value>
</data>
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
<value>Ein wiedererkennbares Bild neben jeden Zugangsdaten anzeigen.</value>
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
<value>Website-Symbole zeigen ein erkennbares Bild neben jedem Login in deinem Tresor.</value>
</data>
<data name="IconsUrl" xml:space="preserve">
<value>Icons Server URL</value>
@@ -1380,15 +1372,11 @@ Das Scannen erfolgt automatisch.</value>
<data name="SearchCollection" xml:space="preserve">
<value>Sammlung durchsuchen</value>
</data>
<data name="SearchFileSends" xml:space="preserve">
<value>Versendete Dateien suchen</value>
<data name="SearchFolder" xml:space="preserve">
<value>Ordner durchsuchen</value>
</data>
<data name="SearchTextSends" xml:space="preserve">
<value>Versendete Texte suchen</value>
</data>
<data name="SearchGroup" xml:space="preserve">
<value>Suche {0}</value>
<comment>ex: Search Logins</comment>
<data name="SearchType" xml:space="preserve">
<value>Suchmodus</value>
</data>
<data name="Type" xml:space="preserve">
<value>Typ</value>
@@ -1457,7 +1445,7 @@ Das Scannen erfolgt automatisch.</value>
<value>Keine Ordner zum Auflisten vorhanden.</value>
</data>
<data name="FingerprintPhrase" xml:space="preserve">
<value>Fingerabdruck-Phrase</value>
<value>Prüfschlüssel</value>
<comment>A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing.</comment>
</data>
<data name="YourAccountsFingerprint" xml:space="preserve">
@@ -1549,12 +1537,6 @@ Das Scannen erfolgt automatisch.</value>
<data name="ThemeDefault" xml:space="preserve">
<value>Standard (System)</value>
</data>
<data name="DefaultDarkTheme" xml:space="preserve">
<value>Dunkles Standard Design</value>
</data>
<data name="DefaultDarkThemeDescription" xml:space="preserve">
<value>Wähle das zu verwendende dunkle Design aus, das bei der Auswahl vom Standard (System) Design verwendet werden soll, während der Dunkelmodus deines Geräts aktiviert ist</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Notizen kopieren</value>
</data>
@@ -1571,24 +1553,20 @@ Das Scannen erfolgt automatisch.</value>
<value>Schwarz</value>
<comment>The color black</comment>
</data>
<data name="Nord" xml:space="preserve">
<value>Nord</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
<data name="BlacklistedUris" xml:space="preserve">
<value>Gesperrte URIs</value>
</data>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Blockierte URIs automatisch ausfüllen</value>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>URIs auf der Sperrliste, werden nicht automatisch ausgefüllt. Die Listeneinträge sollten durch Komma getrennt sein. Z.B.: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-Ausfüllen wird für blockierte URIs nicht angeboten. Trenne mehrere URIs mit einem Komma. Beispiel: "https://twitter.com, androidapp://com.twitter.android".</value>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Speicherdialog deaktivieren</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Danach fragen Zugangsdaten hinzuzufügen</value>
</data>
<data name="AskToAddLoginDescription" xml:space="preserve">
<value>Wenn ein Eintrag nicht in deinem Tresor gefunden wurde, danach fragen.</value>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>Der "Speicherdialog" fordert dich automatisch dazu auf, neue Einträge in deinem Tresor zu speichern, wenn du diese zum ersten Mal eingibst.</value>
</data>
<data name="OnRestart" xml:space="preserve">
<value>Beim Neustart der App</value>
<value>Bei App Neustart</value>
</data>
<data name="AutofillServiceNotEnabled" xml:space="preserve">
<value>Automatisches Ausfüllen vereinfacht es, sicher auf deinen Bitwarden Tresor über andere Webseiten und Apps zuzugreifen. Es sieht aus, als ob du den automatischen Ausfülldienst für Bitwarden nicht aktiviert hast. Aktiviere automatisches Ausfüllen in der "Einstellungen" Ansicht.</value>
@@ -1597,7 +1575,7 @@ Das Scannen erfolgt automatisch.</value>
<value>Deine Änderungen am Aussehen der App werden beim nächsten Neustart der App angewendet.</value>
</data>
<data name="Capitalize" xml:space="preserve">
<value>Wortanfänge großschreiben</value>
<value>Großschreiben</value>
<comment>ex. Uppercase the first character of a word.</comment>
</data>
<data name="IncludeNumber" xml:space="preserve">
@@ -1687,7 +1665,7 @@ Das Scannen erfolgt automatisch.</value>
<comment>Clone an entity (verb).</comment>
</data>
<data name="PasswordGeneratorPolicyInEffect" xml:space="preserve">
<value>Eine oder mehrere Organisationsrichtlinien beeinflussen deine Generator-Einstellungen</value>
<value>Eine oder mehrere Organisationsrichtlinien beeinflussen dein Generator-Einstellungen.</value>
</data>
<data name="Open" xml:space="preserve">
<value>Öffnen</value>
@@ -1878,9 +1856,6 @@ Das Scannen erfolgt automatisch.</value>
<value>Ein eigener Name, um dieses Send zu beschreiben.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="Text" xml:space="preserve">
<value>Text</value>
</data>
<data name="TypeText" xml:space="preserve">
<value>Text</value>
</data>
@@ -1897,18 +1872,6 @@ Das Scannen erfolgt automatisch.</value>
<data name="TypeFileInfo" xml:space="preserve">
<value>Die Datei, die du senden möchtest.</value>
</data>
<data name="FileTypeIsSelected" xml:space="preserve">
<value>Dateityp ist ausgewählt.</value>
</data>
<data name="FileTypeIsNotSelected" xml:space="preserve">
<value>Dateityp ist nicht ausgewählt, tippen zum Auswählen.</value>
</data>
<data name="TextTypeIsSelected" xml:space="preserve">
<value>Texttyp ist ausgewählt.</value>
</data>
<data name="TextTypeIsNotSelected" xml:space="preserve">
<value>Texttyp ist nicht ausgewählt, tippen zum Auswählen.</value>
</data>
<data name="DeletionDate" xml:space="preserve">
<value>Löschdatum</value>
</data>
@@ -2052,7 +2015,7 @@ Das Scannen erfolgt automatisch.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="HideEmail" xml:space="preserve">
<value>Meine E-Mail-Adresse vor den Empfängern verstecken.</value>
<value>Meine E-Mail-Adresse vor den Empfängern ausblenden.</value>
</data>
<data name="SendOptionsPolicyInEffect" xml:space="preserve">
<value>Eine oder mehrere Organisationsrichtlinien beeinflussen deine Send Einstellungen.</value>
@@ -2063,7 +2026,7 @@ Das Scannen erfolgt automatisch.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="SendFileEmailVerificationRequired" xml:space="preserve">
<value>Du musst deine E-Mail verifizieren, um Dateien mit Send zu verwenden. Du kannst deine E-Mail im Web-Tresor verifizieren.</value>
<value>Du musst deine E-Mail verifizieren, um diese Funktion nutzen zu können.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="PasswordPrompt" xml:space="preserve">
@@ -2163,7 +2126,7 @@ Das Scannen erfolgt automatisch.</value>
<value>Konto erfolgreich abgemeldet</value>
</data>
<data name="AccountRemovedSuccessfully" xml:space="preserve">
<value>Konto erfolgreich entfernt</value>
<value>Konto erfolgreich gelöscht</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Konto löschen</value>
@@ -2213,103 +2176,4 @@ Das Scannen erfolgt automatisch.</value>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Gib den Bestätigungscode ein, der an deine E-Mail-Adresse gesendet wurde</value>
</data>
<data name="SubmitCrashLogs" xml:space="preserve">
<value>Absturzprotokolle senden</value>
</data>
<data name="SubmitCrashLogsDescription" xml:space="preserve">
<value>Hilf Bitwarden die Stabilität der App zu verbessern, indem du Absturzberichte sendest.</value>
</data>
<data name="OptionsExpanded" xml:space="preserve">
<value>Optionen sind ausgeklappt, tippen zum einklappen.</value>
</data>
<data name="OptionsCollapsed" xml:space="preserve">
<value>Optionen sind eingeklappt, tippe zum ausklappen.</value>
</data>
<data name="UppercaseAtoZ" xml:space="preserve">
<value>Großbuchstaben (A bis Z)</value>
</data>
<data name="LowercaseAtoZ" xml:space="preserve">
<value>Kleinbuchstaben (A bis Z)</value>
</data>
<data name="NumbersZeroToNine" xml:space="preserve">
<value>Zahlen (0 bis 9)</value>
</data>
<data name="SpecialCharacters" xml:space="preserve">
<value>Sonderzeichen (!@#$%^&amp;*)</value>
</data>
<data name="TapToGoBack" xml:space="preserve">
<value>Tippen, um zurück zu gehen</value>
</data>
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
<value>Passwort ist sichtbar, tippen um es auszublenden.</value>
</data>
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
<value>Passwort ist nicht sichtbar, tippen um es einzublenden.</value>
</data>
<data name="FilterByVault" xml:space="preserve">
<value>Objekte nach Tresor filtern</value>
</data>
<data name="AllVaults" xml:space="preserve">
<value>Alle Tresore</value>
</data>
<data name="Vaults" xml:space="preserve">
<value>Tresore</value>
</data>
<data name="VaultFilterDescription" xml:space="preserve">
<value>Tresor: {0}</value>
</data>
<data name="All" xml:space="preserve">
<value>Alle</value>
</data>
<data name="Totp" xml:space="preserve">
<value>TOTP</value>
</data>
<data name="VerificationCodes" xml:space="preserve">
<value>Verifizierungscodes</value>
</data>
<data name="PremiumSubscriptionRequired" xml:space="preserve">
<value>Premium-Abonnement erforderlich</value>
</data>
<data name="CannotAddAuthenticatorKey" xml:space="preserve">
<value>Authentifizierungsschlüssel lässt sich nicht hinzufügen? </value>
</data>
<data name="ScanQRCode" xml:space="preserve">
<value>QR Code scannen</value>
</data>
<data name="CannotScanQRCode" xml:space="preserve">
<value>QR Code kann nicht gescannt werden? </value>
</data>
<data name="AuthenticatorKeyScanner" xml:space="preserve">
<value>Authentifizierungsschlüssel</value>
</data>
<data name="EnterKeyManually" xml:space="preserve">
<value>Schlüssel manuell eingeben</value>
</data>
<data name="AddTotp" xml:space="preserve">
<value>TOTP hinzufügen</value>
</data>
<data name="SetupTotp" xml:space="preserve">
<value>TOTP einrichten</value>
</data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Sobald der Schlüssel erfolgreich eingegeben wurde, wähle TOTP hinzufügen, um den Schlüssel sicher abzuspeichern</value>
</data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve">
<value>Wenn du deine Sperroptionen auf „Nie“ einstellst, bleibt dein Tresor für jeden zugänglich, der Zugriff auf dein Gerät hat. Wenn du diese Option verwendest, solltest du sicherstellen, dass du dein Gerät angemessen schützt.</value>
</data>
<data name="EnvironmentPageUrlsError" xml:space="preserve">
<value>Eine oder mehrere der eingegebenen URLs sind ungültig. Bitte überprüfe sie und versuche erneut zu speichern.</value>
</data>
<data name="GenericErrorMessage" xml:space="preserve">
<value>Wir konnten deine Anfrage nicht bearbeiten. Bitte versuche es erneut oder kontaktiere uns.</value>
</data>
<data name="AllowScreenCapture" xml:space="preserve">
<value>Bildschirmaufnahme erlauben</value>
</data>
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
<value>Bist du sicher, dass du die Bildschirmaufnahme aktivieren möchtest?</value>
</data>
</root>

Some files were not shown because too many files have changed in this diff Show More