mirror of
https://github.com/bitwarden/mobile
synced 2025-12-10 21:33:36 +00:00
Compare commits
35 Commits
uitests
...
bug/ps-675
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b54371dacd | ||
|
|
e51233bf9b | ||
|
|
f9cbe43627 | ||
|
|
5579817f9f | ||
|
|
51a5f58258 | ||
|
|
388ad4e840 | ||
|
|
48a8d9ae35 | ||
|
|
dd6003bd4f | ||
|
|
fba407f3b6 | ||
|
|
88b406544b | ||
|
|
3438ed94ce | ||
|
|
ec71b21264 | ||
|
|
b223f5f16e | ||
|
|
0a64e4c918 | ||
|
|
9b41db962e | ||
|
|
43d3c7b5d7 | ||
|
|
8168089591 | ||
|
|
6b55fc3032 | ||
|
|
87ab42b155 | ||
|
|
98130e89de | ||
|
|
121f0e3628 | ||
|
|
8a3d88b3ce | ||
|
|
b8b41fe847 | ||
|
|
5bbef3ee16 | ||
|
|
9a2b6c8ec9 | ||
|
|
5272c99643 | ||
|
|
43e9515a03 | ||
|
|
7e9b7398c8 | ||
|
|
58d7b001a5 | ||
|
|
a259560d29 | ||
|
|
22c746543a | ||
|
|
bcbc2738ca | ||
|
|
604e3b6892 | ||
|
|
b081a8c634 | ||
|
|
c251b950d1 |
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -21,12 +21,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Testing requirements
|
|
||||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Before you submit
|
## Before you submit
|
||||||
|
- [ ] I have checked for formatting errors (`dotnet tool run dotnet-format --check`) (required)
|
||||||
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
||||||
- [ ] This change requires a **documentation update** (notify the documentation team)
|
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||||
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||||
|
|||||||
64
.github/workflows/automatic-issue-responses.yml
vendored
Normal file
64
.github/workflows/automatic-issue-responses.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
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: |
|
||||||
|
We’ve 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 haven’t 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.
|
||||||
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -304,18 +304,6 @@ jobs:
|
|||||||
|
|
||||||
$xml.Save($androidPath);
|
$xml.Save($androidPath);
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Uninstall from App.csproj"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($appPath);
|
|
||||||
|
|
||||||
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
|
||||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
|
||||||
|
|
||||||
$xml.Save($appPath);
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
Write-Output "##### Uninstall from Core.csproj"
|
Write-Output "##### Uninstall from Core.csproj"
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
|
|||||||
34
.github/workflows/release.yml
vendored
34
.github/workflows/release.yml
vendored
@@ -34,29 +34,13 @@ jobs:
|
|||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||||
|
|
||||||
- name: Retrieve Mobile release version
|
- name: Check Release Version
|
||||||
id: retrieve-mobile-version
|
id: version
|
||||||
run: |
|
uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
|
||||||
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' \
|
with:
|
||||||
./src/Android/Properties/AndroidManifest.xml | tr -d '"')
|
release-type: ${{ github.event.inputs.release_type }}
|
||||||
echo "::set-output name=mobile_version::${ver}"
|
project-type: xamarin
|
||||||
shell: bash
|
file: src/Android/Properties/AndroidManifest.xml
|
||||||
|
|
||||||
- name: Check to make sure Mobile release version has been bumped
|
|
||||||
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
latest_ver=$(hub release -L 1 -f '%T')
|
|
||||||
latest_ver=${latest_ver:1}
|
|
||||||
echo "Latest version: $latest_ver"
|
|
||||||
ver=${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
|
||||||
echo "Version: $ver"
|
|
||||||
if [ "$latest_ver" = "$ver" ]; then
|
|
||||||
echo "Version has not been bumped!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Get branch name
|
- name: Get branch name
|
||||||
id: branch
|
id: branch
|
||||||
@@ -83,8 +67,8 @@ jobs:
|
|||||||
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
||||||
./Bitwarden iOS.zip"
|
./Bitwarden iOS.zip"
|
||||||
commit: ${{ github.sha }}
|
commit: ${{ github.sha }}
|
||||||
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
tag: v${{ steps.version.outputs.version }}
|
||||||
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
name: Version ${{ steps.version.outputs.version }}
|
||||||
body: "<insert release notes here>"
|
body: "<insert release notes here>"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
draft: true
|
draft: true
|
||||||
|
|||||||
30
.github/workflows/stale-bot.yml
vendored
Normal file
30
.github/workflows/stale-bot.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
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 haven’t 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 we’ve requested and anything else relevant.
|
||||||
|
close-pr-message: |
|
||||||
|
We can’t merge your pull request until you make the changes we’ve requested. As we haven’t heard from you recently, this pull request will be closed.
|
||||||
|
|
||||||
|
If you’re still working on this, please respond here after you’ve made the changes we’ve 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.
|
||||||
@@ -1,40 +1,3 @@
|
|||||||
# How to Contribute
|
# How to Contribute
|
||||||
|
|
||||||
Contributions of all kinds are welcome!
|
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.
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
[](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/
|
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -12,16 +12,7 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
|
|||||||
|
|
||||||
# Build/Run
|
# Build/Run
|
||||||
|
|
||||||
**Requirements**
|
Please refer to the [Mobile section](https://contributing.bitwarden.com/clients/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.
|
||||||
|
|
||||||
- [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!
|
# We're Hiring!
|
||||||
|
|
||||||
@@ -29,8 +20,7 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
|
|||||||
|
|
||||||
# Contribute
|
# Contribute
|
||||||
|
|
||||||
Code contributions are welcome! Visual Studio with Xamarin is required to work on this project. Please commit any pull requests against the `master` branch.
|
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.
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -45,11 +35,3 @@ We recently migrated to using dotnet-format as code formatter. All previous bran
|
|||||||
5. Commit
|
5. Commit
|
||||||
6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357`
|
6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357`
|
||||||
7. Push
|
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
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.7.2</Version>
|
<Version>1.7.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||||
<Version>122.0.0</Version>
|
<Version>122.0.0</Version>
|
||||||
@@ -145,12 +145,12 @@
|
|||||||
<Compile Include="Tiles\GeneratorTileService.cs" />
|
<Compile Include="Tiles\GeneratorTileService.cs" />
|
||||||
<Compile Include="Tiles\MyVaultTileService.cs" />
|
<Compile Include="Tiles\MyVaultTileService.cs" />
|
||||||
<Compile Include="Utilities\AndroidHelpers.cs" />
|
<Compile Include="Utilities\AndroidHelpers.cs" />
|
||||||
<Compile Include="Utilities\AppCenterHelper.cs" />
|
|
||||||
<Compile Include="Utilities\ThemeHelpers.cs" />
|
<Compile Include="Utilities\ThemeHelpers.cs" />
|
||||||
<Compile Include="WebAuthCallbackActivity.cs" />
|
<Compile Include="WebAuthCallbackActivity.cs" />
|
||||||
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
|
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
|
||||||
<Compile Include="Services\ClipboardService.cs" />
|
<Compile Include="Services\ClipboardService.cs" />
|
||||||
<Compile Include="Utilities\IntentExtensions.cs" />
|
<Compile Include="Utilities\IntentExtensions.cs" />
|
||||||
|
<Compile Include="Renderers\CustomPageRenderer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
||||||
|
|||||||
@@ -69,10 +69,7 @@ namespace Bit.Droid
|
|||||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !DEBUG && !FDROID
|
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||||
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
|
|
||||||
var appCenterTask = appCenterHelper.InitAsync();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var toplayout = Window?.DecorView?.RootView;
|
var toplayout = Window?.DecorView?.RootView;
|
||||||
if (toplayout != null)
|
if (toplayout != null)
|
||||||
@@ -85,6 +82,7 @@ namespace Bit.Droid
|
|||||||
_appOptions = GetOptions();
|
_appOptions = GetOptions();
|
||||||
LoadApplication(new App.App(_appOptions));
|
LoadApplication(new App.App(_appOptions));
|
||||||
|
|
||||||
|
|
||||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
_broadcasterService.Subscribe(_activityKey, (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "startEventTimer")
|
if (message.Command == "startEventTimer")
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Services;
|
using Bit.Droid.Services;
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
using Plugin.Fingerprint;
|
using Plugin.Fingerprint;
|
||||||
using Xamarin.Android.Net;
|
using Xamarin.Android.Net;
|
||||||
@@ -20,6 +19,7 @@ using System.Net.Http;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using Android.Gms.Security;
|
using Android.Gms.Security;
|
||||||
#endif
|
#endif
|
||||||
@@ -62,6 +62,15 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
||||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||||
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
||||||
|
|
||||||
|
var accountsManager = new AccountsManager(
|
||||||
|
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
|
||||||
|
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
|
||||||
|
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||||
|
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||||
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
|
ServiceContainer.Resolve<IAuthService>("authService"));
|
||||||
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||||
@@ -124,10 +133,11 @@ namespace Bit.Droid
|
|||||||
var stateService = new StateService(mobileStorageService, secureStorageService);
|
var stateService = new StateService(mobileStorageService, secureStorageService);
|
||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var deviceActionService = new DeviceActionService(stateService, messagingService,
|
var clipboardService = new ClipboardService(stateService);
|
||||||
|
var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
|
||||||
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
||||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||||
broadcasterService);
|
messagingService, broadcasterService);
|
||||||
var biometricService = new BiometricService();
|
var biometricService = new BiometricService();
|
||||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||||
@@ -142,7 +152,7 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||||
ServiceContainer.Register<IStateService>("stateService", stateService);
|
ServiceContainer.Register<IStateService>("stateService", stateService);
|
||||||
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
|
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
|
||||||
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
|
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
|
||||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.18.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.05.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
|
|||||||
31
src/Android/Renderers/CustomPageRenderer.cs
Normal file
31
src/Android/Renderers/CustomPageRenderer.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using AndroidX.AppCompat.Widget;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
|
||||||
|
namespace Bit.Droid.Renderers
|
||||||
|
{
|
||||||
|
public class CustomPageRenderer : PageRenderer
|
||||||
|
{
|
||||||
|
public CustomPageRenderer(Context context) : base(context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
Activity context = (Activity)this.Context;
|
||||||
|
var toolbar = context.FindViewById<Toolbar>(Resource.Id.toolbar);
|
||||||
|
if(toolbar != null)
|
||||||
|
{
|
||||||
|
toolbar.NavigationContentDescription = AppResources.TapToGoBack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -3,11 +3,10 @@ using System.Threading.Tasks;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Security.Keystore;
|
using Android.Security.Keystore;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Java.Security;
|
using Java.Security;
|
||||||
using Javax.Crypto;
|
using Javax.Crypto;
|
||||||
#if !FDROID
|
|
||||||
using Microsoft.AppCenter.Crashes;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
{
|
{
|
||||||
@@ -74,9 +73,7 @@ namespace Bit.Droid.Services
|
|||||||
catch (InvalidKeyException e)
|
catch (InvalidKeyException e)
|
||||||
{
|
{
|
||||||
// Fallback for old bitwarden users without a key
|
// Fallback for old bitwarden users without a key
|
||||||
#if !FDROID
|
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||||
Crashes.TrackError(e);
|
|
||||||
#endif
|
|
||||||
CreateKey();
|
CreateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +98,7 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
// Catch silently to allow biometrics to function on devices that are in a state where key generation
|
// Catch silently to allow biometrics to function on devices that are in a state where key generation
|
||||||
// is not functioning
|
// is not functioning
|
||||||
#if !FDROID
|
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||||
Crashes.TrackError(e);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Bit.Core;
|
using Android.OS;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Droid.Receivers;
|
using Bit.Droid.Receivers;
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
@@ -26,13 +26,41 @@ namespace Bit.Droid.Services
|
|||||||
PendingIntentFlags.UpdateCurrent));
|
PendingIntentFlags.UpdateCurrent));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CopyTextAsync(string text, int expiresInMs = -1)
|
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||||
{
|
{
|
||||||
await Clipboard.SetTextAsync(text);
|
// 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 ClearClipboardAlarmAsync(expiresInMs);
|
await ClearClipboardAlarmAsync(expiresInMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsCopyNotificationHandledByPlatform()
|
||||||
|
{
|
||||||
|
// Android 13+ provides built-in notification when text is copied to the clipboard
|
||||||
|
return (int)Build.VERSION.SdkInt >= 33;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyToClipboard(string text, bool isSensitive = true)
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
var clipboardManager = activity.GetSystemService(
|
||||||
|
Context.ClipboardService) as Android.Content.ClipboardManager;
|
||||||
|
var clipData = ClipData.NewPlainText("bitwarden", text);
|
||||||
|
if (isSensitive)
|
||||||
|
{
|
||||||
|
clipData.Description.Extras ??= new PersistableBundle();
|
||||||
|
clipData.Description.Extras.PutBoolean("android.content.extra.IS_SENSITIVE", true);
|
||||||
|
}
|
||||||
|
clipboardManager.PrimaryClip = clipData;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
|
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
|
||||||
{
|
{
|
||||||
var clearMs = expiresInMs;
|
var clearMs = expiresInMs;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
public class DeviceActionService : IDeviceActionService
|
public class DeviceActionService : IDeviceActionService
|
||||||
{
|
{
|
||||||
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
@@ -47,11 +48,13 @@ namespace Bit.Droid.Services
|
|||||||
private string _userAgent;
|
private string _userAgent;
|
||||||
|
|
||||||
public DeviceActionService(
|
public DeviceActionService(
|
||||||
|
IClipboardService clipboardService,
|
||||||
IStateService stateService,
|
IStateService stateService,
|
||||||
IMessagingService messagingService,
|
IMessagingService messagingService,
|
||||||
IBroadcasterService broadcasterService,
|
IBroadcasterService broadcasterService,
|
||||||
Func<IEventService> eventServiceFunc)
|
Func<IEventService> eventServiceFunc)
|
||||||
{
|
{
|
||||||
|
_clipboardService = clipboardService;
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
_messagingService = messagingService;
|
_messagingService = messagingService;
|
||||||
_broadcasterService = broadcasterService;
|
_broadcasterService = broadcasterService;
|
||||||
@@ -929,20 +932,12 @@ namespace Bit.Droid.Services
|
|||||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||||
if (totp != null)
|
if (totp != null)
|
||||||
{
|
{
|
||||||
CopyToClipboard(totp);
|
await _clipboardService.CopyTextAsync(totp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyToClipboard(string text)
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
var clipboardManager = activity.GetSystemService(
|
|
||||||
Context.ClipboardService) as Android.Content.ClipboardManager;
|
|
||||||
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float GetSystemFontSizeScale()
|
public float GetSystemFontSizeScale()
|
||||||
{
|
{
|
||||||
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
#if !FDROID
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AppCenter;
|
|
||||||
using Microsoft.AppCenter.Crashes;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Utilities
|
|
||||||
{
|
|
||||||
public class AppCenterHelper
|
|
||||||
{
|
|
||||||
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
|
|
||||||
|
|
||||||
private readonly IAppIdService _appIdService;
|
|
||||||
private readonly IStateService _stateService;
|
|
||||||
|
|
||||||
private string _userId;
|
|
||||||
private string _appId;
|
|
||||||
|
|
||||||
public AppCenterHelper(
|
|
||||||
IAppIdService appIdService,
|
|
||||||
IStateService stateService)
|
|
||||||
{
|
|
||||||
_appIdService = appIdService;
|
|
||||||
_stateService = stateService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
|
||||||
{
|
|
||||||
_userId = await _stateService.GetActiveUserIdAsync();
|
|
||||||
_appId = await _appIdService.GetAppIdAsync();
|
|
||||||
|
|
||||||
AppCenter.Start(AppSecret, typeof(Crashes));
|
|
||||||
AppCenter.SetUserId(_userId);
|
|
||||||
|
|
||||||
Crashes.GetErrorAttachments = (ErrorReport report) =>
|
|
||||||
{
|
|
||||||
return new ErrorAttachmentLog[]
|
|
||||||
{
|
|
||||||
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Description
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return JsonConvert.SerializeObject(new
|
|
||||||
{
|
|
||||||
AppId = _appId,
|
|
||||||
UserId = _userId
|
|
||||||
}, Formatting.Indented);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
12
src/App/Abstractions/IAccountsManager.cs
Normal file
12
src/App/Abstractions/IAccountsManager.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
|
|
||||||
|
namespace Bit.App.Abstractions
|
||||||
|
{
|
||||||
|
public interface IAccountsManager
|
||||||
|
{
|
||||||
|
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
|
||||||
|
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/App/Abstractions/IAccountsManagerHost.cs
Normal file
14
src/App/Abstractions/IAccountsManagerHost.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.App.Abstractions
|
||||||
|
{
|
||||||
|
public interface INavigationParams { }
|
||||||
|
|
||||||
|
public interface IAccountsManagerHost
|
||||||
|
{
|
||||||
|
Task SetPreviousPageInfoAsync();
|
||||||
|
void Navigate(NavigationTarget navTarget, INavigationParams navParams = null);
|
||||||
|
Task UpdateThemeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,13 +13,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
|
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
||||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
|
|
||||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
|
||||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.1" />
|
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.2" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
|
||||||
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -129,6 +128,7 @@
|
|||||||
<Folder Include="Resources\" />
|
<Folder Include="Resources\" />
|
||||||
<Folder Include="Behaviors\" />
|
<Folder Include="Behaviors\" />
|
||||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||||
|
<Folder Include="Utilities\AccountManagement\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -421,5 +421,6 @@
|
|||||||
<None Remove="Behaviors\" />
|
<None Remove="Behaviors\" />
|
||||||
<None Remove="Xamarin.CommunityToolkit" />
|
<None Remove="Xamarin.CommunityToolkit" />
|
||||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||||
|
<None Remove="Utilities\AccountManagement\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Bit.App.Pages;
|
|||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Services;
|
using Bit.App.Services;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
@@ -16,7 +17,7 @@ using Xamarin.Forms.Xaml;
|
|||||||
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
|
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
|
||||||
namespace Bit.App
|
namespace Bit.App
|
||||||
{
|
{
|
||||||
public partial class App : Application
|
public partial class App : Application, IAccountsManagerHost
|
||||||
{
|
{
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
@@ -27,6 +28,7 @@ namespace Bit.App
|
|||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
private readonly IStorageService _secureStorageService;
|
private readonly IStorageService _secureStorageService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
private readonly IAccountsManager _accountsManager;
|
||||||
|
|
||||||
private static bool _isResumed;
|
private static bool _isResumed;
|
||||||
|
|
||||||
@@ -47,6 +49,9 @@ namespace Bit.App
|
|||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||||
|
|
||||||
|
_accountsManager.Init(() => Options, this);
|
||||||
|
|
||||||
Bootstrap();
|
Bootstrap();
|
||||||
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
||||||
@@ -71,30 +76,6 @@ namespace Bit.App
|
|||||||
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (message.Command == "locked")
|
|
||||||
{
|
|
||||||
var extras = message.Data as Tuple<string, bool>;
|
|
||||||
var userId = extras?.Item1;
|
|
||||||
var userInitiated = extras?.Item2 ?? false;
|
|
||||||
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
|
||||||
}
|
|
||||||
else if (message.Command == "lockVault")
|
|
||||||
{
|
|
||||||
await _vaultTimeoutService.LockAsync(true);
|
|
||||||
}
|
|
||||||
else if (message.Command == "logout")
|
|
||||||
{
|
|
||||||
var extras = message.Data as Tuple<string, bool, bool>;
|
|
||||||
var userId = extras?.Item1;
|
|
||||||
var userInitiated = extras?.Item2 ?? true;
|
|
||||||
var expired = extras?.Item3 ?? false;
|
|
||||||
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
|
|
||||||
}
|
|
||||||
else if (message.Command == "loggedOut")
|
|
||||||
{
|
|
||||||
// Clean up old migrated key if they ever log out.
|
|
||||||
await _secureStorageService.RemoveAsync("oldKey");
|
|
||||||
}
|
|
||||||
else if (message.Command == "resumed")
|
else if (message.Command == "resumed")
|
||||||
{
|
{
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
@@ -109,22 +90,10 @@ namespace Bit.App
|
|||||||
await SleptAsync();
|
await SleptAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (message.Command == "addAccount")
|
|
||||||
{
|
|
||||||
await AddAccount();
|
|
||||||
}
|
|
||||||
else if (message.Command == "accountAdded")
|
|
||||||
{
|
|
||||||
await UpdateThemeAsync();
|
|
||||||
}
|
|
||||||
else if (message.Command == "switchedAccount")
|
|
||||||
{
|
|
||||||
await SwitchedAccountAsync();
|
|
||||||
}
|
|
||||||
else if (message.Command == "migrated")
|
else if (message.Command == "migrated")
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
await SetMainPageAsync();
|
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||||
}
|
}
|
||||||
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
||||||
message.Command == "popAllAndGoToTabMyVault" ||
|
message.Command == "popAllAndGoToTabMyVault" ||
|
||||||
@@ -168,7 +137,6 @@ namespace Bit.App
|
|||||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,102 +231,6 @@ namespace Bit.App
|
|||||||
new System.Globalization.UmAlQuraCalendar();
|
new System.Globalization.UmAlQuraCalendar();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
|
|
||||||
{
|
|
||||||
await AppHelpers.LogOutAsync(userId, userInitiated);
|
|
||||||
await SetMainPageAsync();
|
|
||||||
_authService.LogOut(() =>
|
|
||||||
{
|
|
||||||
if (expired)
|
|
||||||
{
|
|
||||||
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task AddAccount()
|
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
Options.HideAccountSwitcher = false;
|
|
||||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SwitchedAccountAsync()
|
|
||||||
{
|
|
||||||
await AppHelpers.OnAccountSwitchAsync();
|
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
|
||||||
{
|
|
||||||
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await SetMainPageAsync();
|
|
||||||
}
|
|
||||||
await Task.Delay(50);
|
|
||||||
await UpdateThemeAsync();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SetMainPageAsync()
|
|
||||||
{
|
|
||||||
var authed = await _stateService.IsAuthenticatedAsync();
|
|
||||||
if (authed)
|
|
||||||
{
|
|
||||||
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
|
|
||||||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
|
||||||
{
|
|
||||||
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
|
||||||
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
|
||||||
|
|
||||||
var email = await _stateService.GetEmailAsync();
|
|
||||||
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
|
||||||
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
|
|
||||||
}
|
|
||||||
else if (await _vaultTimeoutService.IsLockedAsync() ||
|
|
||||||
await _vaultTimeoutService.ShouldLockAsync())
|
|
||||||
{
|
|
||||||
Current.MainPage = new NavigationPage(new LockPage(Options));
|
|
||||||
}
|
|
||||||
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
|
|
||||||
{
|
|
||||||
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
|
|
||||||
}
|
|
||||||
else if (Options.Uri != null)
|
|
||||||
{
|
|
||||||
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
|
|
||||||
}
|
|
||||||
else if (Options.CreateSend != null)
|
|
||||||
{
|
|
||||||
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Current.MainPage = new TabsPage(Options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
|
||||||
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
|
|
||||||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
|
||||||
{
|
|
||||||
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
|
||||||
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
|
||||||
|
|
||||||
var email = await _stateService.GetEmailAsync();
|
|
||||||
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ClearCacheIfNeededAsync()
|
private async Task ClearCacheIfNeededAsync()
|
||||||
{
|
{
|
||||||
var lastClear = await _stateService.GetLastFileCacheClearAsync();
|
var lastClear = await _stateService.GetLastFileCacheClearAsync();
|
||||||
@@ -420,7 +292,7 @@ namespace Bit.App
|
|||||||
UpdateThemeAsync();
|
UpdateThemeAsync();
|
||||||
};
|
};
|
||||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||||
var mainPageTask = SetMainPageAsync();
|
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
|
||||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,23 +313,8 @@ namespace Bit.App
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LockedAsync(string userId, bool userInitiated)
|
public async Task SetPreviousPageInfoAsync()
|
||||||
{
|
{
|
||||||
if (!await _stateService.IsActiveAccountAsync(userId))
|
|
||||||
{
|
|
||||||
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var autoPromptBiometric = !userInitiated;
|
|
||||||
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
|
|
||||||
{
|
|
||||||
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
|
|
||||||
if (vaultTimeout == 0)
|
|
||||||
{
|
|
||||||
autoPromptBiometric = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PreviousPageInfo lastPageBeforeLock = null;
|
PreviousPageInfo lastPageBeforeLock = null;
|
||||||
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
|
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -483,8 +340,44 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
|
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
|
||||||
var lockPage = new LockPage(Options, autoPromptBiometric);
|
}
|
||||||
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
|
|
||||||
|
public void Navigate(NavigationTarget navTarget, INavigationParams navParams)
|
||||||
|
{
|
||||||
|
switch (navTarget)
|
||||||
|
{
|
||||||
|
case NavigationTarget.HomeLogin:
|
||||||
|
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Login:
|
||||||
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
|
{
|
||||||
|
Current.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Lock:
|
||||||
|
if (navParams is LockNavigationParams lockParams)
|
||||||
|
{
|
||||||
|
Current.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Current.MainPage = new NavigationPage(new LockPage(Options));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Home:
|
||||||
|
Current.MainPage = new TabsPage(Options);
|
||||||
|
break;
|
||||||
|
case NavigationTarget.AddEditCipher:
|
||||||
|
Current.MainPage = new NavigationPage(new AddEditPage(appOptions: Options));
|
||||||
|
break;
|
||||||
|
case NavigationTarget.AutofillCiphers:
|
||||||
|
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
|
||||||
|
break;
|
||||||
|
case NavigationTarget.SendAddEdit:
|
||||||
|
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
|
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
|
||||||
|
|
||||||
|
public bool LongPressAccountEnabled { get; set; } = true;
|
||||||
|
|
||||||
public async Task ToggleVisibilityAsync()
|
public async Task ToggleVisibilityAsync()
|
||||||
{
|
{
|
||||||
if (IsVisible)
|
if (IsVisible)
|
||||||
@@ -167,7 +169,7 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
|
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
|
||||||
{
|
{
|
||||||
if (!item.IsAccount)
|
if (!LongPressAccountEnabled || !item.IsAccount)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Text=""
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||||
Clicked="MoreButton_Clicked"
|
Clicked="MoreButton_Clicked"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
public class ExtendedCollectionView : CollectionView
|
public class ExtendedCollectionView : CollectionView
|
||||||
{
|
{
|
||||||
|
public string ExtraDataForLogging { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Text=""
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
IsVisible="{Binding ShowOptions, Mode=OneWay}"
|
IsVisible="{Binding ShowOptions, Mode=OneWay}"
|
||||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
||||||
Clicked="MoreButton_Clicked"
|
Clicked="MoreButton_Clicked"
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get => _showPassword;
|
get => _showPassword;
|
||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
|
additionalPropertyNames: new[]
|
||||||
|
{
|
||||||
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPolicyInEffect
|
public bool IsPolicyInEffect
|
||||||
@@ -68,6 +72,8 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="_passwordGrid"
|
x:Name="_passwordGrid"
|
||||||
@@ -119,7 +119,8 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +129,8 @@ namespace Bit.App.Pages
|
|||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string Pin { get; set; }
|
public string Pin { get; set; }
|
||||||
public Action UnlockedAction { get; set; }
|
public Action UnlockedAction { get; set; }
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Padding="10, 0">
|
<StackLayout Padding="10, 0">
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +86,8 @@ namespace Bit.App.Pages
|
|||||||
public Command LogInCommand { get; }
|
public Command LogInCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action LogInSuccessAction { get; set; }
|
public Action LogInSuccessAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +74,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get => _showPassword;
|
get => _showPassword;
|
||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
|
additionalPropertyNames: new[]
|
||||||
|
{
|
||||||
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPolicyInEffect
|
public bool IsPolicyInEffect
|
||||||
@@ -86,6 +90,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
|
private readonly IAppIdService _appIdService;
|
||||||
|
|
||||||
private TwoFactorProviderType? _selectedProviderType;
|
private TwoFactorProviderType? _selectedProviderType;
|
||||||
private string _totpInstruction;
|
private string _totpInstruction;
|
||||||
@@ -49,6 +50,7 @@ namespace Bit.App.Pages
|
|||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
|
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||||
|
|
||||||
PageTitle = AppResources.TwoStepLogin;
|
PageTitle = AppResources.TwoStepLogin;
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
@@ -380,7 +382,8 @@ namespace Bit.App.Pages
|
|||||||
var request = new TwoFactorEmailRequest
|
var request = new TwoFactorEmailRequest
|
||||||
{
|
{
|
||||||
Email = _authService.Email,
|
Email = _authService.Email,
|
||||||
MasterPasswordHash = _authService.MasterPasswordHash
|
MasterPasswordHash = _authService.MasterPasswordHash,
|
||||||
|
DeviceIdentifier = await _appIdService.GetAppIdAsync()
|
||||||
};
|
};
|
||||||
await _apiService.PostTwoFactorEmailAsync(request);
|
await _apiService.PostTwoFactorEmailAsync(request);
|
||||||
if (showLoading)
|
if (showLoading)
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -37,11 +37,14 @@ namespace Bit.App.Pages
|
|||||||
bool cancelled = false;
|
bool cancelled = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// PrefersEphemeralWebBrowserSession should be false to allow access to the hCaptcha accessibility
|
||||||
|
// cookie set in the default browser
|
||||||
|
// https://www.hcaptcha.com/accessibility
|
||||||
var options = new WebAuthenticatorOptions
|
var options = new WebAuthenticatorOptions
|
||||||
{
|
{
|
||||||
Url = new Uri(url),
|
Url = new Uri(url),
|
||||||
CallbackUrl = new Uri(callbackUri),
|
CallbackUrl = new Uri(callbackUri),
|
||||||
PrefersEphemeralWebBrowserSession = true,
|
PrefersEphemeralWebBrowserSession = false,
|
||||||
};
|
};
|
||||||
authResult = await WebAuthenticator.AuthenticateAsync(options);
|
authResult = await WebAuthenticator.AuthenticateAsync(options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,8 @@
|
|||||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||||
ItemsSource="{Binding History}"
|
ItemsSource="{Binding History}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Generator History Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="domain:GeneratedPasswordHistory">
|
<DataTemplate x:DataType="domain:GeneratedPasswordHistory">
|
||||||
<Grid
|
<Grid
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ namespace Bit.App.Pages
|
|||||||
private async void CopyAsync(GeneratedPasswordHistory ph)
|
private async void CopyAsync(GeneratedPasswordHistory ph)
|
||||||
{
|
{
|
||||||
await _clipboardService.CopyTextAsync(ph.Password);
|
await _clipboardService.CopyTextAsync(ph.Password);
|
||||||
_platformUtilsService.ShowToast("info", null,
|
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateOnThemeChanged()
|
public async Task UpdateOnThemeChanged()
|
||||||
|
|||||||
@@ -183,52 +183,68 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="A-Z"
|
Text="A-Z"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Uppercase}"
|
IsToggled="{Binding Uppercase}"
|
||||||
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
|
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="a-z"
|
Text="a-z"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Lowercase}"
|
IsToggled="{Binding Lowercase}"
|
||||||
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
|
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n LowercaseAtoZ}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="0-9"
|
Text="0-9"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Number}"
|
IsToggled="{Binding Number}"
|
||||||
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
|
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="!@#$%^&*"
|
Text="!@#$%^&*"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Special}"
|
IsToggled="{Binding Special}"
|
||||||
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
|
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-stepper">
|
<StackLayout StyleClass="box-row, box-row-stepper">
|
||||||
@@ -277,7 +293,7 @@
|
|||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AvoidAmbiguous}"
|
IsToggled="{Binding AvoidAmbiguousChars}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Bit.App.Utilities;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@@ -23,7 +22,7 @@ namespace Bit.App.Pages
|
|||||||
private bool _lowercase;
|
private bool _lowercase;
|
||||||
private bool _number;
|
private bool _number;
|
||||||
private bool _special;
|
private bool _special;
|
||||||
private bool _avoidAmbiguous;
|
private bool _allowAmbiguousChars;
|
||||||
private int _minNumber;
|
private int _minNumber;
|
||||||
private int _minSpecial;
|
private int _minSpecial;
|
||||||
private int _length = 5;
|
private int _length = 5;
|
||||||
@@ -130,19 +129,29 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AvoidAmbiguous
|
public bool AllowAmbiguousChars
|
||||||
{
|
{
|
||||||
get => _avoidAmbiguous;
|
get => _allowAmbiguousChars;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _avoidAmbiguous, value))
|
if (SetProperty(ref _allowAmbiguousChars, value,
|
||||||
|
additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(AvoidAmbiguousChars)
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
_options.Ambiguous = !value;
|
_options.AllowAmbiguousChar = value;
|
||||||
var task = SaveOptionsAsync();
|
var task = SaveOptionsAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AvoidAmbiguousChars
|
||||||
|
{
|
||||||
|
get => !AllowAmbiguousChars;
|
||||||
|
set => AllowAmbiguousChars = !value;
|
||||||
|
}
|
||||||
|
|
||||||
public int MinNumber
|
public int MinNumber
|
||||||
{
|
{
|
||||||
get => _minNumber;
|
get => _minNumber;
|
||||||
@@ -309,13 +318,12 @@ namespace Bit.App.Pages
|
|||||||
public async Task CopyAsync()
|
public async Task CopyAsync()
|
||||||
{
|
{
|
||||||
await _clipboardService.CopyTextAsync(Password);
|
await _clipboardService.CopyTextAsync(Password);
|
||||||
_platformUtilsService.ShowToast("success", null,
|
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadFromOptions()
|
private void LoadFromOptions()
|
||||||
{
|
{
|
||||||
AvoidAmbiguous = !_options.Ambiguous.GetValueOrDefault();
|
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault();
|
||||||
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
|
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
|
||||||
IsPassword = TypeSelectedIndex == 0;
|
IsPassword = TypeSelectedIndex == 0;
|
||||||
MinNumber = _options.MinNumber.GetValueOrDefault();
|
MinNumber = _options.MinNumber.GetValueOrDefault();
|
||||||
@@ -333,7 +341,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private void SetOptions()
|
private void SetOptions()
|
||||||
{
|
{
|
||||||
_options.Ambiguous = !AvoidAmbiguous;
|
_options.AllowAmbiguousChar = AllowAmbiguousChars;
|
||||||
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
|
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
|
||||||
_options.MinNumber = MinNumber;
|
_options.MinNumber = MinNumber;
|
||||||
_options.MinSpecial = MinSpecial;
|
_options.MinSpecial = MinSpecial;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
||||||
x:Class="Bit.App.Pages.SendAddEditPage"
|
x:Class="Bit.App.Pages.SendAddEditPage"
|
||||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
@@ -121,6 +122,7 @@
|
|||||||
Clicked="FileType_Clicked"
|
Clicked="FileType_Clicked"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n File}"
|
AutomationProperties.Name="{u:I18n File}"
|
||||||
|
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -132,6 +134,7 @@
|
|||||||
Clicked="TextType_Clicked"
|
Clicked="TextType_Clicked"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Text}"
|
AutomationProperties.Name="{u:I18n Text}"
|
||||||
|
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
|
||||||
Grid.Column="1">
|
Grid.Column="1">
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -250,28 +253,31 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="0">
|
Spacing="0"
|
||||||
|
xct:TouchEffect.Command="{Binding ToggleOptionsCommand}"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n Options}"
|
Text="{u:I18n Options}"
|
||||||
x:Name="_btnOptions"
|
x:Name="_btnOptions"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
Clicked="ToggleOptions_Clicked"/>
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
x:Name="_btnOptionsUp"
|
x:Name="_btnOptionsUp"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Clicked="ToggleOptions_Clicked"
|
IsVisible="{Binding ShowOptions}"
|
||||||
IsVisible="{Binding ShowOptions}" />
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
x:Name="_btnOptionsDown"
|
x:Name="_btnOptionsDown"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Clicked="ToggleOptions_Clicked"
|
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
|
||||||
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" />
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding ShowOptions}">
|
<StackLayout IsVisible="{Binding ShowOptions}">
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -438,7 +444,7 @@
|
|||||||
Command="{Binding TogglePasswordCommand}"
|
Command="{Binding TogglePasswordCommand}"
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordInfo}"
|
Text="{u:I18n PasswordInfo}"
|
||||||
|
|||||||
@@ -209,11 +209,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ToggleOptions_Clicked(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
_vm.ToggleOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearExpirationDate_Clicked(object sender, EventArgs e)
|
private void ClearExpirationDate_Clicked(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
nameof(IsText),
|
nameof(IsText),
|
||||||
nameof(IsFile),
|
nameof(IsFile),
|
||||||
|
nameof(FileTypeAccessibilityLabel),
|
||||||
|
nameof(TextTypeAccessibilityLabel)
|
||||||
};
|
};
|
||||||
private bool _disableHideEmail;
|
private bool _disableHideEmail;
|
||||||
private bool _sendOptionsPolicyInEffect;
|
private bool _sendOptionsPolicyInEffect;
|
||||||
@@ -59,6 +61,7 @@ namespace Bit.App.Pages
|
|||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
|
ToggleOptionsCommand = new Command(ToggleOptions);
|
||||||
|
|
||||||
TypeOptions = new List<KeyValuePair<string, SendType>>
|
TypeOptions = new List<KeyValuePair<string, SendType>>
|
||||||
{
|
{
|
||||||
@@ -89,6 +92,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Command TogglePasswordCommand { get; set; }
|
public Command TogglePasswordCommand { get; set; }
|
||||||
|
public Command ToggleOptionsCommand { get; set; }
|
||||||
public string SendId { get; set; }
|
public string SendId { get; set; }
|
||||||
public int SegmentedButtonHeight { get; set; }
|
public int SegmentedButtonHeight { get; set; }
|
||||||
public int SegmentedButtonFontSize { get; set; }
|
public int SegmentedButtonFontSize { get; set; }
|
||||||
@@ -102,6 +106,7 @@ namespace Bit.App.Pages
|
|||||||
public bool DisableHideEmailControl { get; set; }
|
public bool DisableHideEmailControl { get; set; }
|
||||||
public bool IsAddFromShare { get; set; }
|
public bool IsAddFromShare { get; set; }
|
||||||
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
|
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
|
||||||
|
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
|
||||||
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
||||||
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
||||||
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
|
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
|
||||||
@@ -134,7 +139,11 @@ namespace Bit.App.Pages
|
|||||||
public bool ShowOptions
|
public bool ShowOptions
|
||||||
{
|
{
|
||||||
get => _showOptions;
|
get => _showOptions;
|
||||||
set => SetProperty(ref _showOptions, value);
|
set => SetProperty(ref _showOptions, value,
|
||||||
|
additionalPropertyNames: new[]
|
||||||
|
{
|
||||||
|
nameof(OptionsAccessilibityText)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
public int ExpirationDateTypeSelectedIndex
|
public int ExpirationDateTypeSelectedIndex
|
||||||
{
|
{
|
||||||
@@ -211,7 +220,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new[]
|
additionalPropertyNames: new[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public bool DisableHideEmail
|
public bool DisableHideEmail
|
||||||
@@ -231,6 +241,10 @@ namespace Bit.App.Pages
|
|||||||
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
||||||
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
|
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
||||||
|
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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"
|
<pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
x:Class="Bit.App.Pages.SendGroupingsPage"
|
x:Class="Bit.App.Pages.SendGroupingsPage"
|
||||||
@@ -138,7 +138,8 @@
|
|||||||
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform" />
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Send Groupings Page" />
|
||||||
</RefreshView>
|
</RefreshView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
@@ -66,7 +66,8 @@
|
|||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Sends Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:SendView">
|
<DataTemplate x:DataType="views:SendView">
|
||||||
<controls:SendViewCell
|
<controls:SendViewCell
|
||||||
|
|||||||
@@ -104,7 +104,7 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ConfirmYourIdentity}"
|
Text="{u:I18n ConfirmYourIdentity}"
|
||||||
|
|||||||
@@ -109,7 +109,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get => _showPassword;
|
get => _showPassword;
|
||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) });
|
additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UseOTPVerification
|
public bool UseOTPVerification
|
||||||
@@ -139,6 +143,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
|
|
||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,7 +38,8 @@
|
|||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Folders Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:FolderView">
|
<DataTemplate x:DataType="views:FolderView">
|
||||||
<controls:ExtendedStackLayout
|
<controls:ExtendedStackLayout
|
||||||
|
|||||||
@@ -113,6 +113,7 @@
|
|||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform" />
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Settings Page" />
|
||||||
|
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -167,6 +167,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _vm.UpdatePinAsync();
|
await _vm.UpdatePinAsync();
|
||||||
}
|
}
|
||||||
|
else if (item.Name == AppResources.SubmitCrashLogs)
|
||||||
|
{
|
||||||
|
await _vm.LoggerReportingAsync();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var biometricName = AppResources.Biometrics;
|
var biometricName = AppResources.Biometrics;
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ using Bit.App.Resources;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using ZXing.Client.Result;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@@ -29,7 +29,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly ILocalizeService _localizeService;
|
private readonly ILocalizeService _localizeService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
|
private readonly ILogger _loggerService;
|
||||||
private const int CustomVaultTimeoutValue = -100;
|
private const int CustomVaultTimeoutValue = -100;
|
||||||
|
|
||||||
private bool _supportsBiometric;
|
private bool _supportsBiometric;
|
||||||
@@ -39,6 +39,7 @@ namespace Bit.App.Pages
|
|||||||
private string _vaultTimeoutDisplayValue;
|
private string _vaultTimeoutDisplayValue;
|
||||||
private string _vaultTimeoutActionDisplayValue;
|
private string _vaultTimeoutActionDisplayValue;
|
||||||
private bool _showChangeMasterPassword;
|
private bool _showChangeMasterPassword;
|
||||||
|
private bool _reportLoggingEnabled;
|
||||||
|
|
||||||
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
||||||
new List<KeyValuePair<string, int?>>
|
new List<KeyValuePair<string, int?>>
|
||||||
@@ -79,6 +80,7 @@ namespace Bit.App.Pages
|
|||||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
|
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
@@ -123,7 +125,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
||||||
!await _keyConnectorService.GetUsesKeyConnector();
|
!await _keyConnectorService.GetUsesKeyConnector();
|
||||||
|
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +288,26 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task LoggerReportingAsync()
|
||||||
|
{
|
||||||
|
var options = new[]
|
||||||
|
{
|
||||||
|
CreateSelectableOption(AppResources.Yes, _reportLoggingEnabled),
|
||||||
|
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
|
||||||
|
};
|
||||||
|
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.SubmitCrashLogsDescription, AppResources.Cancel, null, options);
|
||||||
|
|
||||||
|
if (selection == null || selection == AppResources.Cancel)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _loggerService.SetEnabled(CompareSelection(selection, AppResources.Yes));
|
||||||
|
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||||
|
BuildList();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task VaultTimeoutActionAsync()
|
public async Task VaultTimeoutActionAsync()
|
||||||
{
|
{
|
||||||
var options = _vaultTimeoutActions.Select(o =>
|
var options = _vaultTimeoutActions.Select(o =>
|
||||||
@@ -494,11 +516,19 @@ namespace Bit.App.Pages
|
|||||||
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
|
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
|
||||||
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
|
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
|
||||||
}
|
}
|
||||||
|
|
||||||
var otherItems = new List<SettingsPageListItem>
|
var otherItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem { Name = AppResources.Options },
|
new SettingsPageListItem { Name = AppResources.Options },
|
||||||
new SettingsPageListItem { Name = AppResources.About },
|
new SettingsPageListItem { Name = AppResources.About },
|
||||||
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
|
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
|
||||||
|
#if !FDROID
|
||||||
|
new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.SubmitCrashLogs,
|
||||||
|
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
||||||
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
||||||
};
|
};
|
||||||
@@ -576,5 +606,9 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
|
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
||||||
|
|
||||||
|
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Bit.App.Effects;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Effects;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
@@ -10,8 +12,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class TabsPage : TabbedPage
|
public class TabsPage : TabbedPage
|
||||||
{
|
{
|
||||||
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
private NavigationPage _groupingsPage;
|
private NavigationPage _groupingsPage;
|
||||||
private NavigationPage _sendGroupingsPage;
|
private NavigationPage _sendGroupingsPage;
|
||||||
@@ -19,6 +23,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
||||||
{
|
{
|
||||||
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
|
|
||||||
@@ -78,12 +83,26 @@ namespace Bit.App.Pages
|
|||||||
protected override async void OnAppearing()
|
protected override async void OnAppearing()
|
||||||
{
|
{
|
||||||
base.OnAppearing();
|
base.OnAppearing();
|
||||||
|
_broadcasterService.Subscribe(nameof(TabsPage), async (message) =>
|
||||||
|
{
|
||||||
|
if (message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await UpdateVaultButtonTitleAsync());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await UpdateVaultButtonTitleAsync();
|
||||||
if (await _keyConnectorService.UserNeedsMigration())
|
if (await _keyConnectorService.UserNeedsMigration())
|
||||||
{
|
{
|
||||||
_messagingService.Send("convertAccountToKeyConnector");
|
_messagingService.Send("convertAccountToKeyConnector");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDisappearing()
|
||||||
|
{
|
||||||
|
base.OnDisappearing();
|
||||||
|
_broadcasterService.Unsubscribe(nameof(TabsPage));
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetToVaultPage()
|
public void ResetToVaultPage()
|
||||||
{
|
{
|
||||||
CurrentPage = _groupingsPage;
|
CurrentPage = _groupingsPage;
|
||||||
@@ -131,5 +150,19 @@ namespace Bit.App.Pages
|
|||||||
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
|
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateVaultButtonTitleAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
|
var isShowingVaultFilter = await policyService.ShouldShowVaultFilterAsync();
|
||||||
|
_groupingsPage.Title = isShowingVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace Bit.App.Pages
|
|||||||
CipherType? type = null,
|
CipherType? type = null,
|
||||||
string folderId = null,
|
string folderId = null,
|
||||||
string collectionId = null,
|
string collectionId = null,
|
||||||
|
string organizationId = null,
|
||||||
string name = null,
|
string name = null,
|
||||||
string uri = null,
|
string uri = null,
|
||||||
bool fromAutofill = false,
|
bool fromAutofill = false,
|
||||||
@@ -51,6 +52,7 @@ namespace Bit.App.Pages
|
|||||||
_vm.CipherId = cipherId;
|
_vm.CipherId = cipherId;
|
||||||
_vm.FolderId = folderId == "none" ? null : folderId;
|
_vm.FolderId = folderId == "none" ? null : folderId;
|
||||||
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
|
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
|
||||||
|
_vm.OrganizationId = organizationId;
|
||||||
_vm.Type = type;
|
_vm.Type = type;
|
||||||
_vm.DefaultName = name ?? appOptions?.SaveName;
|
_vm.DefaultName = name ?? appOptions?.SaveName;
|
||||||
_vm.DefaultUri = uri ?? appOptions?.Uri;
|
_vm.DefaultUri = uri ?? appOptions?.Uri;
|
||||||
|
|||||||
@@ -249,7 +249,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public bool ShowCardNumber
|
public bool ShowCardNumber
|
||||||
@@ -298,6 +299,8 @@ namespace Bit.App.Pages
|
|||||||
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
||||||
public bool AllowPersonal { get; set; }
|
public bool AllowPersonal { get; set; }
|
||||||
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,7 +85,8 @@
|
|||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform" />
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Autofill Ciphers Page" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
Text=""
|
Text=""
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Clicked="BackButton_Clicked"
|
Clicked="BackButton_Clicked"
|
||||||
x:Name="_backButton" />
|
x:Name="_backButton"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n TapToGoBack}"/>
|
||||||
<controls:ExtendedSearchBar
|
<controls:ExtendedSearchBar
|
||||||
x:Name="_searchBar"
|
x:Name="_searchBar"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
@@ -47,6 +49,31 @@
|
|||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
<StackLayout x:Name="_mainLayout" Spacing="0" Padding="0">
|
<StackLayout x:Name="_mainLayout" Spacing="0" Padding="0">
|
||||||
|
<StackLayout
|
||||||
|
IsVisible="{Binding ShowVaultFilter}"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalOptions="FillAndExpand"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{Binding VaultFilterDescription}"
|
||||||
|
LineBreakMode="TailTruncation"
|
||||||
|
Margin="10,0"
|
||||||
|
StyleClass="text-md, text-muted"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Filter}" />
|
||||||
|
<controls:MiButton
|
||||||
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
|
StyleClass="list-row-button-text, list-row-button-platform"
|
||||||
|
Command="{Binding VaultFilterCommand}"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Filter}" />
|
||||||
|
</StackLayout>
|
||||||
|
<BoxView StyleClass="box-row-separator" />
|
||||||
|
|
||||||
<controls:IconLabel IsVisible="{Binding ShowSearchDirection}"
|
<controls:IconLabel IsVisible="{Binding ShowSearchDirection}"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Search}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Search}}"
|
||||||
StyleClass="text-muted"
|
StyleClass="text-muted"
|
||||||
@@ -66,7 +93,8 @@
|
|||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Ciphers Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:CipherView">
|
<DataTemplate x:DataType="views:CipherView">
|
||||||
<controls:CipherViewCell
|
<controls:CipherViewCell
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ namespace Bit.App.Pages
|
|||||||
private CiphersPageViewModel _vm;
|
private CiphersPageViewModel _vm;
|
||||||
private bool _hasFocused;
|
private bool _hasFocused;
|
||||||
|
|
||||||
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string autofillUrl = null, bool deleted = false)
|
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string vaultFilterSelection = null,
|
||||||
|
string autofillUrl = null, bool deleted = false)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_vm = BindingContext as CiphersPageViewModel;
|
_vm = BindingContext as CiphersPageViewModel;
|
||||||
@@ -33,6 +34,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_vm.PageTitle = AppResources.SearchVault;
|
_vm.PageTitle = AppResources.SearchVault;
|
||||||
}
|
}
|
||||||
|
_vm.VaultFilterDescription = vaultFilterSelection;
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -23,11 +26,17 @@ namespace Bit.App.Pages
|
|||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IPolicyService _policyService;
|
||||||
private CancellationTokenSource _searchCancellationTokenSource;
|
private CancellationTokenSource _searchCancellationTokenSource;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private bool _showVaultFilter;
|
||||||
|
private string _vaultFilterSelection;
|
||||||
private bool _showNoData;
|
private bool _showNoData;
|
||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
|
private List<Organization> _organizations;
|
||||||
|
|
||||||
public CiphersPageViewModel()
|
public CiphersPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -37,12 +46,19 @@ namespace Bit.App.Pages
|
|||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||||
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => _logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command CipherOptionsCommand { get; set; }
|
public Command CipherOptionsCommand { get; set; }
|
||||||
|
public ICommand VaultFilterCommand { get; }
|
||||||
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
||||||
public Func<CipherView, bool> Filter { get; set; }
|
public Func<CipherView, bool> Filter { get; set; }
|
||||||
public string AutofillUrl { get; set; }
|
public string AutofillUrl { get; set; }
|
||||||
@@ -65,6 +81,23 @@ namespace Bit.App.Pages
|
|||||||
nameof(ShowSearchDirection)
|
nameof(ShowSearchDirection)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public bool ShowVaultFilter
|
||||||
|
{
|
||||||
|
get => _showVaultFilter;
|
||||||
|
set => SetProperty(ref _showVaultFilter, value);
|
||||||
|
}
|
||||||
|
public string VaultFilterDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||||
|
}
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _vaultFilterSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
||||||
|
|
||||||
@@ -76,11 +109,14 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
_organizations = await _organizationService.GetAllAsync();
|
||||||
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
|
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
||||||
|
if (ShowVaultFilter && _vaultFilterSelection == null)
|
||||||
{
|
{
|
||||||
Search((Page as CiphersPage).SearchBar.Text, 200);
|
_vaultFilterSelection = AppResources.AllVaults;
|
||||||
}
|
}
|
||||||
|
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
|
PerformSearchIfPopulated();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Search(string searchText, int? timeout = null)
|
public void Search(string searchText, int? timeout = null)
|
||||||
@@ -107,8 +143,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var vaultFilteredCiphers = await GetAllCiphersAsync();
|
||||||
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
ciphers = await _searchService.SearchCiphersAsync(searchText,
|
||||||
Filter ?? (c => c.IsDeleted == Deleted), null, cts.Token);
|
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
|
||||||
cts.Token.ThrowIfCancellationRequested();
|
cts.Token.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -192,6 +229,58 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PerformSearchIfPopulated()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
|
||||||
|
{
|
||||||
|
Search((Page as CiphersPage).SearchBar.Text, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task VaultFilterOptionsAsync()
|
||||||
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||||
|
if (_organizations.Any())
|
||||||
|
{
|
||||||
|
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
||||||
|
}
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||||
|
options.ToArray());
|
||||||
|
if (selection == null || selection == AppResources.Cancel ||
|
||||||
|
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||||
|
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultFilterDescription = selection;
|
||||||
|
PerformSearchIfPopulated();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<CipherView>> GetAllCiphersAsync()
|
||||||
|
{
|
||||||
|
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
return decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||||
|
}
|
||||||
|
if (IsVaultFilterOrgVault)
|
||||||
|
{
|
||||||
|
var orgId = GetVaultFilterOrgId();
|
||||||
|
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
return decCiphers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
|
private string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
private async void CipherOptionsAsync(CipherView cipher)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
xmlns:effects="clr-namespace:Bit.App.Effects"
|
xmlns:effects="clr-namespace:Bit.App.Effects"
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||||
|
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||||
x:DataType="pages:GroupingsPageViewModel"
|
x:DataType="pages:GroupingsPageViewModel"
|
||||||
Title="{Binding PageTitle}"
|
Title="{Binding PageTitle}"
|
||||||
x:Name="_page">
|
x:Name="_page">
|
||||||
@@ -106,6 +107,30 @@
|
|||||||
GroupTemplate="{StaticResource groupTemplate}" />
|
GroupTemplate="{StaticResource groupTemplate}" />
|
||||||
|
|
||||||
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
<StackLayout x:Key="mainLayout" x:Name="_mainLayout">
|
||||||
|
<StackLayout
|
||||||
|
IsVisible="{Binding ShowVaultFilter}"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalOptions="FillAndExpand"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{Binding VaultFilterDescription}"
|
||||||
|
LineBreakMode="TailTruncation"
|
||||||
|
Margin="10,0"
|
||||||
|
StyleClass="text-md, text-muted"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Filter}" />
|
||||||
|
<controls:MiButton
|
||||||
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
|
StyleClass="list-row-button-text, list-row-button-platform"
|
||||||
|
Command="{Binding VaultFilterCommand}"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
|
AutomationProperties.Name="{u:I18n Filter}" />
|
||||||
|
</StackLayout>
|
||||||
|
|
||||||
<StackLayout
|
<StackLayout
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
Padding="20, 0"
|
Padding="20, 0"
|
||||||
@@ -130,7 +155,8 @@
|
|||||||
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform" />
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Groupings Page" />
|
||||||
</RefreshView>
|
</RefreshView>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ namespace Bit.App.Pages
|
|||||||
private PreviousPageInfo _previousPage;
|
private PreviousPageInfo _previousPage;
|
||||||
|
|
||||||
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
|
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
|
||||||
string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null,
|
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
|
||||||
bool deleted = false)
|
PreviousPageInfo previousPage = null, bool deleted = false)
|
||||||
{
|
{
|
||||||
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -52,6 +52,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_vm.PageTitle = pageTitle;
|
_vm.PageTitle = pageTitle;
|
||||||
}
|
}
|
||||||
|
if (vaultFilterSelection != null)
|
||||||
|
{
|
||||||
|
_vm.VaultFilterDescription = vaultFilterSelection;
|
||||||
|
}
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
@@ -259,7 +263,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (!_vm.Deleted && DoOnce())
|
if (!_vm.Deleted && DoOnce())
|
||||||
{
|
{
|
||||||
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId);
|
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId, _vm.GetVaultFilterOrgId());
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
@@ -29,7 +30,10 @@ namespace Bit.App.Pages
|
|||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
private bool _syncRefreshing;
|
private bool _syncRefreshing;
|
||||||
|
private bool _showVaultFilter;
|
||||||
|
private string _vaultFilterSelection;
|
||||||
private string _noDataText;
|
private string _noDataText;
|
||||||
|
private List<Organization> _organizations;
|
||||||
private List<CipherView> _allCiphers;
|
private List<CipherView> _allCiphers;
|
||||||
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
||||||
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
||||||
@@ -46,6 +50,8 @@ namespace Bit.App.Pages
|
|||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public GroupingsPageViewModel()
|
public GroupingsPageViewModel()
|
||||||
@@ -60,10 +66,11 @@ namespace Bit.App.Pages
|
|||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
|
||||||
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
Loading = true;
|
Loading = true;
|
||||||
PageTitle = AppResources.MyVault;
|
|
||||||
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
|
||||||
RefreshCommand = new Command(async () =>
|
RefreshCommand = new Command(async () =>
|
||||||
{
|
{
|
||||||
@@ -71,6 +78,9 @@ namespace Bit.App.Pages
|
|||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
});
|
});
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => _logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
{
|
{
|
||||||
@@ -87,8 +97,9 @@ namespace Bit.App.Pages
|
|||||||
public bool HasCiphers { get; set; }
|
public bool HasCiphers { get; set; }
|
||||||
public bool HasFolders { get; set; }
|
public bool HasFolders { get; set; }
|
||||||
public bool HasCollections { get; set; }
|
public bool HasCollections { get; set; }
|
||||||
public bool ShowNoFolderCiphers => (NoFolderCiphers?.Count ?? int.MaxValue) < NoFolderListSize &&
|
public bool ShowNoFolderCipherGroup => NoFolderCiphers != null
|
||||||
(!Collections?.Any() ?? true);
|
&& NoFolderCiphers.Count < NoFolderListSize
|
||||||
|
&& (Collections is null || !Collections.Any());
|
||||||
public List<CipherView> Ciphers { get; set; }
|
public List<CipherView> Ciphers { get; set; }
|
||||||
public List<CipherView> FavoriteCiphers { get; set; }
|
public List<CipherView> FavoriteCiphers { get; set; }
|
||||||
public List<CipherView> NoFolderCiphers { get; set; }
|
public List<CipherView> NoFolderCiphers { get; set; }
|
||||||
@@ -142,12 +153,30 @@ namespace Bit.App.Pages
|
|||||||
get => _websiteIconsEnabled;
|
get => _websiteIconsEnabled;
|
||||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
}
|
}
|
||||||
|
public bool ShowVaultFilter
|
||||||
|
{
|
||||||
|
get => _showVaultFilter;
|
||||||
|
set => SetProperty(ref _showVaultFilter, value);
|
||||||
|
}
|
||||||
|
public string VaultFilterDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||||
|
}
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _vaultFilterSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
|
|
||||||
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||||
|
public ICommand VaultFilterCommand { get; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
@@ -172,6 +201,17 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_organizations = await _organizationService.GetAllAsync();
|
||||||
|
if (MainPage)
|
||||||
|
{
|
||||||
|
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
||||||
|
if (ShowVaultFilter && _vaultFilterSelection == null)
|
||||||
|
{
|
||||||
|
_vaultFilterSelection = AppResources.AllVaults;
|
||||||
|
}
|
||||||
|
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
|
}
|
||||||
|
|
||||||
_doingLoad = true;
|
_doingLoad = true;
|
||||||
LoadedOnce = true;
|
LoadedOnce = true;
|
||||||
ShowNoData = false;
|
ShowNoData = false;
|
||||||
@@ -185,9 +225,9 @@ namespace Bit.App.Pages
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadDataAsync();
|
await LoadDataAsync();
|
||||||
if (ShowNoFolderCiphers && (NestedFolders?.Any() ?? false))
|
if (ShowNoFolderCipherGroup && (NestedFolders?.Any() ?? false))
|
||||||
{
|
{
|
||||||
// Remove "No Folder" from folder listing
|
// Remove "No Folder" folder from folders group
|
||||||
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
NestedFolders = NestedFolders.GetRange(0, NestedFolders.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +302,7 @@ namespace Bit.App.Pages
|
|||||||
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
|
groupedItems.Add(new GroupingsPageListGroup(ciphersListItems, AppResources.Items,
|
||||||
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
|
ciphersListItems.Count, uppercaseGroupNames, !MainPage && !groupedItems.Any()));
|
||||||
}
|
}
|
||||||
if (ShowNoFolderCiphers)
|
if (ShowNoFolderCipherGroup)
|
||||||
{
|
{
|
||||||
var noFolderCiphersListItems = NoFolderCiphers.Select(
|
var noFolderCiphersListItems = NoFolderCiphers.Select(
|
||||||
c => new GroupingsPageListItem { Cipher = c }).ToList();
|
c => new GroupingsPageListItem { Cipher = c }).ToList();
|
||||||
@@ -294,6 +334,12 @@ namespace Bit.App.Pages
|
|||||||
items.AddRange(itemGroup);
|
items.AddRange(itemGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||||
|
// because of update to XF v5.0.0.2401
|
||||||
|
GroupedItems.Clear();
|
||||||
|
}
|
||||||
GroupedItems.ReplaceRange(items);
|
GroupedItems.ReplaceRange(items);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -316,6 +362,12 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if (groupedItems.Any())
|
if (groupedItems.Any())
|
||||||
{
|
{
|
||||||
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||||
|
// because of update to XF v5.0.0.2401
|
||||||
|
GroupedItems.Clear();
|
||||||
|
}
|
||||||
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||||
GroupedItems.AddRange(items);
|
GroupedItems.AddRange(items);
|
||||||
}
|
}
|
||||||
@@ -342,6 +394,30 @@ namespace Bit.App.Pages
|
|||||||
SyncRefreshing = false;
|
SyncRefreshing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task VaultFilterOptionsAsync()
|
||||||
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||||
|
if (_organizations.Any())
|
||||||
|
{
|
||||||
|
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
||||||
|
}
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||||
|
options.ToArray());
|
||||||
|
if (selection == null || selection == AppResources.Cancel ||
|
||||||
|
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||||
|
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultFilterDescription = selection;
|
||||||
|
await LoadAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SelectCipherAsync(CipherView cipher)
|
public async Task SelectCipherAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
var page = new ViewPage(cipher.Id);
|
var page = new ViewPage(cipher.Id);
|
||||||
@@ -368,25 +444,26 @@ namespace Bit.App.Pages
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var page = new GroupingsPage(false, type, null, null, title);
|
var page = new GroupingsPage(false, type, null, null, title, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectFolderAsync(FolderView folder)
|
public async Task SelectFolderAsync(FolderView folder)
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name);
|
var page = new GroupingsPage(false, null, folder.Id ?? "none", null, folder.Name, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
|
public async Task SelectCollectionAsync(Core.Models.View.CollectionView collection)
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name);
|
var page = new GroupingsPage(false, null, null, collection.Id, collection.Name, _vaultFilterSelection);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SelectTrashAsync()
|
public async Task SelectTrashAsync()
|
||||||
{
|
{
|
||||||
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, null, true);
|
var page = new GroupingsPage(false, null, null, null, AppResources.Trash, _vaultFilterSelection, null,
|
||||||
|
true);
|
||||||
await Page.Navigation.PushAsync(page);
|
await Page.Navigation.PushAsync(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,8 +501,8 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
|
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
|
||||||
NoDataText = AppResources.NoItems;
|
NoDataText = AppResources.NoItems;
|
||||||
_allCiphers = await _cipherService.GetAllDecryptedAsync();
|
|
||||||
HasCiphers = _allCiphers.Any();
|
HasCiphers = _allCiphers.Any();
|
||||||
FavoriteCiphers?.Clear();
|
FavoriteCiphers?.Clear();
|
||||||
NoFolderCiphers?.Clear();
|
NoFolderCiphers?.Clear();
|
||||||
@@ -439,12 +516,11 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
Folders = await _folderService.GetAllDecryptedAsync();
|
await FillFoldersAndCollectionsAsync(orgId);
|
||||||
NestedFolders = await _folderService.GetAllNestedAsync();
|
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
||||||
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
||||||
Collections = await _collectionService.GetAllDecryptedAsync();
|
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
||||||
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
|
HasCollections = NestedCollections?.Any() ?? false;
|
||||||
HasCollections = NestedCollections.Any();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -564,6 +640,58 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
|
||||||
|
{
|
||||||
|
string orgId = null;
|
||||||
|
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
_allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||||
|
}
|
||||||
|
else if (IsVaultFilterOrgVault)
|
||||||
|
{
|
||||||
|
orgId = GetVaultFilterOrgId();
|
||||||
|
_allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_allCiphers = decCiphers;
|
||||||
|
}
|
||||||
|
return orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FillFoldersAndCollectionsAsync(string orgId)
|
||||||
|
{
|
||||||
|
var decFolders = await _folderService.GetAllDecryptedAsync();
|
||||||
|
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
Folders = BuildFolders(decFolders);
|
||||||
|
Collections = null;
|
||||||
|
}
|
||||||
|
else if (IsVaultFilterOrgVault && !string.IsNullOrWhiteSpace(orgId))
|
||||||
|
{
|
||||||
|
Folders = BuildFolders(decFolders);
|
||||||
|
Collections = decCollections?.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Folders = decFolders;
|
||||||
|
Collections = decCollections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
|
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
||||||
|
{
|
||||||
|
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
||||||
|
return folders.Any() ? folders : null;
|
||||||
|
}
|
||||||
|
|
||||||
private async void CipherOptionsAsync(CipherView cipher)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -39,7 +39,8 @@
|
|||||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||||
ItemsSource="{Binding History}"
|
ItemsSource="{Binding History}"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
StyleClass="list, list-platform">
|
StyleClass="list, list-platform"
|
||||||
|
ExtraDataForLogging="Password History Page">
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:PasswordHistoryView">
|
<DataTemplate x:DataType="views:PasswordHistoryView">
|
||||||
<Grid
|
<Grid
|
||||||
|
|||||||
@@ -41,15 +41,17 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
var cipher = await _cipherService.GetAsync(CipherId);
|
var cipher = await _cipherService.GetAsync(CipherId);
|
||||||
var decCipher = await cipher.DecryptAsync();
|
var decCipher = await cipher.DecryptAsync();
|
||||||
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
ShowNoData = History.Count == 0;
|
{
|
||||||
|
History.ResetWithRange(decCipher.PasswordHistory ?? new List<PasswordHistoryView>());
|
||||||
|
ShowNoData = History.Count == 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void CopyAsync(PasswordHistoryView ph)
|
private async void CopyAsync(PasswordHistoryView ph)
|
||||||
{
|
{
|
||||||
await _clipboardService.CopyTextAsync(ph.Password);
|
await _clipboardService.CopyTextAsync(ph.Password);
|
||||||
_platformUtilsService.ShowToast("info", null,
|
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ namespace Bit.App.Pages
|
|||||||
_autofocusCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
|
_autofocusCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
|
||||||
|
|
||||||
var autofocusCts = _autofocusCts;
|
var autofocusCts = _autofocusCts;
|
||||||
|
// this task is needed to be awaited OnDisappearing to avoid some crashes
|
||||||
|
// when changing the value of _zxing.IsScanning
|
||||||
_continuousAutofocusTask = Task.Run(async () =>
|
_continuousAutofocusTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -52,7 +54,7 @@ namespace Bit.App.Pages
|
|||||||
while (!autofocusCts.IsCancellationRequested)
|
while (!autofocusCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromSeconds(2), autofocusCts.Token);
|
await Task.Delay(TimeSpan.FromSeconds(2), autofocusCts.Token);
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
await Device.InvokeOnMainThreadAsync(() =>
|
||||||
{
|
{
|
||||||
if (!autofocusCts.IsCancellationRequested)
|
if (!autofocusCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -73,7 +75,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_autofocusCts?.Cancel();
|
_autofocusCts?.Cancel();
|
||||||
|
|
||||||
await _continuousAutofocusTask;
|
if (_continuousAutofocusTask != null)
|
||||||
|
{
|
||||||
|
await _continuousAutofocusTask;
|
||||||
|
}
|
||||||
_zxing.IsScanning = false;
|
_zxing.IsScanning = false;
|
||||||
|
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
|
|||||||
@@ -144,7 +144,7 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _showPassword, value,
|
set => SetProperty(ref _showPassword, value,
|
||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon),
|
||||||
|
nameof(PasswordVisibilityAccessibilityText)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public bool ShowCardNumber
|
public bool ShowCardNumber
|
||||||
@@ -213,6 +214,8 @@ namespace Bit.App.Pages
|
|||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string TotpCodeFormatted
|
public string TotpCodeFormatted
|
||||||
{
|
{
|
||||||
get => _totpCodeFormatted;
|
get => _totpCodeFormatted;
|
||||||
@@ -661,7 +664,7 @@ namespace Bit.App.Pages
|
|||||||
await _clipboardService.CopyTextAsync(text);
|
await _clipboardService.CopyTextAsync(text);
|
||||||
if (!string.IsNullOrWhiteSpace(name))
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
{
|
{
|
||||||
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
|
_platformUtilsService.ShowToastForCopiedValue(name);
|
||||||
}
|
}
|
||||||
if (id == "LoginPassword")
|
if (id == "LoginPassword")
|
||||||
{
|
{
|
||||||
@@ -754,12 +757,12 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (IsBooleanType)
|
if (IsBooleanType)
|
||||||
{
|
{
|
||||||
return _field.Value == "true" ? "" : "";
|
return _field.Value == "true" ? BitwardenIcons.Square : BitwardenIcons.CheckSquare;
|
||||||
}
|
}
|
||||||
else if (IsLinkedType)
|
else if (IsLinkedType)
|
||||||
{
|
{
|
||||||
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
var i18nKey = _cipher.LinkedFieldI18nKey(Field.LinkedId.GetValueOrDefault());
|
||||||
return " " + _i18nService.T(i18nKey);
|
return BitwardenIcons.Link + _i18nService.T(i18nKey);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
126
src/App/Resources/AppResources.Designer.cs
generated
126
src/App/Resources/AppResources.Designer.cs
generated
@@ -3299,6 +3299,12 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string Text {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Text", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string TypeText {
|
public static string TypeText {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("TypeText", resourceCulture);
|
return ResourceManager.GetString("TypeText", resourceCulture);
|
||||||
@@ -3329,6 +3335,30 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string FileTypeIsSelected {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FileTypeIsSelected", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FileTypeIsNotSelected {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FileTypeIsNotSelected", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string TextTypeIsSelected {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TextTypeIsSelected", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string TextTypeIsNotSelected {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TextTypeIsNotSelected", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string DeletionDate {
|
public static string DeletionDate {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("DeletionDate", resourceCulture);
|
return ResourceManager.GetString("DeletionDate", resourceCulture);
|
||||||
@@ -3892,5 +3922,101 @@ namespace Bit.App.Resources {
|
|||||||
return ResourceManager.GetString("EnterTheVerificationCodeThatWasSentToYourEmail", resourceCulture);
|
return ResourceManager.GetString("EnterTheVerificationCodeThatWasSentToYourEmail", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string SubmitCrashLogs {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SubmitCrashLogs", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SubmitCrashLogsDescription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SubmitCrashLogsDescription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string OptionsExpanded {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OptionsExpanded", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string OptionsCollapsed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OptionsCollapsed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string UppercaseAtoZ {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("UppercaseAtoZ", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LowercaseAtoZ {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LowercaseAtoZ", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string NumbersZeroToNine {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NumbersZeroToNine", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SpecialCharacters {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SpecialCharacters", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string TapToGoBack {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TapToGoBack", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string VisibilityTogglePasswordIsVisibleActivateToHide {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("VisibilityTogglePasswordIsVisibleActivateToHide", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string VisibilityTogglePasswordIsNotVisibleActivateToHide {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("VisibilityTogglePasswordIsNotVisibleActivateToHide", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FilterByVault {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FilterByVault", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AllVaults {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AllVaults", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Vaults {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Vaults", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string VaultFilterDescription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("VaultFilterDescription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string All {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("All", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1861,6 +1861,9 @@
|
|||||||
<value>A friendly name to describe this Send.</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>
|
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Text" xml:space="preserve">
|
||||||
|
<value>Text</value>
|
||||||
|
</data>
|
||||||
<data name="TypeText" xml:space="preserve">
|
<data name="TypeText" xml:space="preserve">
|
||||||
<value>Text</value>
|
<value>Text</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1877,6 +1880,18 @@
|
|||||||
<data name="TypeFileInfo" xml:space="preserve">
|
<data name="TypeFileInfo" xml:space="preserve">
|
||||||
<value>The file you want to send.</value>
|
<value>The file you want to send.</value>
|
||||||
</data>
|
</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">
|
<data name="DeletionDate" xml:space="preserve">
|
||||||
<value>Deletion Date</value>
|
<value>Deletion Date</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2181,4 +2196,52 @@
|
|||||||
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
|
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
|
||||||
<value>Enter the verification code that was sent to your email</value>
|
<value>Enter the verification code that was sent to your email</value>
|
||||||
</data>
|
</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 (!@#$%^&*)</value>
|
||||||
|
</data>
|
||||||
|
<data name="TapToGoBack" xml:space="preserve">
|
||||||
|
<value>Tap to go back</value>
|
||||||
|
</data>
|
||||||
|
<data name="VisibilityTogglePasswordIsVisibleActivateToHide" xml:space="preserve">
|
||||||
|
<value>Visibility Toggle, Password is visible, activate to hide.</value>
|
||||||
|
</data>
|
||||||
|
<data name="VisibilityTogglePasswordIsNotVisibleActivateToHide" xml:space="preserve">
|
||||||
|
<value>Visibility Toggle, Password is not visible, activate to show.</value>
|
||||||
|
</data>
|
||||||
|
<data name="FilterByVault" xml:space="preserve">
|
||||||
|
<value>Filter items by vault</value>
|
||||||
|
</data>
|
||||||
|
<data name="AllVaults" xml:space="preserve">
|
||||||
|
<value>All Vaults</value>
|
||||||
|
</data>
|
||||||
|
<data name="Vaults" xml:space="preserve">
|
||||||
|
<value>Vaults</value>
|
||||||
|
</data>
|
||||||
|
<data name="VaultFilterDescription" xml:space="preserve">
|
||||||
|
<value>Vault: {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="All" xml:space="preserve">
|
||||||
|
<value>All</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace Bit.App.Services
|
|||||||
private const int DialogPromiseExpiration = 600000; // 10 minutes
|
private const int DialogPromiseExpiration = 600000; // 10 minutes
|
||||||
|
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
|
||||||
@@ -28,10 +29,12 @@ namespace Bit.App.Services
|
|||||||
|
|
||||||
public MobilePlatformUtilsService(
|
public MobilePlatformUtilsService(
|
||||||
IDeviceActionService deviceActionService,
|
IDeviceActionService deviceActionService,
|
||||||
|
IClipboardService clipboardService,
|
||||||
IMessagingService messagingService,
|
IMessagingService messagingService,
|
||||||
IBroadcasterService broadcasterService)
|
IBroadcasterService broadcasterService)
|
||||||
{
|
{
|
||||||
_deviceActionService = deviceActionService;
|
_deviceActionService = deviceActionService;
|
||||||
|
_clipboardService = clipboardService;
|
||||||
_messagingService = messagingService;
|
_messagingService = messagingService;
|
||||||
_broadcasterService = broadcasterService;
|
_broadcasterService = broadcasterService;
|
||||||
}
|
}
|
||||||
@@ -129,6 +132,15 @@ namespace Bit.App.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ShowToastForCopiedValue(string valueNameCopied)
|
||||||
|
{
|
||||||
|
if (!_clipboardService.IsCopyNotificationHandledByPlatform())
|
||||||
|
{
|
||||||
|
ShowToast("info", null,
|
||||||
|
string.Format(AppResources.ValueHasBeenCopied, valueNameCopied));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool SupportsFido2()
|
public bool SupportsFido2()
|
||||||
{
|
{
|
||||||
return _deviceActionService.SupportsFido2();
|
return _deviceActionService.SupportsFido2();
|
||||||
|
|||||||
@@ -268,6 +268,16 @@
|
|||||||
<Setter Property="TextColor"
|
<Setter Property="TextColor"
|
||||||
Value="{DynamicResource ButtonColor}" />
|
Value="{DynamicResource ButtonColor}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style TargetType="Button"
|
||||||
|
ApplyToDerivedTypes="True"
|
||||||
|
Class="list-row-button-text">
|
||||||
|
<Setter Property="BackgroundColor"
|
||||||
|
Value="Transparent" />
|
||||||
|
<Setter Property="Padding"
|
||||||
|
Value="0" />
|
||||||
|
<Setter Property="TextColor"
|
||||||
|
Value="{DynamicResource ButtonTextColor}" />
|
||||||
|
</Style>
|
||||||
<Style TargetType="Button"
|
<Style TargetType="Button"
|
||||||
ApplyToDerivedTypes="True"
|
ApplyToDerivedTypes="True"
|
||||||
Class="segmented-button">
|
Class="segmented-button">
|
||||||
|
|||||||
227
src/App/Utilities/AccountManagement/AccountsManager.cs
Normal file
227
src/App/Utilities/AccountManagement/AccountsManager.cs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
|
{
|
||||||
|
public static class AccountsManagerMessageCommands
|
||||||
|
{
|
||||||
|
public const string LOCKED = "locked";
|
||||||
|
public const string LOCK_VAULT = "lockVault";
|
||||||
|
public const string LOGOUT = "logout";
|
||||||
|
public const string LOGGED_OUT = "loggedOut";
|
||||||
|
public const string ADD_ACCOUNT = "addAccount";
|
||||||
|
public const string ACCOUNT_ADDED = "accountAdded";
|
||||||
|
public const string SWITCHED_ACCOUNT = "switchedAccount";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AccountsManager : IAccountsManager
|
||||||
|
{
|
||||||
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
private readonly IStorageService _secureStorageService;
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
|
private readonly IAuthService _authService;
|
||||||
|
|
||||||
|
Func<AppOptions> _getOptionsFunc;
|
||||||
|
private IAccountsManagerHost _accountsManagerHost;
|
||||||
|
|
||||||
|
public AccountsManager(IBroadcasterService broadcasterService,
|
||||||
|
IVaultTimeoutService vaultTimeoutService,
|
||||||
|
IStorageService secureStorageService,
|
||||||
|
IStateService stateService,
|
||||||
|
IPlatformUtilsService platformUtilsService,
|
||||||
|
IAuthService authService)
|
||||||
|
{
|
||||||
|
_broadcasterService = broadcasterService;
|
||||||
|
_vaultTimeoutService = vaultTimeoutService;
|
||||||
|
_secureStorageService = secureStorageService;
|
||||||
|
_stateService = stateService;
|
||||||
|
_platformUtilsService = platformUtilsService;
|
||||||
|
_authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
||||||
|
|
||||||
|
public void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost)
|
||||||
|
{
|
||||||
|
_getOptionsFunc = getOptionsFunc;
|
||||||
|
_accountsManagerHost = accountsManagerHost;
|
||||||
|
|
||||||
|
_broadcasterService.Subscribe(nameof(AccountsManager), OnMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task NavigateOnAccountChangeAsync(bool? isAuthed = null)
|
||||||
|
{
|
||||||
|
// TODO: this could be improved by doing chain of responsability pattern
|
||||||
|
// but for now it may be an overkill, if logic gets more complex consider refactoring it
|
||||||
|
|
||||||
|
var authed = isAuthed ?? await _stateService.IsAuthenticatedAsync();
|
||||||
|
if (authed)
|
||||||
|
{
|
||||||
|
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
|
||||||
|
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
||||||
|
{
|
||||||
|
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
||||||
|
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
||||||
|
|
||||||
|
var email = await _stateService.GetEmailAsync();
|
||||||
|
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
|
||||||
|
}
|
||||||
|
else if (await _vaultTimeoutService.IsLockedAsync() ||
|
||||||
|
await _vaultTimeoutService.ShouldLockAsync())
|
||||||
|
{
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.Lock);
|
||||||
|
}
|
||||||
|
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
|
||||||
|
{
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.AddEditCipher);
|
||||||
|
}
|
||||||
|
else if (Options.Uri != null)
|
||||||
|
{
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.AutofillCiphers);
|
||||||
|
}
|
||||||
|
else if (Options.CreateSend != null)
|
||||||
|
{
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.SendAddEdit);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.Home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
|
||||||
|
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
|
||||||
|
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
|
||||||
|
{
|
||||||
|
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
|
||||||
|
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
||||||
|
|
||||||
|
var email = await _stateService.GetEmailAsync();
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnMessage(Message message)
|
||||||
|
{
|
||||||
|
switch (message.Command)
|
||||||
|
{
|
||||||
|
case AccountsManagerMessageCommands.LOCKED:
|
||||||
|
Locked(message.Data as Tuple<string, bool>);
|
||||||
|
break;
|
||||||
|
case AccountsManagerMessageCommands.LOCK_VAULT:
|
||||||
|
await _vaultTimeoutService.LockAsync(true);
|
||||||
|
break;
|
||||||
|
case AccountsManagerMessageCommands.LOGOUT:
|
||||||
|
LogOut(message.Data as Tuple<string, bool, bool>);
|
||||||
|
break;
|
||||||
|
case AccountsManagerMessageCommands.LOGGED_OUT:
|
||||||
|
// Clean up old migrated key if they ever log out.
|
||||||
|
await _secureStorageService.RemoveAsync("oldKey");
|
||||||
|
break;
|
||||||
|
case AccountsManagerMessageCommands.ADD_ACCOUNT:
|
||||||
|
AddAccount();
|
||||||
|
break;
|
||||||
|
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
|
||||||
|
await _accountsManagerHost.UpdateThemeAsync();
|
||||||
|
break;
|
||||||
|
case AccountsManagerMessageCommands.SWITCHED_ACCOUNT:
|
||||||
|
await SwitchedAccountAsync();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Locked(Tuple<string, bool> extras)
|
||||||
|
{
|
||||||
|
var userId = extras?.Item1;
|
||||||
|
var userInitiated = extras?.Item2 ?? false;
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LockedAsync(string userId, bool userInitiated)
|
||||||
|
{
|
||||||
|
if (!await _stateService.IsActiveAccountAsync(userId))
|
||||||
|
{
|
||||||
|
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var autoPromptBiometric = !userInitiated;
|
||||||
|
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
|
||||||
|
if (vaultTimeout == 0)
|
||||||
|
{
|
||||||
|
autoPromptBiometric = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _accountsManagerHost.SetPreviousPageInfoAsync();
|
||||||
|
|
||||||
|
Device.BeginInvokeOnMainThread(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddAccount()
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
Options.HideAccountSwitcher = false;
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogOut(Tuple<string, bool, bool> extras)
|
||||||
|
{
|
||||||
|
var userId = extras?.Item1;
|
||||||
|
var userInitiated = extras?.Item2 ?? true;
|
||||||
|
var expired = extras?.Item3 ?? false;
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
|
||||||
|
{
|
||||||
|
await AppHelpers.LogOutAsync(userId, userInitiated);
|
||||||
|
await NavigateOnAccountChangeAsync();
|
||||||
|
_authService.LogOut(() =>
|
||||||
|
{
|
||||||
|
if (expired)
|
||||||
|
{
|
||||||
|
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SwitchedAccountAsync()
|
||||||
|
{
|
||||||
|
await AppHelpers.OnAccountSwitchAsync();
|
||||||
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
||||||
|
{
|
||||||
|
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await NavigateOnAccountChangeAsync();
|
||||||
|
}
|
||||||
|
await Task.Delay(50);
|
||||||
|
await _accountsManagerHost.UpdateThemeAsync();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/App/Utilities/AccountManagement/LockNavigationParams.cs
Normal file
14
src/App/Utilities/AccountManagement/LockNavigationParams.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Bit.App.Abstractions;
|
||||||
|
|
||||||
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
|
{
|
||||||
|
public class LockNavigationParams : INavigationParams
|
||||||
|
{
|
||||||
|
public LockNavigationParams(bool autoPromptBiometric = true)
|
||||||
|
{
|
||||||
|
AutoPromptBiometric = autoPromptBiometric;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AutoPromptBiometric { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/App/Utilities/AccountManagement/LoginNavigationParams.cs
Normal file
14
src/App/Utilities/AccountManagement/LoginNavigationParams.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Bit.App.Abstractions;
|
||||||
|
|
||||||
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
|
{
|
||||||
|
public class LoginNavigationParams : INavigationParams
|
||||||
|
{
|
||||||
|
public LoginNavigationParams(string email)
|
||||||
|
{
|
||||||
|
Email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Email { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,16 +97,14 @@ namespace Bit.App.Utilities
|
|||||||
else if (selection == AppResources.CopyUsername)
|
else if (selection == AppResources.CopyUsername)
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Login.Username);
|
await clipboardService.CopyTextAsync(cipher.Login.Username);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.Username);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Username));
|
|
||||||
}
|
}
|
||||||
else if (selection == AppResources.CopyPassword)
|
else if (selection == AppResources.CopyPassword)
|
||||||
{
|
{
|
||||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Login.Password);
|
await clipboardService.CopyTextAsync(cipher.Login.Password);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
|
|
||||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, cipher.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,8 +117,7 @@ namespace Bit.App.Utilities
|
|||||||
if (!string.IsNullOrWhiteSpace(totp))
|
if (!string.IsNullOrWhiteSpace(totp))
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(totp);
|
await clipboardService.CopyTextAsync(totp);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.VerificationCodeTotp));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,8 +130,7 @@ namespace Bit.App.Utilities
|
|||||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Card.Number);
|
await clipboardService.CopyTextAsync(cipher.Card.Number);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.Number);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Number));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (selection == AppResources.CopySecurityCode)
|
else if (selection == AppResources.CopySecurityCode)
|
||||||
@@ -142,16 +138,14 @@ namespace Bit.App.Utilities
|
|||||||
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
if (cipher.Reprompt == CipherRepromptType.None || await passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Card.Code);
|
await clipboardService.CopyTextAsync(cipher.Card.Code);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.SecurityCode);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.SecurityCode));
|
|
||||||
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
var task = eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, cipher.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (selection == AppResources.CopyNotes)
|
else if (selection == AppResources.CopyNotes)
|
||||||
{
|
{
|
||||||
await clipboardService.CopyTextAsync(cipher.Notes);
|
await clipboardService.CopyTextAsync(cipher.Notes);
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.Notes);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.Notes));
|
|
||||||
}
|
}
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
@@ -262,8 +256,7 @@ namespace Bit.App.Utilities
|
|||||||
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
var platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
var clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
await clipboardService.CopyTextAsync(GetSendUrl(send));
|
await clipboardService.CopyTextAsync(GetSendUrl(send));
|
||||||
platformUtilsService.ShowToast("info", null,
|
platformUtilsService.ShowToastForCopiedValue(AppResources.SendLink);
|
||||||
string.Format(AppResources.ValueHasBeenCopied, AppResources.SendLink));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task ShareSendUrlAsync(SendView send)
|
public static async Task ShareSendUrlAsync(SendView send)
|
||||||
|
|||||||
@@ -8,9 +8,17 @@ namespace Bit.Core.Abstractions
|
|||||||
/// Copies the <paramref name="text"/> to the Clipboard.
|
/// Copies the <paramref name="text"/> to the Clipboard.
|
||||||
/// If <paramref name="expiresInMs"/> is set > 0 then the Clipboard will be cleared after this time in milliseconds.
|
/// If <paramref name="expiresInMs"/> is set > 0 then the Clipboard will be cleared after this time in milliseconds.
|
||||||
/// if less than 0 then it takes the configuration that the user set in Options.
|
/// if less than 0 then it takes the configuration that the user set in Options.
|
||||||
|
/// If <paramref name="isSensitive"/> is true the sensitive flag is passed to the clipdata to obfuscate the
|
||||||
|
/// clipboard text in the popup (Android 13+ only)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text">Text to be copied to the Clipboard</param>
|
/// <param name="text">Text to be copied to the Clipboard</param>
|
||||||
/// <param name="expiresInMs">Expiration time in milliseconds of the copied text</param>
|
/// <param name="expiresInMs">Expiration time in milliseconds of the copied text</param>
|
||||||
Task CopyTextAsync(string text, int expiresInMs = -1);
|
/// <param name="isSensitive">Flag to mark copied text as sensitive</param>
|
||||||
|
Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the platform provides its own notification when text is copied to the clipboard
|
||||||
|
/// </summary>
|
||||||
|
bool IsCopyNotificationHandledByPlatform();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null);
|
Task<Folder> EncryptAsync(FolderView model, SymmetricCryptoKey key = null);
|
||||||
Task<List<Folder>> GetAllAsync();
|
Task<List<Folder>> GetAllAsync();
|
||||||
Task<List<FolderView>> GetAllDecryptedAsync();
|
Task<List<FolderView>> GetAllDecryptedAsync();
|
||||||
Task<List<TreeNode<FolderView>>> GetAllNestedAsync();
|
Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null);
|
||||||
Task<Folder> GetAsync(string id);
|
Task<Folder> GetAsync(string id);
|
||||||
Task<TreeNode<FolderView>> GetNestedAsync(string id);
|
Task<TreeNode<FolderView>> GetNestedAsync(string id);
|
||||||
Task ReplaceAsync(Dictionary<string, FolderData> folders);
|
Task ReplaceAsync(Dictionary<string, FolderData> folders);
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
public interface ILogger
|
public interface ILogger
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Place necessary code to initiaze logger
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task InitAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns if the current logger is enable or disable.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<bool> IsEnabled();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the state of the current logger. Setting state enabled to false will block logging.
|
||||||
|
/// </summary>
|
||||||
|
Task SetEnabled(bool value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logs something that is not in itself an exception, e.g. a wrong flow or value that needs to be reported
|
/// Logs something that is not in itself an exception, e.g. a wrong flow or value that needs to be reported
|
||||||
/// and looked into.
|
/// and looked into.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<(string password, bool valid)> ShowPasswordDialogAndGetItAsync(string title, string body, Func<string, Task<bool>> validator);
|
Task<(string password, bool valid)> ShowPasswordDialogAndGetItAsync(string title, string body, Func<string, Task<bool>> validator);
|
||||||
void ShowToast(string type, string title, string text, Dictionary<string, object> options = null);
|
void ShowToast(string type, string title, string text, Dictionary<string, object> options = null);
|
||||||
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
|
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
|
||||||
|
void ShowToastForCopiedValue(string valueNameCopied);
|
||||||
bool SupportsFido2();
|
bool SupportsFido2();
|
||||||
bool SupportsDuo();
|
bool SupportsDuo();
|
||||||
Task<bool> SupportsBiometricAsync();
|
Task<bool> SupportsBiometricAsync();
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ namespace Bit.Core.Abstractions
|
|||||||
string orgId);
|
string orgId);
|
||||||
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
||||||
int? GetPolicyInt(Policy policy, string key);
|
int? GetPolicyInt(Policy policy, string key);
|
||||||
|
Task<bool> ShouldShowVaultFilterAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,5 +111,6 @@
|
|||||||
public const string EyeSlash = "\xe96d";
|
public const string EyeSlash = "\xe96d";
|
||||||
public const string File = "\xe96e";
|
public const string File = "\xe96e";
|
||||||
public const string Paste = "\xe96f";
|
public const string Paste = "\xe96f";
|
||||||
|
public const string ViewCellMenu = "\xe5d3";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/Core/Enums/NavigationTarget.cs
Normal file
13
src/Core/Enums/NavigationTarget.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum NavigationTarget
|
||||||
|
{
|
||||||
|
HomeLogin,
|
||||||
|
Login,
|
||||||
|
Lock,
|
||||||
|
Home,
|
||||||
|
AddEditCipher,
|
||||||
|
AutofillCiphers,
|
||||||
|
SendAddEdit
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
if (defaultOptions)
|
if (defaultOptions)
|
||||||
{
|
{
|
||||||
Length = 14;
|
Length = 14;
|
||||||
Ambiguous = false;
|
AllowAmbiguousChar = true;
|
||||||
Number = true;
|
Number = true;
|
||||||
MinNumber = 1;
|
MinNumber = 1;
|
||||||
Uppercase = true;
|
Uppercase = true;
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int? Length { get; set; }
|
public int? Length { get; set; }
|
||||||
public bool? Ambiguous { get; set; }
|
public bool? AllowAmbiguousChar { get; set; }
|
||||||
public bool? Number { get; set; }
|
public bool? Number { get; set; }
|
||||||
public int? MinNumber { get; set; }
|
public int? MinNumber { get; set; }
|
||||||
public bool? Uppercase { get; set; }
|
public bool? Uppercase { get; set; }
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
public void Merge(PasswordGenerationOptions defaults)
|
public void Merge(PasswordGenerationOptions defaults)
|
||||||
{
|
{
|
||||||
Length = Length ?? defaults.Length;
|
Length = Length ?? defaults.Length;
|
||||||
Ambiguous = Ambiguous ?? defaults.Ambiguous;
|
AllowAmbiguousChar = AllowAmbiguousChar ?? defaults.AllowAmbiguousChar;
|
||||||
Number = Number ?? defaults.Number;
|
Number = Number ?? defaults.Number;
|
||||||
MinNumber = MinNumber ?? defaults.MinNumber;
|
MinNumber = MinNumber ?? defaults.MinNumber;
|
||||||
Uppercase = Uppercase ?? defaults.Uppercase;
|
Uppercase = Uppercase ?? defaults.Uppercase;
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
{
|
{
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string MasterPasswordHash { get; set; }
|
public string MasterPasswordHash { get; set; }
|
||||||
|
public string DeviceIdentifier { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,6 +346,7 @@ namespace Bit.Core.Services
|
|||||||
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
|
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
|
||||||
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
|
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
|
||||||
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
|
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
|
||||||
|
await _tokenService.ClearTwoFactorTokenAsync(email);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,9 +107,12 @@ namespace Bit.Core.Services
|
|||||||
return _decryptedFolderCache;
|
return _decryptedFolderCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync()
|
public async Task<List<TreeNode<FolderView>>> GetAllNestedAsync(List<FolderView> folders = null)
|
||||||
{
|
{
|
||||||
var folders = await GetAllDecryptedAsync();
|
if (folders == null)
|
||||||
|
{
|
||||||
|
folders = await GetAllDecryptedAsync();
|
||||||
|
}
|
||||||
var nodes = new List<TreeNode<FolderView>>();
|
var nodes = new List<TreeNode<FolderView>>();
|
||||||
foreach (var f in folders)
|
foreach (var f in folders)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
@@ -45,6 +46,12 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Exception(Exception ex) => Debug.WriteLine(ex);
|
public void Exception(Exception ex) => Debug.WriteLine(ex);
|
||||||
|
|
||||||
|
public Task InitAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task<bool> IsEnabled() => Task.FromResult(true);
|
||||||
|
|
||||||
|
public Task SetEnabled(bool value) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,13 +5,24 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Microsoft.AppCenter;
|
||||||
using Microsoft.AppCenter.Crashes;
|
using Microsoft.AppCenter.Crashes;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public class Logger : ILogger
|
public class Logger : ILogger
|
||||||
{
|
{
|
||||||
|
private const string iOSAppSecret = "51f96ae5-68ba-45f6-99a1-8ad9f63046c3";
|
||||||
|
private const string DroidAppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
|
||||||
|
|
||||||
|
private string _userId;
|
||||||
|
private string _appId;
|
||||||
|
private bool _isInitialised = false;
|
||||||
|
|
||||||
static ILogger _instance;
|
static ILogger _instance;
|
||||||
public static ILogger Instance
|
public static ILogger Instance
|
||||||
{
|
{
|
||||||
@@ -29,6 +40,60 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(new
|
||||||
|
{
|
||||||
|
AppId = _appId,
|
||||||
|
UserId = _userId
|
||||||
|
}, Formatting.Indented);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
if (_isInitialised)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var device = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService").GetDevice();
|
||||||
|
_userId = await ServiceContainer.Resolve<IStateService>("stateService").GetActiveUserIdAsync();
|
||||||
|
_appId = await ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync();
|
||||||
|
|
||||||
|
switch (device)
|
||||||
|
{
|
||||||
|
case Enums.DeviceType.Android:
|
||||||
|
AppCenter.Start(DroidAppSecret, typeof(Crashes));
|
||||||
|
break;
|
||||||
|
case Enums.DeviceType.iOS:
|
||||||
|
AppCenter.Start(iOSAppSecret, typeof(Crashes));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AppCenterException("Cannot start AppCenter. Device type is not configured.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AppCenter.SetUserId(_userId);
|
||||||
|
|
||||||
|
Crashes.GetErrorAttachments = (ErrorReport report) =>
|
||||||
|
{
|
||||||
|
return new ErrorAttachmentLog[]
|
||||||
|
{
|
||||||
|
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_isInitialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsEnabled() => await AppCenter.IsEnabledAsync();
|
||||||
|
|
||||||
|
public async Task SetEnabled(bool value) => await AppCenter.SetEnabledAsync(value);
|
||||||
|
|
||||||
public void Error(string message,
|
public void Error(string message,
|
||||||
IDictionary<string, string> extraData = null,
|
IDictionary<string, string> extraData = null,
|
||||||
[CallerMemberName] string memberName = "",
|
[CallerMemberName] string memberName = "",
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
#if !FDROID
|
|
||||||
using Microsoft.AppCenter.Crashes;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@@ -25,8 +22,9 @@ namespace Bit.Core.Services
|
|||||||
#if !FDROID
|
#if !FDROID
|
||||||
// just in case the caller throws the exception in a moment where the logger can't be resolved
|
// just in case the caller throws the exception in a moment where the logger can't be resolved
|
||||||
// we need to track the error as well
|
// we need to track the error as well
|
||||||
Crashes.TrackError(ex);
|
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
@@ -17,5 +18,11 @@ namespace Bit.Core.Services
|
|||||||
public void Exception(Exception ex)
|
public void Exception(Exception ex)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task InitAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public Task<bool> IsEnabled() => Task.FromResult(false);
|
||||||
|
|
||||||
|
public Task SetEnabled(bool value) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace Bit.Core.Services
|
|||||||
// Build out other character sets
|
// Build out other character sets
|
||||||
var allCharSet = string.Empty;
|
var allCharSet = string.Empty;
|
||||||
var lowercaseCharSet = LowercaseCharSet;
|
var lowercaseCharSet = LowercaseCharSet;
|
||||||
if (options.Ambiguous.GetValueOrDefault())
|
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||||
{
|
{
|
||||||
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
|
lowercaseCharSet = string.Concat(lowercaseCharSet, "l");
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var uppercaseCharSet = UppercaseCharSet;
|
var uppercaseCharSet = UppercaseCharSet;
|
||||||
if (options.Ambiguous.GetValueOrDefault())
|
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||||
{
|
{
|
||||||
uppercaseCharSet = string.Concat(uppercaseCharSet, "IO");
|
uppercaseCharSet = string.Concat(uppercaseCharSet, "IO");
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var numberCharSet = NumberCharSet;
|
var numberCharSet = NumberCharSet;
|
||||||
if (options.Ambiguous.GetValueOrDefault())
|
if (options.AllowAmbiguousChar.GetValueOrDefault())
|
||||||
{
|
{
|
||||||
numberCharSet = string.Concat(numberCharSet, "01");
|
numberCharSet = string.Concat(numberCharSet, "01");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ namespace Bit.Core.Services
|
|||||||
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter, string userId = null)
|
public async Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null,
|
||||||
|
string userId = null)
|
||||||
{
|
{
|
||||||
var policies = await GetAll(policyType, userId);
|
var policies = await GetAll(policyType, userId);
|
||||||
if (policies == null)
|
if (policies == null)
|
||||||
@@ -246,6 +247,12 @@ namespace Bit.Core.Services
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||||
|
{
|
||||||
|
var organizations = await _organizationService.GetAllAsync();
|
||||||
|
return organizations?.Any() ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
private bool? GetPolicyBool(Policy policy, string key)
|
private bool? GetPolicyBool(Policy policy, string key)
|
||||||
{
|
{
|
||||||
if (policy.Data.ContainsKey(key))
|
if (policy.Data.ContainsKey(key))
|
||||||
|
|||||||
@@ -74,28 +74,34 @@ namespace Bit.Core.Services
|
|||||||
CancellationToken ct = default, bool deleted = false)
|
CancellationToken ct = default, bool deleted = false)
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
var matchedCiphers = new List<CipherView>();
|
||||||
|
var lowPriorityMatchedCiphers = new List<CipherView>();
|
||||||
query = query.Trim().ToLower();
|
query = query.Trim().ToLower();
|
||||||
return ciphers.Where(c =>
|
|
||||||
|
foreach (var c in ciphers)
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
if (c.Name?.ToLower().Contains(query) ?? false)
|
if (c.Name?.ToLower().Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
matchedCiphers.Add(c);
|
||||||
}
|
}
|
||||||
if (query.Length >= 8 && c.Id.StartsWith(query))
|
else if (query.Length >= 8 && c.Id.StartsWith(query))
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchedCiphers.Add(c);
|
||||||
}
|
}
|
||||||
if (c.SubTitle?.ToLower().Contains(query) ?? false)
|
else if (c.SubTitle?.ToLower().Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchedCiphers.Add(c);
|
||||||
}
|
}
|
||||||
if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
|
else if (c.Login?.Uri?.ToLower()?.Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchedCiphers.Add(c);
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}).ToList();
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
matchedCiphers.AddRange(lowPriorityMatchedCiphers);
|
||||||
|
return matchedCiphers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<SendView>> SearchSendsAsync(string query, Func<SendView, bool> filter = null,
|
public async Task<List<SendView>> SearchSendsAsync(string query, Func<SendView, bool> filter = null,
|
||||||
@@ -133,25 +139,31 @@ namespace Bit.Core.Services
|
|||||||
public List<SendView> SearchSendsBasic(List<SendView> sends, string query, CancellationToken ct = default,
|
public List<SendView> SearchSendsBasic(List<SendView> sends, string query, CancellationToken ct = default,
|
||||||
bool deleted = false)
|
bool deleted = false)
|
||||||
{
|
{
|
||||||
|
var matchedSends = new List<SendView>();
|
||||||
|
var lowPriorityMatchSends = new List<SendView>();
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
query = query.Trim().ToLower();
|
query = query.Trim().ToLower();
|
||||||
return sends.Where(s =>
|
|
||||||
|
foreach (var s in sends)
|
||||||
{
|
{
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
if (s.Name?.ToLower().Contains(query) ?? false)
|
if (s.Name?.ToLower().Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
matchedSends.Add(s);
|
||||||
}
|
}
|
||||||
if (s.Text?.Text?.ToLower().Contains(query) ?? false)
|
else if (s.Text?.Text?.ToLower().Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchSends.Add(s);
|
||||||
}
|
}
|
||||||
if (s.File?.FileName?.ToLower()?.Contains(query) ?? false)
|
else if (s.File?.FileName?.ToLower()?.Contains(query) ?? false)
|
||||||
{
|
{
|
||||||
return true;
|
lowPriorityMatchSends.Add(s);
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}).ToList();
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
matchedSends.AddRange(lowPriorityMatchSends);
|
||||||
|
return matchedSends;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1372,10 +1372,6 @@ namespace Bit.Core.Services
|
|||||||
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
||||||
await SetEncryptedSendsAsync(null, userId);
|
await SetEncryptedSendsAsync(null, userId);
|
||||||
await SetSettingsAsync(null, userId);
|
await SetSettingsAsync(null, userId);
|
||||||
if (!string.IsNullOrWhiteSpace(email))
|
|
||||||
{
|
|
||||||
await SetTwoFactorTokenAsync(null, email);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userInitiated)
|
if (userInitiated)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,28 +1,32 @@
|
|||||||
using AuthenticationServices;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AuthenticationServices;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.iOS.Autofill.Models;
|
using Bit.iOS.Autofill.Models;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using Foundation;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.App.Pages;
|
|
||||||
using UIKit;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.App.Models;
|
|
||||||
using Bit.iOS.Core.Views;
|
using Bit.iOS.Core.Views;
|
||||||
using CoreNFC;
|
using CoreNFC;
|
||||||
|
using Foundation;
|
||||||
|
using UIKit;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill
|
namespace Bit.iOS.Autofill
|
||||||
{
|
{
|
||||||
public partial class CredentialProviderViewController : ASCredentialProviderViewController
|
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost
|
||||||
{
|
{
|
||||||
private Context _context;
|
private Context _context;
|
||||||
private bool _initedAppCenter;
|
|
||||||
private NFCNdefReaderSession _nfcSession = null;
|
private NFCNdefReaderSession _nfcSession = null;
|
||||||
private Core.NFCReaderDelegate _nfcDelegate = null;
|
private Core.NFCReaderDelegate _nfcDelegate = null;
|
||||||
|
private IAccountsManager _accountsManager;
|
||||||
|
|
||||||
|
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>("stateService");
|
||||||
|
|
||||||
public CredentialProviderViewController(IntPtr handle)
|
public CredentialProviderViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
@@ -57,7 +61,7 @@ namespace Bit.iOS.Autofill
|
|||||||
}
|
}
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchHomePage();
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
}
|
}
|
||||||
else if (await IsLocked())
|
else if (await IsLocked())
|
||||||
{
|
{
|
||||||
@@ -79,9 +83,8 @@ namespace Bit.iOS.Autofill
|
|||||||
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
InitAppIfNeeded();
|
||||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
|
||||||
await stateService.SetPasswordRepromptAutofillAsync(false);
|
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
|
||||||
await stateService.SetPasswordVerifiedAutofillAsync(false);
|
|
||||||
if (!await IsAuthed() || await IsLocked())
|
if (!await IsAuthed() || await IsLocked())
|
||||||
{
|
{
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
@@ -98,7 +101,7 @@ namespace Bit.iOS.Autofill
|
|||||||
InitAppIfNeeded();
|
InitAppIfNeeded();
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchHomePage();
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_context.CredentialIdentity = credentialIdentity;
|
_context.CredentialIdentity = credentialIdentity;
|
||||||
@@ -111,7 +114,7 @@ namespace Bit.iOS.Autofill
|
|||||||
_context.Configuring = true;
|
_context.Configuring = true;
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
LaunchHomePage();
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CheckLock(() => PerformSegue("setupSegue", this));
|
CheckLock(() => PerformSegue("setupSegue", this));
|
||||||
@@ -230,7 +233,6 @@ namespace Bit.iOS.Autofill
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
|
||||||
var decCipher = await cipher.DecryptAsync();
|
var decCipher = await cipher.DecryptAsync();
|
||||||
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
|
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
|
||||||
{
|
{
|
||||||
@@ -238,13 +240,13 @@ namespace Bit.iOS.Autofill
|
|||||||
// already verified the password.
|
// already verified the password.
|
||||||
if (!userInteraction)
|
if (!userInteraction)
|
||||||
{
|
{
|
||||||
await stateService.SetPasswordRepromptAutofillAsync(true);
|
await _stateService.Value.SetPasswordRepromptAutofillAsync(true);
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
||||||
ExtensionContext?.CancelRequest(err);
|
ExtensionContext?.CancelRequest(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (!await stateService.GetPasswordVerifiedAutofillAsync())
|
else if (!await _stateService.Value.GetPasswordVerifiedAutofillAsync())
|
||||||
{
|
{
|
||||||
// Add a timeout to resolve keyboard not always showing up.
|
// Add a timeout to resolve keyboard not always showing up.
|
||||||
await Task.Delay(250);
|
await Task.Delay(250);
|
||||||
@@ -259,10 +261,10 @@ namespace Bit.iOS.Autofill
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
string totpCode = null;
|
string totpCode = null;
|
||||||
var disableTotpCopy = await stateService.GetDisableAutoTotpCopyAsync();
|
var disableTotpCopy = await _stateService.Value.GetDisableAutoTotpCopyAsync();
|
||||||
if (!disableTotpCopy.GetValueOrDefault(false))
|
if (!disableTotpCopy.GetValueOrDefault(false))
|
||||||
{
|
{
|
||||||
var canAccessPremiumAsync = await stateService.CanAccessPremiumAsync();
|
var canAccessPremiumAsync = await _stateService.Value.CanAccessPremiumAsync();
|
||||||
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
|
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
|
||||||
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
|
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
|
||||||
{
|
{
|
||||||
@@ -276,8 +278,7 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
private async void CheckLock(Action notLockedAction)
|
private async void CheckLock(Action notLockedAction)
|
||||||
{
|
{
|
||||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
||||||
if (await IsLocked() || await stateService.GetPasswordRepromptAutofillAsync())
|
|
||||||
{
|
{
|
||||||
PerformSegue("lockPasswordSegue", this);
|
PerformSegue("lockPasswordSegue", this);
|
||||||
}
|
}
|
||||||
@@ -295,8 +296,7 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
private Task<bool> IsAuthed()
|
private Task<bool> IsAuthed()
|
||||||
{
|
{
|
||||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
return _stateService.Value.IsAuthenticatedAsync();
|
||||||
return stateService.IsAuthenticatedAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogoutIfAuthed()
|
private void LogoutIfAuthed()
|
||||||
@@ -305,8 +305,7 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
if (await IsAuthed())
|
if (await IsAuthed())
|
||||||
{
|
{
|
||||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
||||||
await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync());
|
|
||||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
if (deviceActionService.SystemMajorVersion() >= 12)
|
if (deviceActionService.SystemMajorVersion() >= 12)
|
||||||
{
|
{
|
||||||
@@ -330,18 +329,18 @@ namespace Bit.iOS.Autofill
|
|||||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent,
|
ServiceContainer.Init(deviceActionService.DeviceUserAgent,
|
||||||
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
||||||
if (!_initedAppCenter)
|
iOSCoreHelpers.InitLogger();
|
||||||
{
|
|
||||||
iOSCoreHelpers.RegisterAppCenter();
|
|
||||||
_initedAppCenter = true;
|
|
||||||
}
|
|
||||||
iOSCoreHelpers.Bootstrap();
|
iOSCoreHelpers.Bootstrap();
|
||||||
var app = new App.App(new AppOptions { IosExtension = true });
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
|
var app = new App.App(appOptions);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
ThemeManager.SetTheme(app.Resources);
|
||||||
iOSCoreHelpers.AppearanceAdjustments();
|
iOSCoreHelpers.AppearanceAdjustments();
|
||||||
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
||||||
messagingService.Send("gotYubiKeyOTP", message));
|
messagingService.Send("gotYubiKeyOTP", message));
|
||||||
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
|
iOSCoreHelpers.SubscribeBroadcastReceiver(this, _nfcSession, _nfcDelegate);
|
||||||
|
|
||||||
|
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||||
|
_accountsManager.Init(() => appOptions, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitAppIfNeeded()
|
private void InitAppIfNeeded()
|
||||||
@@ -519,5 +518,35 @@ namespace Bit.iOS.Autofill
|
|||||||
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
PresentViewController(updateTempPasswordController, true, null);
|
PresentViewController(updateTempPasswordController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
|
||||||
|
public Task UpdateThemeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
||||||
|
{
|
||||||
|
switch (navTarget)
|
||||||
|
{
|
||||||
|
case NavigationTarget.HomeLogin:
|
||||||
|
DismissViewController(false, () => LaunchHomePage());
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Login:
|
||||||
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
|
{
|
||||||
|
DismissViewController(false, () => LaunchLoginFlow(loginParams.Email));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DismissViewController(false, () => LaunchLoginFlow());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NavigationTarget.Lock:
|
||||||
|
DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
|
||||||
|
break;
|
||||||
|
case NavigationTarget.AutofillCiphers:
|
||||||
|
case NavigationTarget.Home:
|
||||||
|
DismissViewController(false, () => PerformSegue("loginListSegue", this));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.18.1</string>
|
<string>2022.05.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill
|
namespace Bit.iOS.Autofill
|
||||||
{
|
{
|
||||||
public partial class LockPasswordViewController : Core.Controllers.LockPasswordViewController
|
public partial class LockPasswordViewController : Core.Controllers.BaseLockPasswordViewController
|
||||||
{
|
{
|
||||||
|
AccountSwitchingOverlayView _accountSwitchingOverlayView;
|
||||||
|
AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper;
|
||||||
|
|
||||||
|
public override UITableView TableView => MainTableView;
|
||||||
|
|
||||||
public LockPasswordViewController(IntPtr handle)
|
public LockPasswordViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{
|
{
|
||||||
@@ -20,6 +27,21 @@ namespace Bit.iOS.Autofill
|
|||||||
public override Action Success => () => CPViewController.DismissLockAndContinue();
|
public override Action Success => () => CPViewController.DismissLockAndContinue();
|
||||||
public override Action Cancel => () => CPViewController.CompleteRequest();
|
public override Action Cancel => () => CPViewController.CompleteRequest();
|
||||||
|
|
||||||
|
public override async void ViewDidLoad()
|
||||||
|
{
|
||||||
|
base.ViewDidLoad();
|
||||||
|
|
||||||
|
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
|
||||||
|
AccountSwitchingBarButton.Image = await _accountSwitchingOverlayHelper.CreateAvatarImageAsync();
|
||||||
|
|
||||||
|
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(OverlayView);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void AccountSwitchingBarButton_Activated(UIBarButtonItem sender)
|
||||||
|
{
|
||||||
|
_accountSwitchingOverlayHelper.OnToolbarItemActivated(_accountSwitchingOverlayView, OverlayView);
|
||||||
|
}
|
||||||
|
|
||||||
partial void SubmitButton_Activated(UIBarButtonItem sender)
|
partial void SubmitButton_Activated(UIBarButtonItem sender)
|
||||||
{
|
{
|
||||||
var task = CheckPasswordAsync();
|
var task = CheckPasswordAsync();
|
||||||
|
|||||||
107
src/iOS.Autofill/LockPasswordViewController.designer.cs
generated
107
src/iOS.Autofill/LockPasswordViewController.designer.cs
generated
@@ -1,64 +1,79 @@
|
|||||||
// WARNING
|
// WARNING
|
||||||
//
|
//
|
||||||
// This file has been generated automatically by Visual Studio from the outlets and
|
// This file has been generated automatically by Visual Studio to store outlets and
|
||||||
// actions declared in your storyboard file.
|
// actions made in the UI designer. If it is removed, they will be lost.
|
||||||
// Manual changes to this file will not be maintained.
|
// Manual changes to this file may not be handled correctly.
|
||||||
//
|
//
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using System;
|
|
||||||
using System.CodeDom.Compiler;
|
using System.CodeDom.Compiler;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill
|
namespace Bit.iOS.Autofill
|
||||||
{
|
{
|
||||||
[Register ("LockPasswordViewController")]
|
[Register ("LockPasswordViewController")]
|
||||||
partial class LockPasswordViewController
|
partial class LockPasswordViewController
|
||||||
{
|
{
|
||||||
[Outlet]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
|
||||||
UIKit.UIBarButtonItem CancelButton { get; set; }
|
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
[GeneratedCode ("iOS Designer", "1.0")]
|
||||||
UIKit.UITableView MainTableView { get; set; }
|
UIKit.UIBarButtonItem CancelButton { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
[GeneratedCode ("iOS Designer", "1.0")]
|
||||||
UIKit.UINavigationItem NavItem { get; set; }
|
UIKit.UITableView MainTableView { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
[GeneratedCode ("iOS Designer", "1.0")]
|
||||||
UIKit.UIBarButtonItem SubmitButton { get; set; }
|
UIKit.UINavigationItem NavItem { get; set; }
|
||||||
|
|
||||||
[Action ("CancelButton_Activated:")]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
UIKit.UIView OverlayView { get; set; }
|
||||||
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
|
||||||
|
|
||||||
[Action ("SubmitButton_Activated:")]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
[GeneratedCode ("iOS Designer", "1.0")]
|
||||||
partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender);
|
UIKit.UIBarButtonItem SubmitButton { get; set; }
|
||||||
|
|
||||||
void ReleaseDesignerOutlets ()
|
[Action ("AccountSwitchingBarButton_Activated:")]
|
||||||
{
|
partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
if (CancelButton != null) {
|
|
||||||
CancelButton.Dispose ();
|
|
||||||
CancelButton = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MainTableView != null) {
|
[Action ("CancelButton_Activated:")]
|
||||||
MainTableView.Dispose ();
|
partial void CancelButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
MainTableView = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NavItem != null) {
|
[Action ("SubmitButton_Activated:")]
|
||||||
NavItem.Dispose ();
|
partial void SubmitButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
NavItem = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SubmitButton != null) {
|
void ReleaseDesignerOutlets ()
|
||||||
SubmitButton.Dispose ();
|
{
|
||||||
SubmitButton = null;
|
if (AccountSwitchingBarButton != null) {
|
||||||
}
|
AccountSwitchingBarButton.Dispose ();
|
||||||
}
|
AccountSwitchingBarButton = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CancelButton != null) {
|
||||||
|
CancelButton.Dispose ();
|
||||||
|
CancelButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MainTableView != null) {
|
||||||
|
MainTableView.Dispose ();
|
||||||
|
MainTableView = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NavItem != null) {
|
||||||
|
NavItem.Dispose ();
|
||||||
|
NavItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SubmitButton != null) {
|
||||||
|
SubmitButton.Dispose ();
|
||||||
|
SubmitButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OverlayView != null) {
|
||||||
|
OverlayView.Dispose ();
|
||||||
|
OverlayView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user