mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Merge branch 'master' into feature/totp-tab
# Conflicts: # src/App/Pages/Vault/GroupingsPage/GroupingsPageViewModel.cs # src/App/Pages/Vault/ScanPage.xaml.cs
This commit is contained in:
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
|
|
||||||
## 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.
|
||||||
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@@ -60,6 +60,11 @@ jobs:
|
|||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
@@ -209,6 +214,11 @@ jobs:
|
|||||||
name: F-Droid Build
|
name: F-Droid Build
|
||||||
runs-on: windows-2019
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
@@ -368,6 +378,11 @@ jobs:
|
|||||||
runs-on: macos-11
|
runs-on: macos-11
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup NuGet
|
||||||
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
|
with:
|
||||||
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
nuget help | grep Version
|
nuget help | grep Version
|
||||||
|
|||||||
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>
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -121,13 +130,14 @@ namespace Bit.Droid
|
|||||||
var secureStorageService = new SecureStorageService();
|
var secureStorageService = new SecureStorageService();
|
||||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||||
var stateService = new StateService(mobileStorageService, secureStorageService);
|
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
|
||||||
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);
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ namespace Bit.Droid.Utilities
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
|
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
|
||||||
{
|
{
|
||||||
theme = "dark";
|
theme = ThemeManager.Dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theme == "dark" || theme == "black" || theme == "nord")
|
if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
|
||||||
{
|
{
|
||||||
LightTheme = false;
|
LightTheme = false;
|
||||||
}
|
}
|
||||||
|
|||||||
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,12 +13,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
|
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
||||||
<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>
|
||||||
@@ -128,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>
|
||||||
@@ -420,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()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +202,7 @@ namespace Bit.App
|
|||||||
|
|
||||||
private async Task ResumedAsync()
|
private async Task ResumedAsync()
|
||||||
{
|
{
|
||||||
|
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
_messagingService.Send("startEventTimer");
|
_messagingService.Send("startEventTimer");
|
||||||
await UpdateThemeAsync();
|
await UpdateThemeAsync();
|
||||||
@@ -263,102 +232,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 +293,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 +314,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 +341,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,10 @@ 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 Action AfterHide { get; set; }
|
||||||
|
|
||||||
public async Task ToggleVisibilityAsync()
|
public async Task ToggleVisibilityAsync()
|
||||||
{
|
{
|
||||||
if (IsVisible)
|
if (IsVisible)
|
||||||
@@ -135,6 +139,8 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
// remove overlay
|
// remove overlay
|
||||||
IsVisible = false;
|
IsVisible = false;
|
||||||
|
|
||||||
|
AfterHide?.Invoke();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +173,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,23 +45,28 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public ICommand LongPressAccountCommand { get; }
|
public ICommand LongPressAccountCommand { get; }
|
||||||
|
|
||||||
|
public bool FromIOSExtension { get; set; }
|
||||||
|
|
||||||
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||||
{
|
{
|
||||||
if (item.AccountView.IsAccount)
|
if (!item.AccountView.IsAccount)
|
||||||
{
|
{
|
||||||
if (!item.AccountView.IsActive)
|
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.AccountView.IsActive)
|
||||||
|
{
|
||||||
|
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
||||||
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
|
if (FromIOSExtension)
|
||||||
{
|
{
|
||||||
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
|
||||||
_messagingService.Send("switchedAccount");
|
|
||||||
}
|
|
||||||
else if (AllowActiveAccountSelection)
|
|
||||||
{
|
|
||||||
_messagingService.Send("switchedAccount");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (AllowActiveAccountSelection)
|
||||||
{
|
{
|
||||||
_messagingService.Send("addAccount");
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
public class ExtendedCollectionView : CollectionView
|
public class ExtendedCollectionView : CollectionView
|
||||||
{
|
{
|
||||||
|
public string ExtraDataForLogging { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
@@ -319,8 +318,7 @@ 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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -33,6 +33,23 @@
|
|||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
Text="{u:I18n ThemeDescription}" />
|
Text="{u:I18n ThemeDescription}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box"
|
||||||
|
IsVisible="{Binding ShowAutoDarkThemeOptions}">
|
||||||
|
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n DefaultDarkTheme}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Picker
|
||||||
|
x:Name="_autoDarkThemePicker"
|
||||||
|
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
|
||||||
|
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
|
||||||
|
StyleClass="box-value" />
|
||||||
|
</StackLayout>
|
||||||
|
<Label
|
||||||
|
StyleClass="box-footer-label"
|
||||||
|
Text="{u:I18n DefaultDarkThemeDescription}" />
|
||||||
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace Bit.App.Pages
|
|||||||
_vm = BindingContext as OptionsPageViewModel;
|
_vm = BindingContext as OptionsPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_themePicker.ItemDisplayBinding = new Binding("Value");
|
_themePicker.ItemDisplayBinding = new Binding("Value");
|
||||||
|
_autoDarkThemePicker.ItemDisplayBinding = new Binding("Value");
|
||||||
_uriMatchPicker.ItemDisplayBinding = new Binding("Value");
|
_uriMatchPicker.ItemDisplayBinding = new Binding("Value");
|
||||||
_clearClipboardPicker.ItemDisplayBinding = new Binding("Value");
|
_clearClipboardPicker.ItemDisplayBinding = new Binding("Value");
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
@@ -29,6 +30,7 @@ namespace Bit.App.Pages
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
_themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||||
|
_autoDarkThemePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||||
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||||
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace Bit.App.Pages
|
|||||||
private bool _disableAutoTotpCopy;
|
private bool _disableAutoTotpCopy;
|
||||||
private int _clearClipboardSelectedIndex;
|
private int _clearClipboardSelectedIndex;
|
||||||
private int _themeSelectedIndex;
|
private int _themeSelectedIndex;
|
||||||
|
private int _autoDarkThemeSelectedIndex;
|
||||||
private int _uriMatchSelectedIndex;
|
private int _uriMatchSelectedIndex;
|
||||||
private bool _inited;
|
private bool _inited;
|
||||||
private bool _updatingAutofill;
|
private bool _updatingAutofill;
|
||||||
@@ -53,10 +54,16 @@ namespace Bit.App.Pages
|
|||||||
ThemeOptions = new List<KeyValuePair<string, string>>
|
ThemeOptions = new List<KeyValuePair<string, string>>
|
||||||
{
|
{
|
||||||
new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
|
new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
|
||||||
new KeyValuePair<string, string>("light", AppResources.Light),
|
new KeyValuePair<string, string>(ThemeManager.Light, AppResources.Light),
|
||||||
new KeyValuePair<string, string>("dark", AppResources.Dark),
|
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
||||||
new KeyValuePair<string, string>("black", AppResources.Black),
|
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
||||||
new KeyValuePair<string, string>("nord", "Nord"),
|
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
||||||
|
};
|
||||||
|
AutoDarkThemeOptions = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
||||||
|
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
||||||
|
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
||||||
};
|
};
|
||||||
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
|
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
|
||||||
{
|
{
|
||||||
@@ -71,6 +78,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
||||||
public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
|
public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
|
||||||
|
public List<KeyValuePair<string, string>> AutoDarkThemeOptions { get; set; }
|
||||||
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
|
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
|
||||||
|
|
||||||
public int ClearClipboardSelectedIndex
|
public int ClearClipboardSelectedIndex
|
||||||
@@ -80,7 +88,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref _clearClipboardSelectedIndex, value))
|
if (SetProperty(ref _clearClipboardSelectedIndex, value))
|
||||||
{
|
{
|
||||||
var task = SaveClipboardChangedAsync();
|
SaveClipboardChangedAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,9 +98,25 @@ namespace Bit.App.Pages
|
|||||||
get => _themeSelectedIndex;
|
get => _themeSelectedIndex;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _themeSelectedIndex, value))
|
if (SetProperty(ref _themeSelectedIndex, value,
|
||||||
|
additionalPropertyNames: new[] { nameof(ShowAutoDarkThemeOptions) })
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var task = SaveThemeAsync();
|
SaveThemeAsync().FireAndForget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowAutoDarkThemeOptions => ThemeOptions[ThemeSelectedIndex].Key == null;
|
||||||
|
|
||||||
|
public int AutoDarkThemeSelectedIndex
|
||||||
|
{
|
||||||
|
get => _autoDarkThemeSelectedIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _autoDarkThemeSelectedIndex, value))
|
||||||
|
{
|
||||||
|
SaveThemeAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +128,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref _uriMatchSelectedIndex, value))
|
if (SetProperty(ref _uriMatchSelectedIndex, value))
|
||||||
{
|
{
|
||||||
var task = SaveDefaultUriAsync();
|
SaveDefaultUriAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +140,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref _disableFavicon, value))
|
if (SetProperty(ref _disableFavicon, value))
|
||||||
{
|
{
|
||||||
var task = UpdateDisableFaviconAsync();
|
UpdateDisableFaviconAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +152,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref _disableAutoTotpCopy, value))
|
if (SetProperty(ref _disableAutoTotpCopy, value))
|
||||||
{
|
{
|
||||||
var task = UpdateAutoTotpCopyAsync();
|
UpdateAutoTotpCopyAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +164,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref _autofillDisableSavePrompt, value))
|
if (SetProperty(ref _autofillDisableSavePrompt, value))
|
||||||
{
|
{
|
||||||
var task = UpdateAutofillDisableSavePromptAsync();
|
UpdateAutofillDisableSavePromptAsync().FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +190,8 @@ namespace Bit.App.Pages
|
|||||||
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
var theme = await _stateService.GetThemeAsync();
|
var theme = await _stateService.GetThemeAsync();
|
||||||
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
|
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
|
||||||
|
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
|
||||||
|
AutoDarkThemeSelectedIndex = AutoDarkThemeOptions.FindIndex(k => k.Key == autoDarkTheme);
|
||||||
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
|
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
|
||||||
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
|
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
|
||||||
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
|
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
|
||||||
@@ -202,8 +228,8 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (_inited && ThemeSelectedIndex > -1)
|
if (_inited && ThemeSelectedIndex > -1)
|
||||||
{
|
{
|
||||||
var theme = ThemeOptions[ThemeSelectedIndex].Key;
|
await _stateService.SetThemeAsync(ThemeOptions[ThemeSelectedIndex].Key);
|
||||||
await _stateService.SetThemeAsync(theme);
|
await _stateService.SetAutoDarkThemeAsync(AutoDarkThemeOptions[AutoDarkThemeSelectedIndex].Key);
|
||||||
ThemeManager.SetTheme(Application.Current.Resources);
|
ThemeManager.SetTheme(Application.Current.Resources);
|
||||||
_messagingService.Send("updatedTheme");
|
_messagingService.Send("updatedTheme");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +167,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _vm.UpdatePinAsync();
|
await _vm.UpdatePinAsync();
|
||||||
}
|
}
|
||||||
else if (item.Name == AppResources.ReportCrashLogs)
|
else if (item.Name == AppResources.SubmitCrashLogs)
|
||||||
{
|
{
|
||||||
await _vm.LoggerReportingAsync();
|
await _vm.LoggerReportingAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ namespace Bit.App.Pages
|
|||||||
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
|
CreateSelectableOption(AppResources.No, !_reportLoggingEnabled),
|
||||||
};
|
};
|
||||||
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.ReportCrashLogsDescription, AppResources.Cancel, null, options);
|
var selection = await Page.DisplayActionSheet(AppResources.SubmitCrashLogsDescription, AppResources.Cancel, null, options);
|
||||||
|
|
||||||
if (selection == null || selection == AppResources.Cancel)
|
if (selection == null || selection == AppResources.Cancel)
|
||||||
{
|
{
|
||||||
@@ -525,7 +525,7 @@ namespace Bit.App.Pages
|
|||||||
#if !FDROID
|
#if !FDROID
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.ReportCrashLogs,
|
Name = AppResources.SubmitCrashLogs,
|
||||||
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -49,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"
|
||||||
@@ -68,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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
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
|
||||||
{
|
{
|
||||||
public class CiphersPageViewModel : BaseViewModel
|
public class CiphersPageViewModel : VaultFilterViewModel
|
||||||
{
|
{
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
@@ -23,7 +23,10 @@ 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 _showNoData;
|
private bool _showNoData;
|
||||||
private bool _showList;
|
private bool _showList;
|
||||||
@@ -37,6 +40,9 @@ 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);
|
||||||
@@ -48,6 +54,11 @@ namespace Bit.App.Pages
|
|||||||
public string AutofillUrl { get; set; }
|
public string AutofillUrl { get; set; }
|
||||||
public bool Deleted { get; set; }
|
public bool Deleted { get; set; }
|
||||||
|
|
||||||
|
protected override ICipherService cipherService => _cipherService;
|
||||||
|
protected override IPolicyService policyService => _policyService;
|
||||||
|
protected override IOrganizationService organizationService => _organizationService;
|
||||||
|
protected override ILogger logger => _logger;
|
||||||
|
|
||||||
public bool ShowNoData
|
public bool ShowNoData
|
||||||
{
|
{
|
||||||
get => _showNoData;
|
get => _showNoData;
|
||||||
@@ -76,11 +87,9 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
|
await InitVaultFilterAsync(true);
|
||||||
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
|
PerformSearchIfPopulated();
|
||||||
{
|
|
||||||
Search((Page as CiphersPage).SearchBar.Text, 200);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Search(string searchText, int? timeout = null)
|
public void Search(string searchText, int? timeout = null)
|
||||||
@@ -107,8 +116,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 +202,19 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PerformSearchIfPopulated()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
|
||||||
|
{
|
||||||
|
Search((Page as CiphersPage).SearchBar.Text, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnVaultFilterSelectedAsync()
|
||||||
|
{
|
||||||
|
PerformSearchIfPopulated();
|
||||||
|
}
|
||||||
|
|
||||||
private async void CipherOptionsAsync(CipherView cipher)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
AutomationProperties.Name="{u:I18n Filter}" />
|
AutomationProperties.Name="{u:I18n Filter}" />
|
||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
|
||||||
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
|
StyleClass="list-row-button-text, list-row-button-platform"
|
||||||
Command="{Binding VaultFilterCommand}"
|
Command="{Binding VaultFilterCommand}"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
@@ -185,7 +185,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>
|
||||||
|
|||||||
@@ -264,7 +264,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GroupingsPageViewModel : BaseViewModel
|
public class GroupingsPageViewModel : VaultFilterViewModel
|
||||||
{
|
{
|
||||||
private const int NoFolderListSize = 100;
|
private const int NoFolderListSize = 100;
|
||||||
|
|
||||||
@@ -30,12 +30,9 @@ 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 bool _showTotpFilter;
|
||||||
private bool _showTOTPFilter;
|
|
||||||
private bool _totpFilterEnable;
|
private bool _totpFilterEnable;
|
||||||
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>();
|
||||||
@@ -113,6 +110,11 @@ namespace Bit.App.Pages
|
|||||||
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
||||||
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
|
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
|
||||||
|
|
||||||
|
protected override ICipherService cipherService => _cipherService;
|
||||||
|
protected override IPolicyService policyService => _policyService;
|
||||||
|
protected override IOrganizationService organizationService => _organizationService;
|
||||||
|
protected override ILogger logger => _logger;
|
||||||
|
|
||||||
public bool Refreshing
|
public bool Refreshing
|
||||||
{
|
{
|
||||||
get => _refreshing;
|
get => _refreshing;
|
||||||
@@ -158,41 +160,20 @@ 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 bool ShowTotpFilter
|
public bool ShowTotpFilter
|
||||||
{
|
{
|
||||||
get => _showTOTPFilter;
|
get => _showTotpFilter;
|
||||||
set => SetProperty(ref _showTOTPFilter, value);
|
set => SetProperty(ref _showTotpFilter, value);
|
||||||
}
|
}
|
||||||
public bool TotpFilterEnable
|
public bool TotpFilterEnable
|
||||||
{
|
{
|
||||||
get => _totpFilterEnable;
|
get => _totpFilterEnable;
|
||||||
set => SetProperty(ref _totpFilterEnable, value);
|
set => SetProperty(ref _totpFilterEnable, 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 ICommand TotpFilterCommand { get; }
|
public ICommand TotpFilterCommand { get; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
|
|
||||||
@@ -218,14 +199,9 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_organizations = await _organizationService.GetAllAsync();
|
await InitVaultFilterAsync(MainPage);
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
|
||||||
if (ShowVaultFilter && _vaultFilterSelection == null)
|
|
||||||
{
|
|
||||||
_vaultFilterSelection = AppResources.AllVaults;
|
|
||||||
}
|
|
||||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
}
|
}
|
||||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||||
@@ -436,22 +412,8 @@ namespace Bit.App.Pages
|
|||||||
SyncRefreshing = false;
|
SyncRefreshing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task VaultFilterOptionsAsync()
|
protected override async Task OnVaultFilterSelectedAsync()
|
||||||
{
|
{
|
||||||
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
|
||||||
if (_organizations.Any())
|
|
||||||
{
|
|
||||||
options.AddRange(_organizations.Select(o => o.Name));
|
|
||||||
}
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
|
||||||
options.ToArray());
|
|
||||||
if (selection == AppResources.Cancel ||
|
|
||||||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
|
||||||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VaultFilterDescription = selection;
|
|
||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,8 +500,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 GetAllCiphersAsync();
|
||||||
HasCiphers = _allCiphers.Any();
|
HasCiphers = _allCiphers.Any();
|
||||||
FavoriteCiphers?.Clear();
|
FavoriteCiphers?.Clear();
|
||||||
NoFolderCiphers?.Clear();
|
NoFolderCiphers?.Clear();
|
||||||
@@ -553,7 +515,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
await FillFoldersAndCollectionsAsync(orgId);
|
await FillFoldersAndCollectionsAsync();
|
||||||
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
||||||
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
||||||
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
||||||
@@ -677,28 +639,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
|
private async Task FillFoldersAndCollectionsAsync()
|
||||||
{
|
|
||||||
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 orgId = GetVaultFilterOrgId();
|
||||||
var decFolders = await _folderService.GetAllDecryptedAsync();
|
var decFolders = await _folderService.GetAllDecryptedAsync();
|
||||||
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
||||||
if (IsVaultFilterMyVault)
|
if (IsVaultFilterMyVault)
|
||||||
@@ -718,16 +661,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
|
||||||
|
|
||||||
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
|
||||||
_vaultFilterSelection != AppResources.MyVault;
|
|
||||||
|
|
||||||
private string GetVaultFilterOrgId()
|
|
||||||
{
|
|
||||||
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
||||||
{
|
{
|
||||||
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ namespace Bit.App.Pages
|
|||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,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
|
||||||
@@ -70,7 +72,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)
|
||||||
{
|
{
|
||||||
@@ -99,7 +101,10 @@ namespace Bit.App.Pages
|
|||||||
protected override async void OnDisappearing()
|
protected override async void OnDisappearing()
|
||||||
{
|
{
|
||||||
_autofocusCts?.Cancel();
|
_autofocusCts?.Cancel();
|
||||||
await _continuousAutofocusTask;
|
if (_continuousAutofocusTask != null)
|
||||||
|
{
|
||||||
|
await _continuousAutofocusTask;
|
||||||
|
}
|
||||||
_zxing.IsScanning = false;
|
_zxing.IsScanning = false;
|
||||||
_pageIsActive = false;
|
_pageIsActive = false;
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
|
|||||||
@@ -688,7 +688,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")
|
||||||
{
|
{
|
||||||
|
|||||||
123
src/App/Pages/VaultFilterViewModel.cs
Normal file
123
src/App/Pages/VaultFilterViewModel.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public abstract class VaultFilterViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
protected abstract ICipherService cipherService { get; }
|
||||||
|
protected abstract IPolicyService policyService { get; }
|
||||||
|
protected abstract IOrganizationService organizationService { get; }
|
||||||
|
protected abstract ILogger logger { get; }
|
||||||
|
|
||||||
|
protected bool _showVaultFilter;
|
||||||
|
protected bool _personalOwnershipPolicyApplies;
|
||||||
|
protected string _vaultFilterSelection;
|
||||||
|
protected List<Organization> _organizations;
|
||||||
|
|
||||||
|
public VaultFilterViewModel()
|
||||||
|
{
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand VaultFilterCommand { get; set; }
|
||||||
|
|
||||||
|
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 string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
protected bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
|
protected async Task InitVaultFilterAsync(bool shouldUpdateShowVaultFilter)
|
||||||
|
{
|
||||||
|
_organizations = await organizationService.GetAllAsync();
|
||||||
|
if (_organizations?.Any() ?? false)
|
||||||
|
{
|
||||||
|
_personalOwnershipPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||||
|
var singleOrgPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.OnlyOrg);
|
||||||
|
if (_vaultFilterSelection == null || (_personalOwnershipPolicyApplies && singleOrgPolicyApplies))
|
||||||
|
{
|
||||||
|
VaultFilterDescription = AppResources.AllVaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldUpdateShowVaultFilter)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
ShowVaultFilter = await policyService.ShouldShowVaultFilterAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task VaultFilterOptionsAsync()
|
||||||
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults };
|
||||||
|
if (!_personalOwnershipPolicyApplies)
|
||||||
|
{
|
||||||
|
options.Add(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 OnVaultFilterSelectedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task OnVaultFilterSelectedAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/App/Resources/AppResources.Designer.cs
generated
30
src/App/Resources/AppResources.Designer.cs
generated
@@ -1,7 +1,6 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// This code was generated by a tool.
|
||||||
// Runtime Version:4.0.30319.42000
|
|
||||||
//
|
//
|
||||||
// Changes to this file may cause incorrect behavior and will be lost if
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// the code is regenerated.
|
// the code is regenerated.
|
||||||
@@ -10,10 +9,9 @@
|
|||||||
|
|
||||||
namespace Bit.App.Resources {
|
namespace Bit.App.Resources {
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
|
|
||||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.1.0.0")]
|
||||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
public class AppResources {
|
public class AppResources {
|
||||||
@@ -2705,6 +2703,18 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string DefaultDarkTheme {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DefaultDarkTheme", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string DefaultDarkThemeDescription {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DefaultDarkThemeDescription", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string CopyNotes {
|
public static string CopyNotes {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("CopyNotes", resourceCulture);
|
return ResourceManager.GetString("CopyNotes", resourceCulture);
|
||||||
@@ -2735,6 +2745,12 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string Nord {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Nord", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string BlacklistedUris {
|
public static string BlacklistedUris {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("BlacklistedUris", resourceCulture);
|
return ResourceManager.GetString("BlacklistedUris", resourceCulture);
|
||||||
@@ -3929,15 +3945,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ReportCrashLogs {
|
public static string SubmitCrashLogs {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("ReportCrashLogs", resourceCulture);
|
return ResourceManager.GetString("SubmitCrashLogs", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ReportCrashLogsDescription {
|
public static string SubmitCrashLogsDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("ReportCrashLogsDescription", resourceCulture);
|
return ResourceManager.GetString("SubmitCrashLogsDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1545,6 +1545,12 @@
|
|||||||
<data name="ThemeDefault" xml:space="preserve">
|
<data name="ThemeDefault" xml:space="preserve">
|
||||||
<value>Default (System)</value>
|
<value>Default (System)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DefaultDarkTheme" xml:space="preserve">
|
||||||
|
<value>Default Dark Theme</value>
|
||||||
|
</data>
|
||||||
|
<data name="DefaultDarkThemeDescription" xml:space="preserve">
|
||||||
|
<value>Choose the dark theme to use when using Default (System) theme while your device's dark mode is enabled</value>
|
||||||
|
</data>
|
||||||
<data name="CopyNotes" xml:space="preserve">
|
<data name="CopyNotes" xml:space="preserve">
|
||||||
<value>Copy Note</value>
|
<value>Copy Note</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1561,6 +1567,10 @@
|
|||||||
<value>Black</value>
|
<value>Black</value>
|
||||||
<comment>The color black</comment>
|
<comment>The color black</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Nord" xml:space="preserve">
|
||||||
|
<value>Nord</value>
|
||||||
|
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
||||||
|
</data>
|
||||||
<data name="BlacklistedUris" xml:space="preserve">
|
<data name="BlacklistedUris" xml:space="preserve">
|
||||||
<value>Blacklisted URIs</value>
|
<value>Blacklisted URIs</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2200,11 +2210,11 @@
|
|||||||
<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="ReportCrashLogs" xml:space="preserve">
|
<data name="SubmitCrashLogs" xml:space="preserve">
|
||||||
<value>Report crash logs</value>
|
<value>Submit crash logs</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ReportCrashLogsDescription" xml:space="preserve">
|
<data name="SubmitCrashLogsDescription" xml:space="preserve">
|
||||||
<value>Help Bitwarden improve app stability by allowing crash reports.</value>
|
<value>Help Bitwarden improve app stability by submitting crash reports.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="OptionsExpanded" xml:space="preserve">
|
<data name="OptionsExpanded" xml:space="preserve">
|
||||||
<value>Options are expanded, tap to collapse.</value>
|
<value>Options are expanded, tap to collapse.</value>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace Bit.App.Services
|
|||||||
Constants.LastBuildKey,
|
Constants.LastBuildKey,
|
||||||
Constants.ClearCiphersCacheKey,
|
Constants.ClearCiphersCacheKey,
|
||||||
Constants.BiometricIntegrityKey,
|
Constants.BiometricIntegrityKey,
|
||||||
|
Constants.iOSExtensionActiveUserIdKey,
|
||||||
Constants.iOSAutoFillClearCiphersCacheKey,
|
Constants.iOSAutoFillClearCiphersCacheKey,
|
||||||
Constants.iOSAutoFillBiometricIntegrityKey,
|
Constants.iOSAutoFillBiometricIntegrityKey,
|
||||||
Constants.iOSExtensionClearCiphersCacheKey,
|
Constants.iOSExtensionClearCiphersCacheKey,
|
||||||
@@ -32,7 +33,7 @@ namespace Bit.App.Services
|
|||||||
Constants.iOSShareExtensionClearCiphersCacheKey,
|
Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||||
Constants.iOSShareExtensionBiometricIntegrityKey,
|
Constants.iOSShareExtensionBiometricIntegrityKey,
|
||||||
Constants.RememberedEmailKey,
|
Constants.RememberedEmailKey,
|
||||||
Constants.RememberedOrgIdentifierKey,
|
Constants.RememberedOrgIdentifierKey
|
||||||
};
|
};
|
||||||
|
|
||||||
public MobileStorageService(
|
public MobileStorageService(
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
217
src/App/Utilities/AccountManagement/AccountsManager.cs
Normal file
217
src/App/Utilities/AccountManagement/AccountsManager.cs
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
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 Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
await Device.InvokeOnMainThreadAsync(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)
|
||||||
|
|||||||
@@ -17,13 +17,18 @@ namespace Bit.App.Utilities
|
|||||||
|
|
||||||
public static bool IsThemeDirty = false;
|
public static bool IsThemeDirty = false;
|
||||||
|
|
||||||
public static void SetThemeStyle(string name, ResourceDictionary resources)
|
public const string Light = "light";
|
||||||
|
public const string Dark = "dark";
|
||||||
|
public const string Black = "black";
|
||||||
|
public const string Nord = "nord";
|
||||||
|
|
||||||
|
public static void SetThemeStyle(string name, string autoDarkName, ResourceDictionary resources)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Resources = () => resources;
|
Resources = () => resources;
|
||||||
|
|
||||||
var newTheme = NeedsThemeUpdate(name, resources);
|
var newTheme = NeedsThemeUpdate(name, autoDarkName, resources);
|
||||||
if (newTheme is null)
|
if (newTheme is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -85,22 +90,30 @@ namespace Bit.App.Utilities
|
|||||||
: Activator.CreateInstance(themeType) as ResourceDictionary;
|
: Activator.CreateInstance(themeType) as ResourceDictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ResourceDictionary NeedsThemeUpdate(string themeName, ResourceDictionary resources)
|
static ResourceDictionary NeedsThemeUpdate(string themeName, string autoDarkThemeName, ResourceDictionary resources)
|
||||||
{
|
{
|
||||||
switch (themeName)
|
switch (themeName)
|
||||||
{
|
{
|
||||||
case "dark":
|
case Dark:
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
||||||
case "black":
|
case Black:
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
|
||||||
case "nord":
|
case Nord:
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
|
||||||
case "light":
|
case Light:
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
||||||
default:
|
default:
|
||||||
if (OsDarkModeEnabled())
|
if (OsDarkModeEnabled())
|
||||||
{
|
{
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
switch (autoDarkThemeName)
|
||||||
|
{
|
||||||
|
case Black:
|
||||||
|
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
|
||||||
|
case Nord:
|
||||||
|
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
|
||||||
|
default:
|
||||||
|
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
||||||
}
|
}
|
||||||
@@ -108,7 +121,7 @@ namespace Bit.App.Utilities
|
|||||||
|
|
||||||
public static void SetTheme(ResourceDictionary resources)
|
public static void SetTheme(ResourceDictionary resources)
|
||||||
{
|
{
|
||||||
SetThemeStyle(GetTheme(), resources);
|
SetThemeStyle(GetTheme(), GetAutoDarkTheme(), resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetTheme()
|
public static string GetTheme()
|
||||||
@@ -117,6 +130,12 @@ namespace Bit.App.Utilities
|
|||||||
return stateService.GetThemeAsync().GetAwaiter().GetResult();
|
return stateService.GetThemeAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetAutoDarkTheme()
|
||||||
|
{
|
||||||
|
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
|
return stateService.GetAutoDarkThemeAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
public static bool OsDarkModeEnabled()
|
public static bool OsDarkModeEnabled()
|
||||||
{
|
{
|
||||||
if (Application.Current == null)
|
if (Application.Current == null)
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<string> GetActiveUserIdAsync();
|
Task<string> GetActiveUserIdAsync();
|
||||||
Task<bool> IsActiveAccountAsync(string userId = null);
|
Task<bool> IsActiveAccountAsync(string userId = null);
|
||||||
Task SetActiveUserAsync(string userId);
|
Task SetActiveUserAsync(string userId);
|
||||||
|
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||||
Task<bool> IsAuthenticatedAsync(string userId = null);
|
Task<bool> IsAuthenticatedAsync(string userId = null);
|
||||||
Task<string> GetUserIdAsync(string email);
|
Task<string> GetUserIdAsync(string email);
|
||||||
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
||||||
@@ -109,6 +110,8 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetRememberedOrgIdentifierAsync(string value);
|
Task SetRememberedOrgIdentifierAsync(string value);
|
||||||
Task<string> GetThemeAsync(string userId = null);
|
Task<string> GetThemeAsync(string userId = null);
|
||||||
Task SetThemeAsync(string value, string userId = null);
|
Task SetThemeAsync(string value, string userId = null);
|
||||||
|
Task<string> GetAutoDarkThemeAsync(string userId = null);
|
||||||
|
Task SetAutoDarkThemeAsync(string value, string userId = null);
|
||||||
Task<bool?> GetAddSitePromptShownAsync(string userId = null);
|
Task<bool?> GetAddSitePromptShownAsync(string userId = null);
|
||||||
Task SetAddSitePromptShownAsync(bool? value, string userId = null);
|
Task SetAddSitePromptShownAsync(bool? value, string userId = null);
|
||||||
Task<bool?> GetPushInitialPromptShownAsync();
|
Task<bool?> GetPushInitialPromptShownAsync();
|
||||||
@@ -145,5 +148,6 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
||||||
Task<string> GetTwoFactorTokenAsync(string email = null);
|
Task<string> GetTwoFactorTokenAsync(string email = null);
|
||||||
Task SetTwoFactorTokenAsync(string value, string email = null);
|
Task SetTwoFactorTokenAsync(string value, string email = null);
|
||||||
|
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
||||||
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
||||||
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
||||||
|
public static string iOSExtensionActiveUserIdKey = "iOSExtensionActiveUserId";
|
||||||
public static string EventCollectionKey = "eventCollection";
|
public static string EventCollectionKey = "eventCollection";
|
||||||
public static string RememberedEmailKey = "rememberedEmail";
|
public static string RememberedEmailKey = "rememberedEmail";
|
||||||
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}";
|
public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}";
|
||||||
public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}";
|
public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}";
|
||||||
public static string ThemeKey(string userId) => $"theme_{userId}";
|
public static string ThemeKey(string userId) => $"theme_{userId}";
|
||||||
|
public static string AutoDarkThemeKey(string userId) => $"autoDarkTheme_{userId}";
|
||||||
public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}";
|
public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}";
|
||||||
public static string PreviousPageKey(string userId) => $"previousPage_{userId}";
|
public static string PreviousPageKey(string userId) => $"previousPage_{userId}";
|
||||||
public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}";
|
public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}";
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -249,9 +249,14 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<bool> ShouldShowVaultFilterAsync()
|
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||||
{
|
{
|
||||||
var organizations = await _organizationService.GetAllAsync();
|
|
||||||
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||||
return (organizations?.Any() ?? false) && !personalOwnershipPolicyApplies;
|
var singleOrgPolicyApplies = await PolicyAppliesToUser(PolicyType.OnlyOrg);
|
||||||
|
if (personalOwnershipPolicyApplies && singleOrgPolicyApplies)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var organizations = await _organizationService.GetAllAsync();
|
||||||
|
return organizations?.Any() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool? GetPolicyBool(Policy policy, string key)
|
private bool? GetPolicyBool(Policy policy, string key)
|
||||||
|
|||||||
@@ -15,16 +15,20 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly IStorageService _secureStorageService;
|
private readonly IStorageService _secureStorageService;
|
||||||
|
private readonly IMessagingService _messagingService;
|
||||||
|
|
||||||
private State _state;
|
private State _state;
|
||||||
private bool _migrationChecked;
|
private bool _migrationChecked;
|
||||||
|
|
||||||
public List<AccountView> AccountViews { get; set; }
|
public List<AccountView> AccountViews { get; set; }
|
||||||
|
|
||||||
public StateService(IStorageService storageService, IStorageService secureStorageService)
|
public StateService(IStorageService storageService,
|
||||||
|
IStorageService secureStorageService,
|
||||||
|
IMessagingService messagingService)
|
||||||
{
|
{
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
_secureStorageService = secureStorageService;
|
_secureStorageService = secureStorageService;
|
||||||
|
_messagingService = messagingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetActiveUserIdAsync()
|
public async Task<string> GetActiveUserIdAsync()
|
||||||
@@ -67,6 +71,28 @@ namespace Bit.Core.Services
|
|||||||
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
|
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task CheckExtensionActiveUserAndSwitchIfNeededAsync()
|
||||||
|
{
|
||||||
|
var extensionUserId = await GetExtensionActiveUserIdFromStorageAsync();
|
||||||
|
if (string.IsNullOrEmpty(extensionUserId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_state?.ActiveUserId == extensionUserId)
|
||||||
|
{
|
||||||
|
// Clear the value in case the user changes the active user from the app
|
||||||
|
// so if that happens and the user sends the app to background and comes back
|
||||||
|
// the user is not changed again.
|
||||||
|
await SaveExtensionActiveUserIdToStorageAsync(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SetActiveUserAsync(extensionUserId);
|
||||||
|
await SaveExtensionActiveUserIdToStorageAsync(null);
|
||||||
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> IsAuthenticatedAsync(string userId = null)
|
public async Task<bool> IsAuthenticatedAsync(string userId = null)
|
||||||
{
|
{
|
||||||
return await GetAccessTokenAsync(userId) != null;
|
return await GetAccessTokenAsync(userId) != null;
|
||||||
@@ -898,6 +924,25 @@ namespace Bit.Core.Services
|
|||||||
SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget();
|
SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetAutoDarkThemeAsync(string userId = null)
|
||||||
|
{
|
||||||
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId);
|
||||||
|
return await GetValueAsync<string>(key, reconciledOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetAutoDarkThemeAsync(string value, string userId = null)
|
||||||
|
{
|
||||||
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId);
|
||||||
|
await SetValueAsync(key, value, reconciledOptions);
|
||||||
|
|
||||||
|
// TODO remove this to restore per-account Theme support
|
||||||
|
SetValueGloballyAsync(Constants.AutoDarkThemeKey, value, reconciledOptions).FireAndForget();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool?> GetAddSitePromptShownAsync(string userId = null)
|
public async Task<bool?> GetAddSitePromptShownAsync(string userId = null)
|
||||||
{
|
{
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
@@ -1388,6 +1433,7 @@ namespace Bit.Core.Services
|
|||||||
await SetPasswordVerifiedAutofillAsync(null, userId);
|
await SetPasswordVerifiedAutofillAsync(null, userId);
|
||||||
await SetSyncOnRefreshAsync(null, userId);
|
await SetSyncOnRefreshAsync(null, userId);
|
||||||
await SetThemeAsync(null, userId);
|
await SetThemeAsync(null, userId);
|
||||||
|
await SetAutoDarkThemeAsync(null, userId);
|
||||||
await SetAddSitePromptShownAsync(null, userId);
|
await SetAddSitePromptShownAsync(null, userId);
|
||||||
await SetPasswordGenerationOptionsAsync(null, userId);
|
await SetPasswordGenerationOptionsAsync(null, userId);
|
||||||
}
|
}
|
||||||
@@ -1397,6 +1443,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
await CheckStateAsync();
|
await CheckStateAsync();
|
||||||
var currentTheme = await GetThemeAsync();
|
var currentTheme = await GetThemeAsync();
|
||||||
|
var currentAutoDarkTheme = await GetAutoDarkThemeAsync();
|
||||||
var currentDisableFavicons = await GetDisableFaviconAsync();
|
var currentDisableFavicons = await GetDisableFaviconAsync();
|
||||||
|
|
||||||
account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync();
|
account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync();
|
||||||
@@ -1426,6 +1473,7 @@ namespace Bit.Core.Services
|
|||||||
account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock;
|
account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock;
|
||||||
}
|
}
|
||||||
await SetThemeAsync(currentTheme, account.Profile.UserId);
|
await SetThemeAsync(currentTheme, account.Profile.UserId);
|
||||||
|
await SetAutoDarkThemeAsync(currentAutoDarkTheme, account.Profile.UserId);
|
||||||
await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId);
|
await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId);
|
||||||
|
|
||||||
state.Accounts[account.Profile.UserId] = account;
|
state.Accounts[account.Profile.UserId] = account;
|
||||||
@@ -1510,6 +1558,16 @@ namespace Bit.Core.Services
|
|||||||
await _storageService.SaveAsync(Constants.StateKey, state);
|
await _storageService.SaveAsync(Constants.StateKey, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetExtensionActiveUserIdFromStorageAsync()
|
||||||
|
{
|
||||||
|
return await _storageService.GetAsync<string>(Constants.iOSExtensionActiveUserIdKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveExtensionActiveUserIdToStorageAsync(string userId)
|
||||||
|
{
|
||||||
|
await _storageService.SaveAsync(Constants.iOSExtensionActiveUserIdKey, userId);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CheckStateAsync()
|
private async Task CheckStateAsync()
|
||||||
{
|
{
|
||||||
if (!_migrationChecked)
|
if (!_migrationChecked)
|
||||||
|
|||||||
13
src/Core/Utilities/AccountsManagerMessageCommands.cs
Normal file
13
src/Core/Utilities/AccountsManagerMessageCommands.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Bit.Core.Utilities
|
||||||
|
{
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +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 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)
|
||||||
@@ -56,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())
|
||||||
{
|
{
|
||||||
@@ -78,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"),
|
||||||
@@ -97,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;
|
||||||
@@ -110,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));
|
||||||
@@ -229,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)
|
||||||
{
|
{
|
||||||
@@ -237,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);
|
||||||
@@ -258,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))
|
||||||
{
|
{
|
||||||
@@ -275,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);
|
||||||
}
|
}
|
||||||
@@ -294,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()
|
||||||
@@ -304,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)
|
||||||
{
|
{
|
||||||
@@ -331,12 +331,16 @@ namespace Bit.iOS.Autofill
|
|||||||
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, Bit.Core.Constants.iOSAllClearCipherCacheKeys);
|
||||||
iOSCoreHelpers.InitLogger();
|
iOSCoreHelpers.InitLogger();
|
||||||
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()
|
||||||
@@ -514,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
109
src/iOS.Autofill/LockPasswordViewController.designer.cs
generated
109
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;
|
|
||||||
}
|
void ReleaseDesignerOutlets ()
|
||||||
|
{
|
||||||
|
if (AccountSwitchingBarButton != null) {
|
||||||
|
AccountSwitchingBarButton.Dispose ();
|
||||||
|
AccountSwitchingBarButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (SubmitButton != null) {
|
if (CancelButton != null) {
|
||||||
SubmitButton.Dispose ();
|
CancelButton.Dispose ();
|
||||||
SubmitButton = null;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Bit.iOS.Autofill.Models;
|
using Bit.iOS.Autofill.Models;
|
||||||
|
using Bit.iOS.Autofill.Utilities;
|
||||||
|
using Bit.iOS.Core.Controllers;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using CoreFoundation;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using Bit.iOS.Core.Controllers;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.iOS.Core.Views;
|
|
||||||
using Bit.iOS.Autofill.Utilities;
|
|
||||||
using Bit.iOS.Core.Utilities;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill
|
namespace Bit.iOS.Autofill
|
||||||
{
|
{
|
||||||
public partial class LoginListViewController : ExtendedUITableViewController
|
public partial class LoginListViewController : ExtendedUIViewController
|
||||||
{
|
{
|
||||||
public LoginListViewController(IntPtr handle)
|
public LoginListViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
@@ -26,17 +28,30 @@ namespace Bit.iOS.Autofill
|
|||||||
public CredentialProviderViewController CPViewController { get; set; }
|
public CredentialProviderViewController CPViewController { get; set; }
|
||||||
public IPasswordRepromptService PasswordRepromptService { get; private set; }
|
public IPasswordRepromptService PasswordRepromptService { get; private set; }
|
||||||
|
|
||||||
|
AccountSwitchingOverlayView _accountSwitchingOverlayView;
|
||||||
|
AccountSwitchingOverlayHelper _accountSwitchingOverlayHelper;
|
||||||
|
|
||||||
|
LazyResolve<IBroadcasterService> _broadcasterService = new LazyResolve<IBroadcasterService>("broadcasterService");
|
||||||
|
LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
bool _alreadyLoadItemsOnce = false;
|
||||||
|
|
||||||
public async override void ViewDidLoad()
|
public async override void ViewDidLoad()
|
||||||
{
|
{
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
|
SubscribeSyncCompleted();
|
||||||
|
|
||||||
NavItem.Title = AppResources.Items;
|
NavItem.Title = AppResources.Items;
|
||||||
CancelBarButton.Title = AppResources.Cancel;
|
CancelBarButton.Title = AppResources.Cancel;
|
||||||
|
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
TableView.EstimatedRowHeight = 44;
|
TableView.EstimatedRowHeight = 44;
|
||||||
|
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
TableView.Source = new TableSource(this);
|
TableView.Source = new TableSource(this);
|
||||||
await ((TableSource)TableView.Source).LoadItemsAsync();
|
await ((TableSource)TableView.Source).LoadItemsAsync();
|
||||||
|
|
||||||
|
_alreadyLoadItemsOnce = true;
|
||||||
|
|
||||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
var needsAutofillReplacement = await storageService.GetAsync<bool?>(
|
var needsAutofillReplacement = await storageService.GetAsync<bool?>(
|
||||||
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
||||||
@@ -44,6 +59,16 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
await ASHelpers.ReplaceAllIdentities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_accountSwitchingOverlayHelper = new AccountSwitchingOverlayHelper();
|
||||||
|
AccountSwitchingBarButton.Image = await _accountSwitchingOverlayHelper.CreateAvatarImageAsync();
|
||||||
|
|
||||||
|
_accountSwitchingOverlayView = _accountSwitchingOverlayHelper.CreateAccountSwitchingOverlayView(OverlayView);
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void AccountSwitchingBarButton_Activated(UIBarButtonItem sender)
|
||||||
|
{
|
||||||
|
_accountSwitchingOverlayHelper.OnToolbarItemActivated(_accountSwitchingOverlayView, OverlayView);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void CancelBarButton_Activated(UIBarButtonItem sender)
|
partial void CancelBarButton_Activated(UIBarButtonItem sender)
|
||||||
@@ -88,6 +113,35 @@ namespace Bit.iOS.Autofill
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SubscribeSyncCompleted()
|
||||||
|
{
|
||||||
|
_broadcasterService.Value.Subscribe(nameof(LoginListViewController), message =>
|
||||||
|
{
|
||||||
|
if (message.Command == "syncCompleted" && _alreadyLoadItemsOnce)
|
||||||
|
{
|
||||||
|
DispatchQueue.MainQueue.DispatchAsync(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ((TableSource)TableView.Source).LoadItemsAsync();
|
||||||
|
TableView.ReloadData();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ViewDidUnload()
|
||||||
|
{
|
||||||
|
base.ViewDidUnload();
|
||||||
|
|
||||||
|
_broadcasterService.Value.Unsubscribe(nameof(LoginListViewController));
|
||||||
|
}
|
||||||
|
|
||||||
public void DismissModal()
|
public void DismissModal()
|
||||||
{
|
{
|
||||||
DismissViewController(true, async () =>
|
DismissViewController(true, async () =>
|
||||||
@@ -99,13 +153,11 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
public class TableSource : ExtensionTableSource
|
public class TableSource : ExtensionTableSource
|
||||||
{
|
{
|
||||||
private Context _context;
|
|
||||||
private LoginListViewController _controller;
|
private LoginListViewController _controller;
|
||||||
|
|
||||||
public TableSource(LoginListViewController controller)
|
public TableSource(LoginListViewController controller)
|
||||||
: base(controller.Context, controller)
|
: base(controller.Context, controller)
|
||||||
{
|
{
|
||||||
_context = controller.Context;
|
|
||||||
_controller = controller;
|
_controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
116
src/iOS.Autofill/LoginListViewController.designer.cs
generated
116
src/iOS.Autofill/LoginListViewController.designer.cs
generated
@@ -1,59 +1,89 @@
|
|||||||
// 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 ("LoginListViewController")]
|
[Register ("LoginListViewController")]
|
||||||
partial class LoginListViewController
|
partial class LoginListViewController
|
||||||
{
|
{
|
||||||
[Outlet]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
UIKit.UIBarButtonItem AccountSwitchingBarButton { get; set; }
|
||||||
UIKit.UIBarButtonItem AddBarButton { get; set; }
|
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
[GeneratedCode ("iOS Designer", "1.0")]
|
||||||
UIKit.UIBarButtonItem CancelBarButton { get; set; }
|
UIKit.UIBarButtonItem AddBarButton { get; set; }
|
||||||
|
|
||||||
[Outlet]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
[GeneratedCode ("iOS Designer", "1.0")]
|
||||||
UIKit.UINavigationItem NavItem { get; set; }
|
UIKit.UIBarButtonItem CancelBarButton { get; set; }
|
||||||
|
|
||||||
[Action ("AddBarButton_Activated:")]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
UIKit.UIView MainView { get; set; }
|
||||||
partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
|
|
||||||
|
|
||||||
[Action ("CancelBarButton_Activated:")]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
[GeneratedCode ("iOS Designer", "1.0")]
|
||||||
partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender);
|
UIKit.UINavigationItem NavItem { get; set; }
|
||||||
|
|
||||||
[Action ("SearchBarButton_Activated:")]
|
[Outlet]
|
||||||
[GeneratedCode ("iOS Designer", "1.0")]
|
UIKit.UIView OverlayView { get; set; }
|
||||||
partial void SearchBarButton_Activated (UIKit.UIBarButtonItem sender);
|
|
||||||
|
|
||||||
void ReleaseDesignerOutlets ()
|
[Outlet]
|
||||||
{
|
UIKit.UITableView TableView { get; set; }
|
||||||
if (AddBarButton != null) {
|
|
||||||
AddBarButton.Dispose ();
|
|
||||||
AddBarButton = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CancelBarButton != null) {
|
[Action ("AccountSwitchingBarButton_Activated:")]
|
||||||
CancelBarButton.Dispose ();
|
partial void AccountSwitchingBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
CancelBarButton = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NavItem != null) {
|
[Action ("AddBarButton_Activated:")]
|
||||||
NavItem.Dispose ();
|
partial void AddBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
NavItem = null;
|
|
||||||
}
|
[Action ("CancelBarButton_Activated:")]
|
||||||
}
|
partial void CancelBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
}
|
|
||||||
}
|
[Action ("SearchBarButton_Activated:")]
|
||||||
|
partial void SearchBarButton_Activated (UIKit.UIBarButtonItem sender);
|
||||||
|
|
||||||
|
void ReleaseDesignerOutlets ()
|
||||||
|
{
|
||||||
|
if (AddBarButton != null) {
|
||||||
|
AddBarButton.Dispose ();
|
||||||
|
AddBarButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CancelBarButton != null) {
|
||||||
|
CancelBarButton.Dispose ();
|
||||||
|
CancelBarButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MainView != null) {
|
||||||
|
MainView.Dispose ();
|
||||||
|
MainView = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NavItem != null) {
|
||||||
|
NavItem.Dispose ();
|
||||||
|
NavItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OverlayView != null) {
|
||||||
|
OverlayView.Dispose ();
|
||||||
|
OverlayView = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TableView != null) {
|
||||||
|
TableView.Dispose ();
|
||||||
|
TableView = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AccountSwitchingBarButton != null) {
|
||||||
|
AccountSwitchingBarButton.Dispose ();
|
||||||
|
AccountSwitchingBarButton = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,10 +33,7 @@ namespace Bit.iOS.Autofill
|
|||||||
CancelBarButton.Title = AppResources.Cancel;
|
CancelBarButton.Title = AppResources.Cancel;
|
||||||
SearchBar.Placeholder = AppResources.Search;
|
SearchBar.Placeholder = AppResources.Search;
|
||||||
SearchBar.BackgroundColor = SearchBar.BarTintColor = ThemeHelpers.ListHeaderBackgroundColor;
|
SearchBar.BackgroundColor = SearchBar.BarTintColor = ThemeHelpers.ListHeaderBackgroundColor;
|
||||||
if (!ThemeHelpers.LightTheme)
|
SearchBar.UpdateThemeIfNeeded();
|
||||||
{
|
|
||||||
SearchBar.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
TableView.EstimatedRowHeight = 44;
|
TableView.EstimatedRowHeight = 44;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="43">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="43">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
@@ -9,30 +13,27 @@
|
|||||||
<scene sceneID="42">
|
<scene sceneID="42">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="43" customClass="CredentialProviderViewController" sceneMemberID="viewController">
|
<viewController id="43" customClass="CredentialProviderViewController" sceneMemberID="viewController">
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="40"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="41"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="44">
|
<view key="view" contentMode="scaleToFill" id="44">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="1713">
|
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="1713">
|
||||||
<rect key="frame" x="66" y="316" width="282" height="44"/>
|
<rect key="frame" x="66" y="396" width="282" height="44"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="YG6-2d-qpF"/>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="1713" firstAttribute="centerY" secondItem="44" secondAttribute="centerY" constant="-30" id="1763"/>
|
<constraint firstItem="1713" firstAttribute="centerY" secondItem="44" secondAttribute="centerY" constant="-30" id="1763"/>
|
||||||
<constraint firstItem="1713" firstAttribute="centerX" secondItem="44" secondAttribute="centerX" id="1764"/>
|
<constraint firstItem="1713" firstAttribute="centerX" secondItem="YG6-2d-qpF" secondAttribute="centerX" id="1764"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="Logo" destination="1713" id="name-outlet-1713"/>
|
||||||
<segue destination="oCZ-GQ-aOK" kind="show" identifier="loginListSegue" id="1679"/>
|
<segue destination="oCZ-GQ-aOK" kind="show" identifier="loginListSegue" id="1679"/>
|
||||||
<segue destination="6855" kind="presentation" identifier="lockPasswordSegue" id="9874"/>
|
<segue destination="6855" kind="presentation" identifier="lockPasswordSegue" id="9874"/>
|
||||||
<segue destination="10580" kind="presentation" identifier="setupSegue" modalTransitionStyle="coverVertical" id="11089"/>
|
<segue destination="10580" kind="presentation" identifier="setupSegue" modalTransitionStyle="coverVertical" id="11089"/>
|
||||||
<segue destination="11552" kind="show" identifier="loginSearchSegue" id="12959"/>
|
<segue destination="11552" kind="show" identifier="loginSearchSegue" id="12959"/>
|
||||||
<outlet property="Logo" destination="1713" id="name-outlet-1713"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="45" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="45" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="oCZ-GQ-aOK" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="oCZ-GQ-aOK" sceneMemberID="viewController">
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="8A5-AR-QHS">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="8A5-AR-QHS">
|
||||||
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="1845" sceneMemberID="viewController">
|
<navigationController definesPresentationContext="YES" id="1845" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="1848">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="1848">
|
||||||
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
|
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<textAttributes key="titleTextAttributes">
|
<textAttributes key="titleTextAttributes">
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="2087" customClass="LoginAddViewController" sceneMemberID="viewController">
|
<tableViewController id="2087" customClass="LoginAddViewController" sceneMemberID="viewController">
|
||||||
<tableView key="view" opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" allowsSelection="NO" rowHeight="50" sectionHeaderHeight="22" sectionFooterHeight="22" id="2088">
|
<tableView key="view" opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" allowsSelection="NO" rowHeight="50" sectionHeaderHeight="22" sectionFooterHeight="22" id="2088">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="sectionIndexBackgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="sectionIndexBackgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
@@ -125,50 +126,79 @@
|
|||||||
<!--Logins-->
|
<!--Logins-->
|
||||||
<scene sceneID="2303">
|
<scene sceneID="2303">
|
||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="2304" customClass="LoginListViewController" sceneMemberID="viewController">
|
<viewController id="2304" customClass="LoginListViewController" sceneMemberID="viewController">
|
||||||
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="2305">
|
<view key="view" contentMode="scaleToFill" id="q9o-3n-3xL">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<subviews>
|
||||||
<prototypes>
|
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="2305">
|
||||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="3763" detailTextLabel="3764" rowHeight="44" style="IBUITableViewCellStyleSubtitle" id="3761">
|
<rect key="frame" x="0.0" y="0.0" width="414" height="737"/>
|
||||||
<rect key="frame" x="0.0" y="22" width="414" height="44"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<prototypes>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3761" id="3762">
|
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="3763" detailTextLabel="3764" rowHeight="44" style="IBUITableViewCellStyleSubtitle" id="3761">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
<rect key="frame" x="0.0" y="44.5" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="3761" id="3762">
|
||||||
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3763">
|
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||||
<rect key="frame" x="20" y="4" width="35" height="21.5"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="18"/>
|
<subviews>
|
||||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3763">
|
||||||
<nil key="highlightedColor"/>
|
<rect key="frame" x="20" y="4" width="35" height="21.5"/>
|
||||||
</label>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3764">
|
<fontDescription key="fontDescription" type="system" pointSize="18"/>
|
||||||
<rect key="frame" x="20" y="25.5" width="44" height="14.5"/>
|
<color key="textColor" systemColor="darkTextColor"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<nil key="highlightedColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
</label>
|
||||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
<label opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3764">
|
||||||
<nil key="highlightedColor"/>
|
<rect key="frame" x="20" y="25.5" width="44" height="14.5"/>
|
||||||
</label>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</subviews>
|
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||||
</tableViewCellContentView>
|
<color key="textColor" systemColor="darkTextColor"/>
|
||||||
</tableViewCell>
|
<nil key="highlightedColor"/>
|
||||||
</prototypes>
|
</label>
|
||||||
<connections>
|
</subviews>
|
||||||
<outlet property="dataSource" destination="2304" id="2306"/>
|
</tableViewCellContentView>
|
||||||
<outlet property="delegate" destination="2304" id="2307"/>
|
</tableViewCell>
|
||||||
</connections>
|
</prototypes>
|
||||||
</tableView>
|
<connections>
|
||||||
|
<outlet property="dataSource" destination="2304" id="2306"/>
|
||||||
|
<outlet property="delegate" destination="2304" id="2307"/>
|
||||||
|
</connections>
|
||||||
|
</tableView>
|
||||||
|
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Tq0-Ep-tHr" userLabel="OverlayView">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="737"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="BQW-dG-XMM"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Tq0-Ep-tHr" firstAttribute="leading" secondItem="BQW-dG-XMM" secondAttribute="leading" id="4wL-FF-CVk"/>
|
||||||
|
<constraint firstItem="BQW-dG-XMM" firstAttribute="trailing" secondItem="Tq0-Ep-tHr" secondAttribute="trailing" id="5BV-0y-vU1"/>
|
||||||
|
<constraint firstItem="BQW-dG-XMM" firstAttribute="bottom" secondItem="2305" secondAttribute="bottom" id="6EB-rh-lLS"/>
|
||||||
|
<constraint firstItem="Tq0-Ep-tHr" firstAttribute="top" secondItem="BQW-dG-XMM" secondAttribute="top" id="eT6-Bv-JaR"/>
|
||||||
|
<constraint firstItem="BQW-dG-XMM" firstAttribute="trailing" secondItem="2305" secondAttribute="trailing" id="ofJ-fL-adF"/>
|
||||||
|
<constraint firstItem="BQW-dG-XMM" firstAttribute="bottom" secondItem="Tq0-Ep-tHr" secondAttribute="bottom" id="pBa-o1-Mtx"/>
|
||||||
|
<constraint firstItem="2305" firstAttribute="top" secondItem="BQW-dG-XMM" secondAttribute="top" id="pGe-1e-B4s"/>
|
||||||
|
<constraint firstItem="2305" firstAttribute="leading" secondItem="BQW-dG-XMM" secondAttribute="leading" id="xfQ-VQ-yWe"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationItem key="navigationItem" title="Logins" id="3734">
|
<navigationItem key="navigationItem" title="Logins" id="3734">
|
||||||
<barButtonItem key="leftBarButtonItem" title="Cancel" id="3735">
|
<leftBarButtonItems>
|
||||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<barButtonItem title="Cancel" id="3735">
|
||||||
<connections>
|
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<action selector="CancelBarButton_Activated:" destination="2304" id="3750"/>
|
<connections>
|
||||||
</connections>
|
<action selector="CancelBarButton_Activated:" destination="2304" id="3750"/>
|
||||||
</barButtonItem>
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
<barButtonItem title="Account" image="person.2" catalog="system" style="plain" id="I0b-et-FGw" userLabel="Accoutn Switching Button">
|
||||||
|
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="AccountSwitchingBarButton_Activated:" destination="2304" id="dZn-bd-bC6"/>
|
||||||
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
</leftBarButtonItems>
|
||||||
<rightBarButtonItems>
|
<rightBarButtonItems>
|
||||||
<barButtonItem systemItem="add" id="3736">
|
<barButtonItem systemItem="add" id="3736">
|
||||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
@@ -186,13 +216,17 @@
|
|||||||
</navigationItem>
|
</navigationItem>
|
||||||
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
|
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="AccountSwitchingBarButton" destination="I0b-et-FGw" id="KZj-EO-7wd"/>
|
||||||
<outlet property="AddBarButton" destination="3736" id="name-outlet-3736"/>
|
<outlet property="AddBarButton" destination="3736" id="name-outlet-3736"/>
|
||||||
<outlet property="CancelBarButton" destination="3735" id="name-outlet-3735"/>
|
<outlet property="CancelBarButton" destination="3735" id="name-outlet-3735"/>
|
||||||
|
<outlet property="MainView" destination="q9o-3n-3xL" id="gjJ-12-71Q"/>
|
||||||
<outlet property="NavItem" destination="3734" id="name-outlet-3734"/>
|
<outlet property="NavItem" destination="3734" id="name-outlet-3734"/>
|
||||||
|
<outlet property="OverlayView" destination="Tq0-Ep-tHr" id="igj-R2-gXJ"/>
|
||||||
|
<outlet property="TableView" destination="2305" id="aUe-Uz-iIb"/>
|
||||||
<segue destination="1845" kind="presentation" identifier="loginAddSegue" modalPresentationStyle="fullScreen" modalTransitionStyle="coverVertical" id="3731"/>
|
<segue destination="1845" kind="presentation" identifier="loginAddSegue" modalPresentationStyle="fullScreen" modalTransitionStyle="coverVertical" id="3731"/>
|
||||||
<segue destination="11552" kind="show" identifier="loginSearchFromListSegue" id="12574"/>
|
<segue destination="11552" kind="show" identifier="loginSearchFromListSegue" id="12574"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="2310" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="2310" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1157" y="566"/>
|
<point key="canvasLocation" x="1157" y="566"/>
|
||||||
@@ -202,7 +236,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="4574" sceneMemberID="viewController">
|
<navigationController definesPresentationContext="YES" id="4574" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="4577">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="4577">
|
||||||
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
|
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
<color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="0.0" green="0.52549019607843139" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
@@ -222,37 +256,34 @@
|
|||||||
<scene sceneID="4579">
|
<scene sceneID="4579">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="4576" customClass="PasswordGeneratorViewController" sceneMemberID="viewController">
|
<viewController id="4576" customClass="PasswordGeneratorViewController" sceneMemberID="viewController">
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="4571"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="4572"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="4930">
|
<view key="view" contentMode="scaleToFill" id="4930">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<containerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4933">
|
<containerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4933">
|
||||||
<rect key="frame" x="0.0" y="160.5" width="414" height="575.5"/>
|
<rect key="frame" x="0.0" y="90.5" width="414" height="683.5"/>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<connections>
|
<connections>
|
||||||
<segue destination="4912" kind="embed" id="6480"/>
|
<segue destination="4912" kind="embed" id="6480"/>
|
||||||
</connections>
|
</connections>
|
||||||
</containerView>
|
</containerView>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Label" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="4940">
|
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Label" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="4940">
|
||||||
<rect key="frame" x="15" y="105" width="384" height="20.5"/>
|
<rect key="frame" x="15" y="35" width="384" height="20.5"/>
|
||||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
<color key="textColor" systemColor="darkTextColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="Kvt-lG-wyu"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailing" secondItem="4933" secondAttribute="trailing" id="6484"/>
|
<constraint firstItem="Kvt-lG-wyu" firstAttribute="trailing" secondItem="4933" secondAttribute="trailing" id="6484"/>
|
||||||
<constraint firstItem="4933" firstAttribute="top" secondItem="4940" secondAttribute="bottom" constant="35" id="6485"/>
|
<constraint firstItem="4933" firstAttribute="top" secondItem="4940" secondAttribute="bottom" constant="35" id="6485"/>
|
||||||
<constraint firstItem="4933" firstAttribute="leading" secondItem="4930" secondAttribute="leading" id="6486"/>
|
<constraint firstItem="4933" firstAttribute="leading" secondItem="Kvt-lG-wyu" secondAttribute="leading" id="6486"/>
|
||||||
<constraint firstItem="4940" firstAttribute="leading" secondItem="4930" secondAttribute="leading" constant="15" id="6487"/>
|
<constraint firstItem="4940" firstAttribute="leading" secondItem="Kvt-lG-wyu" secondAttribute="leading" constant="15" id="6487"/>
|
||||||
<constraint firstItem="4940" firstAttribute="top" secondItem="4571" secondAttribute="bottom" constant="35" id="6488"/>
|
<constraint firstItem="4940" firstAttribute="top" secondItem="Kvt-lG-wyu" secondAttribute="top" constant="35" id="6488"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="4940" secondAttribute="trailing" constant="15" id="6489"/>
|
<constraint firstItem="Kvt-lG-wyu" firstAttribute="trailing" secondItem="4940" secondAttribute="trailing" constant="15" id="6489"/>
|
||||||
<constraint firstItem="4572" firstAttribute="top" secondItem="4933" secondAttribute="bottom" id="6490"/>
|
<constraint firstItem="Kvt-lG-wyu" firstAttribute="bottom" secondItem="4933" secondAttribute="bottom" id="6490"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<navigationItem key="navigationItem" title="Generate Password" id="4580">
|
<navigationItem key="navigationItem" title="Generate Password" id="4580">
|
||||||
@@ -287,7 +318,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="4912" sceneMemberID="viewController">
|
<tableViewController id="4912" sceneMemberID="viewController">
|
||||||
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="4913">
|
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="4913">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="575.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="683.5"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<connections>
|
<connections>
|
||||||
@@ -301,15 +332,11 @@
|
|||||||
<point key="canvasLocation" x="4708" y="-194"/>
|
<point key="canvasLocation" x="4708" y="-194"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Navigation Controller-->
|
<!--Navigation Controller-->
|
||||||
<!--Verify Fingerprint-->
|
|
||||||
<!--Verify PIN-->
|
|
||||||
<!--Navigation Controller-->
|
|
||||||
<!--Navigation Controller-->
|
|
||||||
<scene sceneID="6854">
|
<scene sceneID="6854">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController definesPresentationContext="YES" id="6855" sceneMemberID="viewController">
|
<navigationController definesPresentationContext="YES" id="6855" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="6857">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="6857">
|
||||||
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
@@ -318,88 +345,139 @@
|
|||||||
</textAttributes>
|
</textAttributes>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
<connections>
|
<connections>
|
||||||
<segue destination="7413" kind="relationship" relationship="rootViewController" id="8266"/>
|
<segue destination="cn5-F4-59n" kind="relationship" relationship="rootViewController" id="Q23-VB-h61"/>
|
||||||
</connections>
|
</connections>
|
||||||
</navigationController>
|
</navigationController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="6858" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="6858" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="390" y="1407"/>
|
<point key="canvasLocation" x="120" y="1407"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Verify Master Password-->
|
<!--Verify Master Password-->
|
||||||
<scene sceneID="7412">
|
<scene sceneID="5CE-bQ-Rfq">
|
||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="7413" customClass="LockPasswordViewController" sceneMemberID="viewController">
|
<viewController id="cn5-F4-59n" customClass="LockPasswordViewController" sceneMemberID="viewController">
|
||||||
<tableView key="view" opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="7414">
|
<view key="view" contentMode="scaleToFill" id="0qM-RN-J2i" userLabel="Main View">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<subviews>
|
||||||
<connections>
|
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="FcI-Ph-m9e">
|
||||||
<outlet property="dataSource" destination="7413" id="7415"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
||||||
<outlet property="delegate" destination="7413" id="7416"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</connections>
|
<prototypes>
|
||||||
</tableView>
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="oQZ-wW-5uB">
|
||||||
<navigationItem key="navigationItem" title="Verify Master Password" id="8265">
|
<rect key="frame" x="0.0" y="49.5" width="414" height="44"/>
|
||||||
<barButtonItem key="leftBarButtonItem" title="Cancel" id="8268">
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oQZ-wW-5uB" id="SUk-LD-cXo">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="1cB-yn-7ii">
|
||||||
|
<rect key="frame" x="0.0" y="93.5" width="414" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="1cB-yn-7ii" id="xFt-Jc-feN">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" id="Fzo-Cj-aM4">
|
||||||
|
<rect key="frame" x="0.0" y="137.5" width="414" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Fzo-Cj-aM4" id="vSb-0G-Bic">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
</tableViewCell>
|
||||||
|
</prototypes>
|
||||||
|
<sections/>
|
||||||
|
</tableView>
|
||||||
|
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sDX-BN-qLw" userLabel="OverlayView">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="uMZ-kT-NSt"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="FcI-Ph-m9e" firstAttribute="top" secondItem="uMZ-kT-NSt" secondAttribute="top" id="68e-VU-o4s"/>
|
||||||
|
<constraint firstItem="uMZ-kT-NSt" firstAttribute="bottom" secondItem="FcI-Ph-m9e" secondAttribute="bottom" id="7UM-br-sxz"/>
|
||||||
|
<constraint firstItem="uMZ-kT-NSt" firstAttribute="bottom" secondItem="sDX-BN-qLw" secondAttribute="bottom" id="QGT-ck-8TL"/>
|
||||||
|
<constraint firstItem="sDX-BN-qLw" firstAttribute="leading" secondItem="uMZ-kT-NSt" secondAttribute="leading" id="aVW-Nw-awb"/>
|
||||||
|
<constraint firstItem="uMZ-kT-NSt" firstAttribute="trailing" secondItem="sDX-BN-qLw" secondAttribute="trailing" id="f5n-9J-y3c"/>
|
||||||
|
<constraint firstItem="FcI-Ph-m9e" firstAttribute="leading" secondItem="uMZ-kT-NSt" secondAttribute="leading" id="sKL-iw-CWd"/>
|
||||||
|
<constraint firstItem="uMZ-kT-NSt" firstAttribute="trailing" secondItem="FcI-Ph-m9e" secondAttribute="trailing" id="xgg-of-dPl"/>
|
||||||
|
<constraint firstItem="sDX-BN-qLw" firstAttribute="top" secondItem="uMZ-kT-NSt" secondAttribute="top" id="yO9-oz-zRn"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<navigationItem key="navigationItem" title="Verify Master Password" id="NCb-RV-Vqq">
|
||||||
|
<leftBarButtonItems>
|
||||||
|
<barButtonItem title="Cancel" id="Xoh-Zv-hhd">
|
||||||
|
<connections>
|
||||||
|
<action selector="CancelButton_Activated:" destination="cn5-F4-59n" id="1gM-mE-phn"/>
|
||||||
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
<barButtonItem title="Account" image="person.2" catalog="system" style="plain" id="nwd-aM-kFD" userLabel="Accoutn Switching Button">
|
||||||
|
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="AccountSwitchingBarButton_Activated:" destination="cn5-F4-59n" id="vVZ-IM-rkU"/>
|
||||||
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
</leftBarButtonItems>
|
||||||
|
<barButtonItem key="rightBarButtonItem" title="Submit" id="gju-yD-EmI">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="CancelButton_Activated:" destination="7413" id="8287"/>
|
<action selector="SubmitButton_Activated:" destination="cn5-F4-59n" id="O1U-fk-BDh"/>
|
||||||
</connections>
|
|
||||||
</barButtonItem>
|
|
||||||
<barButtonItem key="rightBarButtonItem" title="Submit" id="8269">
|
|
||||||
<connections>
|
|
||||||
<action selector="SubmitButton_Activated:" destination="7413" id="8288"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="CancelButton" destination="8268" id="name-outlet-8268"/>
|
<outlet property="AccountSwitchingBarButton" destination="nwd-aM-kFD" id="T8F-CN-2il"/>
|
||||||
<outlet property="MainTableView" destination="7414" id="name-outlet-7414"/>
|
<outlet property="CancelButton" destination="Xoh-Zv-hhd" id="mwi-4K-maj"/>
|
||||||
<outlet property="NavItem" destination="8265" id="name-outlet-8265"/>
|
<outlet property="MainTableView" destination="FcI-Ph-m9e" id="Ybv-5r-VGA"/>
|
||||||
<outlet property="SubmitButton" destination="8269" id="name-outlet-8269"/>
|
<outlet property="NavItem" destination="NCb-RV-Vqq" id="L9b-At-x0A"/>
|
||||||
|
<outlet property="OverlayView" destination="sDX-BN-qLw" id="veu-q4-CeW"/>
|
||||||
|
<outlet property="SubmitButton" destination="gju-yD-EmI" id="mjg-yP-09M"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="7419" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="vNI-gq-ubp" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="955" y="1407"/>
|
<point key="canvasLocation" x="836" y="1407"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Setup View Controller-->
|
<!--Setup View Controller-->
|
||||||
<scene sceneID="10573">
|
<scene sceneID="10573">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="10570" customClass="SetupViewController" sceneMemberID="viewController">
|
<viewController id="10570" customClass="SetupViewController" sceneMemberID="viewController">
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="10565"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="10566"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="10575">
|
<view key="view" contentMode="scaleToFill" id="10575">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Extension Activated!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11092">
|
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Extension Activated!" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11092">
|
||||||
<rect key="frame" x="15" y="100" width="384" height="20.5"/>
|
<rect key="frame" x="15" y="30" width="384" height="20.5"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
<color key="textColor" systemColor="darkTextColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11093">
|
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" misplaced="YES" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="11093">
|
||||||
<rect key="frame" x="15" y="134.5" width="570" height="41"/>
|
<rect key="frame" x="15" y="134.5" width="570" height="41"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
<color key="textColor" systemColor="darkTextColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="check.png" translatesAutoresizingMaskIntoConstraints="NO" id="11094">
|
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="check.png" translatesAutoresizingMaskIntoConstraints="NO" id="11094">
|
||||||
<rect key="frame" x="255" y="205.5" width="90" height="90"/>
|
<rect key="frame" x="255" y="205.5" width="90" height="90"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="hcg-mr-Ilx"/>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="11092" firstAttribute="leading" secondItem="10575" secondAttribute="leading" constant="15" id="11114"/>
|
<constraint firstItem="11092" firstAttribute="leading" secondItem="hcg-mr-Ilx" secondAttribute="leading" constant="15" id="11114"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="11092" secondAttribute="trailing" constant="15" id="11115"/>
|
<constraint firstItem="hcg-mr-Ilx" firstAttribute="trailing" secondItem="11092" secondAttribute="trailing" constant="15" id="11115"/>
|
||||||
<constraint firstItem="11092" firstAttribute="top" secondItem="10565" secondAttribute="bottom" constant="30" id="11116"/>
|
<constraint firstItem="11092" firstAttribute="top" secondItem="hcg-mr-Ilx" secondAttribute="top" constant="30" id="11116"/>
|
||||||
<constraint firstItem="11093" firstAttribute="leading" secondItem="10575" secondAttribute="leading" constant="15" id="11119"/>
|
<constraint firstItem="11093" firstAttribute="leading" secondItem="hcg-mr-Ilx" secondAttribute="leading" constant="15" id="11119"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="11093" secondAttribute="trailing" constant="15" id="11120"/>
|
<constraint firstItem="hcg-mr-Ilx" firstAttribute="trailing" secondItem="11093" secondAttribute="trailing" constant="15" id="11120"/>
|
||||||
<constraint firstItem="11093" firstAttribute="top" secondItem="11092" secondAttribute="bottom" constant="20" id="11121"/>
|
<constraint firstItem="11093" firstAttribute="top" secondItem="11092" secondAttribute="bottom" constant="20" id="11121"/>
|
||||||
<constraint firstItem="11094" firstAttribute="centerX" secondItem="10575" secondAttribute="centerX" id="11122"/>
|
<constraint firstItem="11094" firstAttribute="centerX" secondItem="hcg-mr-Ilx" secondAttribute="centerX" id="11122"/>
|
||||||
<constraint firstItem="11094" firstAttribute="top" secondItem="11093" secondAttribute="bottom" constant="30" id="11123"/>
|
<constraint firstItem="11094" firstAttribute="top" secondItem="11093" secondAttribute="bottom" constant="30" id="11123"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
@@ -429,7 +507,7 @@
|
|||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="10580" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="10580" sceneMemberID="viewController">
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="10583">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="10583">
|
||||||
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<textAttributes key="titleTextAttributes">
|
<textAttributes key="titleTextAttributes">
|
||||||
@@ -450,7 +528,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="11543" customClass="LoginSearchViewController" sceneMemberID="viewController">
|
<tableViewController id="11543" customClass="LoginSearchViewController" sceneMemberID="viewController">
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="11545">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="11545">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="786"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||||
<searchBar key="tableHeaderView" contentMode="redraw" id="13084">
|
<searchBar key="tableHeaderView" contentMode="redraw" id="13084">
|
||||||
@@ -463,10 +541,10 @@
|
|||||||
</searchBar>
|
</searchBar>
|
||||||
<prototypes>
|
<prototypes>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="11548">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" id="11548">
|
||||||
<rect key="frame" x="0.0" y="72" width="414" height="44"/>
|
<rect key="frame" x="0.0" y="88.5" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="11548" id="11549">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="11548" id="11549">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
@@ -506,7 +584,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<navigationController id="11552" sceneMemberID="viewController">
|
<navigationController id="11552" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="11554">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="11554">
|
||||||
<rect key="frame" x="0.0" y="20" width="414" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="barTintColor" red="0.23529411764705882" green="0.55294117647058827" blue="0.73725490196078436" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<textAttributes key="titleTextAttributes">
|
<textAttributes key="titleTextAttributes">
|
||||||
@@ -522,8 +600,19 @@
|
|||||||
<point key="canvasLocation" x="1920" y="908"/>
|
<point key="canvasLocation" x="1920" y="908"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
|
<inferredMetricsTieBreakers>
|
||||||
|
<segue reference="12574"/>
|
||||||
|
<segue reference="3731"/>
|
||||||
|
</inferredMetricsTieBreakers>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="check.png" width="90" height="90"/>
|
<image name="check.png" width="90" height="90"/>
|
||||||
<image name="logo.png" width="282" height="44"/>
|
<image name="logo.png" width="282" height="44"/>
|
||||||
|
<image name="person.2" catalog="system" width="128" height="81"/>
|
||||||
|
<systemColor name="darkTextColor">
|
||||||
|
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Bit.iOS.Autofill.Utilities
|
|||||||
{
|
{
|
||||||
public async static Task TableRowSelectedAsync(UITableView tableView, NSIndexPath indexPath,
|
public async static Task TableRowSelectedAsync(UITableView tableView, NSIndexPath indexPath,
|
||||||
ExtensionTableSource tableSource, CredentialProviderViewController cpViewController,
|
ExtensionTableSource tableSource, CredentialProviderViewController cpViewController,
|
||||||
UITableViewController controller, IPasswordRepromptService passwordRepromptService,
|
UIViewController controller, IPasswordRepromptService passwordRepromptService,
|
||||||
string loginAddSegue)
|
string loginAddSegue)
|
||||||
{
|
{
|
||||||
tableView.DeselectRow(indexPath, true);
|
tableView.DeselectRow(indexPath, true);
|
||||||
|
|||||||
509
src/iOS.Core/Controllers/BaseLockPasswordViewController.cs
Normal file
509
src/iOS.Core/Controllers/BaseLockPasswordViewController.cs
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
using System;
|
||||||
|
using UIKit;
|
||||||
|
using Foundation;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Bit.Core;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Controllers
|
||||||
|
{
|
||||||
|
public abstract class BaseLockPasswordViewController : ExtendedUIViewController
|
||||||
|
{
|
||||||
|
private IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
private ICryptoService _cryptoService;
|
||||||
|
private IDeviceActionService _deviceActionService;
|
||||||
|
private IStateService _stateService;
|
||||||
|
private IStorageService _secureStorageService;
|
||||||
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
|
private IBiometricService _biometricService;
|
||||||
|
private IKeyConnectorService _keyConnectorService;
|
||||||
|
private bool _isPinProtected;
|
||||||
|
private bool _isPinProtectedWithKey;
|
||||||
|
private bool _pinLock;
|
||||||
|
private bool _biometricLock;
|
||||||
|
private bool _biometricIntegrityValid = true;
|
||||||
|
private bool _passwordReprompt = false;
|
||||||
|
private bool _usesKeyConnector;
|
||||||
|
private bool _biometricUnlockOnly = false;
|
||||||
|
|
||||||
|
protected bool autofillExtension = false;
|
||||||
|
|
||||||
|
public BaseLockPasswordViewController(IntPtr handle)
|
||||||
|
: base(handle)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public abstract UINavigationItem BaseNavItem { get; }
|
||||||
|
public abstract UIBarButtonItem BaseCancelButton { get; }
|
||||||
|
public abstract UIBarButtonItem BaseSubmitButton { get; }
|
||||||
|
public abstract Action Success { get; }
|
||||||
|
public abstract Action Cancel { get; }
|
||||||
|
|
||||||
|
public FormEntryTableViewCell MasterPasswordCell { get; set; } = new FormEntryTableViewCell(
|
||||||
|
AppResources.MasterPassword, useButton: true);
|
||||||
|
|
||||||
|
public string BiometricIntegrityKey { get; set; }
|
||||||
|
|
||||||
|
public UITableViewCell BiometricCell
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var cell = new UITableViewCell();
|
||||||
|
cell.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
|
if (_biometricIntegrityValid)
|
||||||
|
{
|
||||||
|
var biometricButtonText = _deviceActionService.SupportsFaceBiometric() ?
|
||||||
|
AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock;
|
||||||
|
cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor;
|
||||||
|
cell.TextLabel.Text = biometricButtonText;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
||||||
|
cell.TextLabel.Font = ThemeHelpers.GetDangerFont();
|
||||||
|
cell.TextLabel.Lines = 0;
|
||||||
|
cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
|
||||||
|
cell.TextLabel.Text = AppResources.BiometricInvalidatedExtension;
|
||||||
|
}
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract UITableView TableView { get; }
|
||||||
|
|
||||||
|
public override async void ViewDidLoad()
|
||||||
|
{
|
||||||
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
|
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
||||||
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
|
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
||||||
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
|
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||||
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
|
|
||||||
|
// We re-use the lock screen for autofill extension to verify master password
|
||||||
|
// when trying to access protected items.
|
||||||
|
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
|
||||||
|
{
|
||||||
|
_passwordReprompt = true;
|
||||||
|
_isPinProtected = false;
|
||||||
|
_isPinProtectedWithKey = false;
|
||||||
|
_pinLock = false;
|
||||||
|
_biometricLock = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
|
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||||
|
_isPinProtectedWithKey;
|
||||||
|
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||||
|
await _cryptoService.HasKeyAsync();
|
||||||
|
_biometricIntegrityValid = await _biometricService.ValidateIntegrityAsync(BiometricIntegrityKey);
|
||||||
|
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
|
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pinLock)
|
||||||
|
{
|
||||||
|
BaseNavItem.Title = AppResources.VerifyPIN;
|
||||||
|
}
|
||||||
|
else if (_usesKeyConnector)
|
||||||
|
{
|
||||||
|
BaseNavItem.Title = AppResources.UnlockVault;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BaseNavItem.Title = AppResources.VerifyMasterPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseCancelButton.Title = AppResources.Cancel;
|
||||||
|
|
||||||
|
if (_biometricUnlockOnly)
|
||||||
|
{
|
||||||
|
BaseSubmitButton.Title = null;
|
||||||
|
BaseSubmitButton.Enabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BaseSubmitButton.Title = AppResources.Submit;
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptor = UIFontDescriptor.PreferredBody;
|
||||||
|
|
||||||
|
if (!_biometricUnlockOnly)
|
||||||
|
{
|
||||||
|
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword;
|
||||||
|
MasterPasswordCell.TextField.SecureTextEntry = true;
|
||||||
|
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
|
||||||
|
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
|
||||||
|
{
|
||||||
|
CheckPasswordAsync().GetAwaiter().GetResult();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (_pinLock)
|
||||||
|
{
|
||||||
|
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
|
||||||
|
}
|
||||||
|
MasterPasswordCell.Button.TitleLabel.Font = UIFont.FromName("bwi-font", 28f);
|
||||||
|
MasterPasswordCell.Button.SetTitle(BitwardenIcons.Eye, UIControlState.Normal);
|
||||||
|
MasterPasswordCell.Button.TouchUpInside += (sender, e) =>
|
||||||
|
{
|
||||||
|
MasterPasswordCell.TextField.SecureTextEntry = !MasterPasswordCell.TextField.SecureTextEntry;
|
||||||
|
MasterPasswordCell.Button.SetTitle(MasterPasswordCell.TextField.SecureTextEntry ? BitwardenIcons.Eye : BitwardenIcons.EyeSlash, UIControlState.Normal);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TableView != null)
|
||||||
|
{
|
||||||
|
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
|
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
|
TableView.EstimatedRowHeight = 70;
|
||||||
|
TableView.Source = new TableSource(this);
|
||||||
|
TableView.AllowsSelection = true;
|
||||||
|
|
||||||
|
base.ViewDidLoad();
|
||||||
|
|
||||||
|
if (_biometricLock)
|
||||||
|
{
|
||||||
|
if (!_biometricIntegrityValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tasks = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
NSRunLoop.Main.BeginInvokeOnMainThread(async () => await PromptBiometricAsync());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async void ViewDidAppear(bool animated)
|
||||||
|
{
|
||||||
|
base.ViewDidAppear(animated);
|
||||||
|
|
||||||
|
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||||
|
if (_usesKeyConnector)
|
||||||
|
{
|
||||||
|
if (!(_pinLock || _biometricLock) ||
|
||||||
|
(_biometricLock && !_biometricIntegrityValid))
|
||||||
|
{
|
||||||
|
PromptSSO();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!_biometricLock || !_biometricIntegrityValid)
|
||||||
|
{
|
||||||
|
MasterPasswordCell.TextField.BecomeFirstResponder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task CheckPasswordAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(MasterPasswordCell.TextField.Text))
|
||||||
|
{
|
||||||
|
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||||
|
string.Format(AppResources.ValidationFieldRequired,
|
||||||
|
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
|
||||||
|
AppResources.Ok);
|
||||||
|
PresentViewController(alert, true, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var email = await _stateService.GetEmailAsync();
|
||||||
|
var kdf = await _stateService.GetKdfTypeAsync();
|
||||||
|
var kdfIterations = await _stateService.GetKdfIterationsAsync();
|
||||||
|
var inputtedValue = MasterPasswordCell.TextField.Text;
|
||||||
|
|
||||||
|
if (_pinLock)
|
||||||
|
{
|
||||||
|
var failed = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_isPinProtected)
|
||||||
|
{
|
||||||
|
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||||
|
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
|
||||||
|
await _stateService.GetPinProtectedKeyAsync());
|
||||||
|
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||||
|
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||||
|
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||||
|
failed = decPin != inputtedValue;
|
||||||
|
if (!failed)
|
||||||
|
{
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
await SetKeyAndContinueAsync(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||||
|
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
|
||||||
|
failed = false;
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
await SetKeyAndContinueAsync(key2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
if (failed)
|
||||||
|
{
|
||||||
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
|
if (invalidUnlockAttempts >= 5)
|
||||||
|
{
|
||||||
|
await LogOutAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InvalidValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations);
|
||||||
|
|
||||||
|
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||||
|
if (storedKeyHash == null)
|
||||||
|
{
|
||||||
|
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
||||||
|
if (key2.KeyB64 == oldKey)
|
||||||
|
{
|
||||||
|
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
|
||||||
|
await _secureStorageService.RemoveAsync("oldKey");
|
||||||
|
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
|
||||||
|
if (passwordValid)
|
||||||
|
{
|
||||||
|
if (_isPinProtected)
|
||||||
|
{
|
||||||
|
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||||
|
var encKey = await _cryptoService.GetEncKeyAsync(key2);
|
||||||
|
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||||
|
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
|
||||||
|
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
|
||||||
|
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
|
||||||
|
}
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
await SetKeyAndContinueAsync(key2, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
|
if (invalidUnlockAttempts >= 5)
|
||||||
|
{
|
||||||
|
await LogOutAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InvalidValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PromptBiometricAsync()
|
||||||
|
{
|
||||||
|
if (!_biometricLock || !_biometricIntegrityValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
|
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||||
|
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||||
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
DoContinue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PromptSSO()
|
||||||
|
{
|
||||||
|
var loginPage = new LoginSsoPage();
|
||||||
|
var app = new App.App(new AppOptions { IosExtension = true });
|
||||||
|
ThemeManager.SetTheme(app.Resources);
|
||||||
|
ThemeManager.ApplyResourcesToPage(loginPage);
|
||||||
|
if (loginPage.BindingContext is LoginSsoPageViewModel vm)
|
||||||
|
{
|
||||||
|
vm.SsoAuthSuccessAction = () => DoContinue();
|
||||||
|
vm.CloseAction = Cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationPage = new NavigationPage(loginPage);
|
||||||
|
var loginController = navigationPage.CreateViewController();
|
||||||
|
loginController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
|
PresentViewController(loginController, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false)
|
||||||
|
{
|
||||||
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
|
if (!hasKey)
|
||||||
|
{
|
||||||
|
await _cryptoService.SetKeyAsync(key);
|
||||||
|
}
|
||||||
|
DoContinue(masterPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void DoContinue(bool masterPassword = false)
|
||||||
|
{
|
||||||
|
if (masterPassword)
|
||||||
|
{
|
||||||
|
await _stateService.SetPasswordVerifiedAutofillAsync(true);
|
||||||
|
}
|
||||||
|
await EnableBiometricsIfNeeded();
|
||||||
|
await _stateService.SetBiometricLockedAsync(false);
|
||||||
|
MasterPasswordCell.TextField.ResignFirstResponder();
|
||||||
|
Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnableBiometricsIfNeeded()
|
||||||
|
{
|
||||||
|
// Re-enable biometrics if initial use
|
||||||
|
if (_biometricLock & !_biometricIntegrityValid)
|
||||||
|
{
|
||||||
|
await _biometricService.SetupBiometricAsync(BiometricIntegrityKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvalidValue()
|
||||||
|
{
|
||||||
|
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||||
|
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
||||||
|
AppResources.Ok, (a) =>
|
||||||
|
{
|
||||||
|
|
||||||
|
MasterPasswordCell.TextField.Text = string.Empty;
|
||||||
|
MasterPasswordCell.TextField.BecomeFirstResponder();
|
||||||
|
});
|
||||||
|
PresentViewController(alert, true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LogOutAsync()
|
||||||
|
{
|
||||||
|
await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync());
|
||||||
|
var authService = ServiceContainer.Resolve<IAuthService>("authService");
|
||||||
|
authService.LogOut(() =>
|
||||||
|
{
|
||||||
|
Cancel?.Invoke();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TableSource : ExtendedUITableViewSource
|
||||||
|
{
|
||||||
|
private readonly BaseLockPasswordViewController _controller;
|
||||||
|
|
||||||
|
public TableSource(BaseLockPasswordViewController controller)
|
||||||
|
{
|
||||||
|
_controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
if (indexPath.Section == 0)
|
||||||
|
{
|
||||||
|
if (indexPath.Row == 0)
|
||||||
|
{
|
||||||
|
if (_controller._biometricUnlockOnly)
|
||||||
|
{
|
||||||
|
return _controller.BiometricCell;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _controller.MasterPasswordCell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (indexPath.Section == 1)
|
||||||
|
{
|
||||||
|
if (indexPath.Row == 0)
|
||||||
|
{
|
||||||
|
if (_controller._passwordReprompt)
|
||||||
|
{
|
||||||
|
var cell = new ExtendedUITableViewCell();
|
||||||
|
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
||||||
|
cell.TextLabel.Font = ThemeHelpers.GetDangerFont();
|
||||||
|
cell.TextLabel.Lines = 0;
|
||||||
|
cell.TextLabel.LineBreakMode = UILineBreakMode.WordWrap;
|
||||||
|
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
else if (!_controller._biometricUnlockOnly)
|
||||||
|
{
|
||||||
|
return _controller.BiometricCell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ExtendedUITableViewCell();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
return UITableView.AutomaticDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nint NumberOfSections(UITableView tableView)
|
||||||
|
{
|
||||||
|
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
|
||||||
|
_controller._passwordReprompt
|
||||||
|
? 2
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nint RowsInSection(UITableView tableview, nint section)
|
||||||
|
{
|
||||||
|
if (section <= 1)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
return section == 1 ? 0.00001f : UITableView.AutomaticDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TitleForHeader(UITableView tableView, nint section)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
tableView.DeselectRow(indexPath, true);
|
||||||
|
tableView.EndEditing(true);
|
||||||
|
if (indexPath.Row == 0 &&
|
||||||
|
((_controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
||||||
|
indexPath.Section == 1))
|
||||||
|
{
|
||||||
|
var task = _controller.PromptBiometricAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var cell = tableView.CellAt(indexPath);
|
||||||
|
if (cell == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cell is ISelectable selectableCell)
|
||||||
|
{
|
||||||
|
selectableCell.Select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -18,6 +18,8 @@ using Bit.Core;
|
|||||||
|
|
||||||
namespace Bit.iOS.Core.Controllers
|
namespace Bit.iOS.Core.Controllers
|
||||||
{
|
{
|
||||||
|
// TODO: Leaving this here until all inheritance is changed to use BaseLockPasswordViewController instead of UITableViewController
|
||||||
|
[Obsolete("Use BaseLockPasswordViewController instead")]
|
||||||
public abstract class LockPasswordViewController : ExtendedUITableViewController
|
public abstract class LockPasswordViewController : ExtendedUITableViewController
|
||||||
{
|
{
|
||||||
private IVaultTimeoutService _vaultTimeoutService;
|
private IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.iOS.Core.Renderers.CollectionView;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedCollectionView), typeof(ExtendedCollectionViewRenderer))]
|
||||||
|
namespace Bit.iOS.Core.Renderers.CollectionView
|
||||||
|
{
|
||||||
|
public class ExtendedCollectionViewRenderer : GroupableItemsViewRenderer<ExtendedCollectionView, GroupableItemsViewController<ExtendedCollectionView>>
|
||||||
|
{
|
||||||
|
protected override GroupableItemsViewController<ExtendedCollectionView> CreateController(ExtendedCollectionView itemsView, ItemsViewLayout layout)
|
||||||
|
{
|
||||||
|
return new ExtendedGroupableItemsViewController<ExtendedCollectionView>(itemsView, layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Foundation;
|
||||||
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Renderers.CollectionView
|
||||||
|
{
|
||||||
|
public class ExtendedGroupableItemsViewController<TItemsView> : GroupableItemsViewController<TItemsView>
|
||||||
|
where TItemsView : ExtendedCollectionView
|
||||||
|
{
|
||||||
|
public ExtendedGroupableItemsViewController(TItemsView groupableItemsView, ItemsViewLayout layout)
|
||||||
|
: base(groupableItemsView, layout)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateTemplatedCell(TemplatedCell cell, NSIndexPath indexPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
base.UpdateTemplatedCell(cell, indexPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ItemsView?.ExtraDataForLogging != null)
|
||||||
|
{
|
||||||
|
throw new Exception("Error in ExtendedCollectionView, extra data: " + ItemsView.ExtraDataForLogging, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,10 @@ namespace Bit.iOS.Core.Services
|
|||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CopyTextAsync(string text, int expiresInMs = -1)
|
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||||
{
|
{
|
||||||
|
// isSensitive is only used by Android for now
|
||||||
|
|
||||||
int clearSeconds = -1;
|
int clearSeconds = -1;
|
||||||
if (expiresInMs < 0)
|
if (expiresInMs < 0)
|
||||||
{
|
{
|
||||||
@@ -36,5 +38,11 @@ namespace Bit.iOS.Core.Services
|
|||||||
ExpirationDate = clearSeconds > 0 ? NSDate.FromTimeIntervalSinceNow(clearSeconds) : null
|
ExpirationDate = clearSeconds > 0 ? NSDate.FromTimeIntervalSinceNow(clearSeconds) : null
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsCopyNotificationHandledByPlatform()
|
||||||
|
{
|
||||||
|
// return true for any future versions of iOS that notify the user when text is copied to the clipboard
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
85
src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs
Normal file
85
src/iOS.Core/Utilities/AccountSwitchingOverlayHelper.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using UIKit;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Utilities
|
||||||
|
{
|
||||||
|
public class AccountSwitchingOverlayHelper
|
||||||
|
{
|
||||||
|
IStateService _stateService;
|
||||||
|
IMessagingService _messagingService;
|
||||||
|
ILogger _logger;
|
||||||
|
|
||||||
|
public AccountSwitchingOverlayHelper()
|
||||||
|
{
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UIImage> CreateAvatarImageAsync()
|
||||||
|
{
|
||||||
|
var avatarImageSource = new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
|
||||||
|
var avatarUIImage = await avatarImageSource.GetNativeImageAsync();
|
||||||
|
return avatarUIImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountSwitchingOverlayView CreateAccountSwitchingOverlayView(UIView containerView)
|
||||||
|
{
|
||||||
|
var overlay = new AccountSwitchingOverlayView()
|
||||||
|
{
|
||||||
|
LongPressAccountEnabled = false,
|
||||||
|
AfterHide = () =>
|
||||||
|
{
|
||||||
|
if (containerView != null)
|
||||||
|
{
|
||||||
|
containerView.Hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var vm = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
|
{
|
||||||
|
FromIOSExtension = true
|
||||||
|
};
|
||||||
|
overlay.BindingContext = vm;
|
||||||
|
overlay.IsVisible = false;
|
||||||
|
|
||||||
|
var renderer = Platform.CreateRenderer(overlay.Content);
|
||||||
|
renderer.SetElementSize(new Size(containerView.Frame.Size.Width, containerView.Frame.Size.Height));
|
||||||
|
|
||||||
|
var view = renderer.NativeView;
|
||||||
|
view.TranslatesAutoresizingMaskIntoConstraints = false;
|
||||||
|
|
||||||
|
containerView.AddSubview(view);
|
||||||
|
containerView.AddConstraints(new NSLayoutConstraint[]
|
||||||
|
{
|
||||||
|
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Trailing, NSLayoutRelation.Equal, view, NSLayoutAttribute.Trailing, 1f, 0f),
|
||||||
|
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Leading, NSLayoutRelation.Equal, view, NSLayoutAttribute.Leading, 1f, 0f),
|
||||||
|
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Top, NSLayoutRelation.Equal, view, NSLayoutAttribute.Top, 1f, 0f),
|
||||||
|
NSLayoutConstraint.Create(containerView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, view, NSLayoutAttribute.Bottom, 1f, 0f)
|
||||||
|
});
|
||||||
|
containerView.Hidden = true;
|
||||||
|
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnToolbarItemActivated(AccountSwitchingOverlayView accountSwitchingOverlayView, UIView containerView)
|
||||||
|
{
|
||||||
|
var overlayVisible = accountSwitchingOverlayView.IsVisible;
|
||||||
|
if (!overlayVisible)
|
||||||
|
{
|
||||||
|
// So that the animation doesn't break we only care about showing it
|
||||||
|
// and the hiding if done through AccountSwitchingOverlayView -> AfterHide
|
||||||
|
containerView.Hidden = false;
|
||||||
|
}
|
||||||
|
accountSwitchingOverlayView.ToggleVisibililtyCommand.Execute(null);
|
||||||
|
containerView.UserInteractionEnabled = !overlayVisible;
|
||||||
|
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/iOS.Core/Utilities/ImageSourceExtensions.cs
Normal file
44
src/iOS.Core/Utilities/ImageSourceExtensions.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UIKit;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Internals;
|
||||||
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Utilities
|
||||||
|
{
|
||||||
|
public static class ImageSourceExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the native image from the ImageSource.
|
||||||
|
/// Taken from https://github.com/xamarin/Xamarin.Forms/blob/02dee20dfa1365d0104758e534581d1fa5958990/Xamarin.Forms.Platform.iOS/Renderers/ImageElementManager.cs#L264
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<UIImage> GetNativeImageAsync(this ImageSource source, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
{
|
||||||
|
if (source == null || source.IsEmpty)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var handler = Xamarin.Forms.Internals.Registrar.Registered.GetHandlerForObject<IImageSourceHandler>(source);
|
||||||
|
if (handler == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
float scale = (float)UIScreen.MainScreen.Scale;
|
||||||
|
|
||||||
|
return await handler.LoadImageAsync(source, scale: scale, cancelationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Log.Warning("Image loading", "Image load cancelled");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Warning("Image loading", $"Image load failed: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,10 +83,10 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
|
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
|
||||||
{
|
{
|
||||||
theme = "dark";
|
theme = ThemeManager.Dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theme == "dark" || theme == "black" || theme == "nord")
|
if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
|
||||||
{
|
{
|
||||||
LightTheme = false;
|
LightTheme = false;
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/iOS.Core/Utilities/UISearchBarExtensions.cs
Normal file
20
src/iOS.Core/Utilities/UISearchBarExtensions.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using UIKit;
|
||||||
|
|
||||||
|
namespace Bit.iOS.Core.Utilities
|
||||||
|
{
|
||||||
|
public static class UISearchBarExtensions
|
||||||
|
{
|
||||||
|
public static void UpdateThemeIfNeeded(this UISearchBar searchBar)
|
||||||
|
{
|
||||||
|
if (!ThemeHelpers.LightTheme)
|
||||||
|
{
|
||||||
|
searchBar.KeyboardAppearance = UIKeyboardAppearance.Dark;
|
||||||
|
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
|
||||||
|
{
|
||||||
|
searchBar.SearchTextField.TextColor = UIColor.White;
|
||||||
|
searchBar.SearchTextField.LeftView.TintColor = UIColor.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,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.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@@ -58,13 +59,13 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
() => ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync());
|
() => ServiceContainer.Resolve<IAppIdService>("appIdService").GetAppIdAsync());
|
||||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||||
var stateService = new StateService(mobileStorageService, secureStorageService);
|
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
|
||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
||||||
var clipboardService = new ClipboardService(stateService);
|
var clipboardService = new ClipboardService(stateService);
|
||||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||||
broadcasterService);
|
messagingService, broadcasterService);
|
||||||
var biometricService = new BiometricService(mobileStorageService);
|
var biometricService = new BiometricService(mobileStorageService);
|
||||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||||
@@ -172,6 +173,15 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
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 (postBootstrapFunc != null)
|
if (postBootstrapFunc != null)
|
||||||
{
|
{
|
||||||
await postBootstrapFunc.Invoke();
|
await postBootstrapFunc.Invoke();
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
using Bit.App.Resources;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@@ -6,12 +12,6 @@ using Bit.iOS.Core.Controllers;
|
|||||||
using Bit.iOS.Core.Models;
|
using Bit.iOS.Core.Models;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Views
|
namespace Bit.iOS.Core.Views
|
||||||
@@ -122,7 +122,10 @@ namespace Bit.iOS.Core.Views
|
|||||||
|
|
||||||
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
|
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
if (Items == null || Items.Count() == 0 || cell == null)
|
if (Items == null
|
||||||
|
|| !Items.Any()
|
||||||
|
|| cell?.TextLabel == null
|
||||||
|
|| cell.DetailTextLabel == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Resources\" />
|
<Folder Include="Resources\" />
|
||||||
<Folder Include="Effects\" />
|
<Folder Include="Effects\" />
|
||||||
|
<Folder Include="Renderers\CollectionView\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Constants.cs" />
|
<Compile Include="Constants.cs" />
|
||||||
@@ -194,6 +195,12 @@
|
|||||||
<Compile Include="Services\ClipboardService.cs" />
|
<Compile Include="Services\ClipboardService.cs" />
|
||||||
<Compile Include="Utilities\FontElementExtensions.cs" />
|
<Compile Include="Utilities\FontElementExtensions.cs" />
|
||||||
<Compile Include="Effects\ScrollViewContentInsetAdjustmentBehaviorEffect.cs" />
|
<Compile Include="Effects\ScrollViewContentInsetAdjustmentBehaviorEffect.cs" />
|
||||||
|
<Compile Include="Utilities\AccountSwitchingOverlayHelper.cs" />
|
||||||
|
<Compile Include="Utilities\ImageSourceExtensions.cs" />
|
||||||
|
<Compile Include="Controllers\BaseLockPasswordViewController.cs" />
|
||||||
|
<Compile Include="Renderers\CollectionView\ExtendedCollectionViewRenderer.cs" />
|
||||||
|
<Compile Include="Renderers\CollectionView\ExtendedGroupableItemsViewController.cs" />
|
||||||
|
<Compile Include="Utilities\UISearchBarExtensions.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\App\App.csproj">
|
<ProjectReference Include="..\App\App.csproj">
|
||||||
|
|||||||
@@ -167,7 +167,7 @@
|
|||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
|
||||||
<PackageReference Include="Microsoft.AppCenter.Crashes">
|
<PackageReference Include="Microsoft.AppCenter.Crashes">
|
||||||
<Version>4.4.0</Version>
|
<Version>4.4.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.7.2</Version>
|
<Version>1.7.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||||
|
|||||||
Reference in New Issue
Block a user