mirror of
https://github.com/bitwarden/mobile
synced 2025-12-13 06:43:17 +00:00
Compare commits
4 Commits
v2022.6.1
...
bug/ps-675
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b54371dacd | ||
|
|
e51233bf9b | ||
|
|
f9cbe43627 | ||
|
|
5579817f9f |
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,11 +60,6 @@ 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
|
||||||
|
|
||||||
@@ -214,11 +209,6 @@ 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
|
||||||
|
|
||||||
@@ -378,11 +368,6 @@ 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
|
||||||
|
|||||||
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
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ 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, messagingService);
|
var stateService = new StateService(mobileStorageService, secureStorageService);
|
||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var clipboardService = new ClipboardService(stateService);
|
var clipboardService = new ClipboardService(stateService);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.6.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.05.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,6 @@ 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();
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public bool LongPressAccountEnabled { get; set; } = true;
|
public bool LongPressAccountEnabled { get; set; } = true;
|
||||||
|
|
||||||
public Action AfterHide { get; set; }
|
|
||||||
|
|
||||||
public async Task ToggleVisibilityAsync()
|
public async Task ToggleVisibilityAsync()
|
||||||
{
|
{
|
||||||
if (IsVisible)
|
if (IsVisible)
|
||||||
@@ -139,8 +137,6 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
// remove overlay
|
// remove overlay
|
||||||
IsVisible = false;
|
IsVisible = false;
|
||||||
|
|
||||||
AfterHide?.Invoke();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,28 +45,23 @@ 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)
|
||||||
{
|
{
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item.AccountView.IsActive)
|
if (!item.AccountView.IsActive)
|
||||||
{
|
{
|
||||||
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
_messagingService.Send("switchedAccount");
|
||||||
if (FromIOSExtension)
|
|
||||||
{
|
|
||||||
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (AllowActiveAccountSelection)
|
else if (AllowActiveAccountSelection)
|
||||||
{
|
{
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
_messagingService.Send("switchedAccount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_messagingService.Send("addAccount");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
|
|||||||
@@ -80,8 +80,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="_passwordGrid"
|
x:Name="_passwordGrid"
|
||||||
@@ -120,7 +119,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ namespace Bit.App.Pages
|
|||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string Pin { get; set; }
|
public string Pin { get; set; }
|
||||||
public Action UnlockedAction { get; set; }
|
public Action UnlockedAction { get; set; }
|
||||||
|
|||||||
@@ -101,8 +101,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Padding="10, 0">
|
<StackLayout Padding="10, 0">
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ namespace Bit.App.Pages
|
|||||||
public Command LogInCommand { get; }
|
public Command LogInCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action LogInSuccessAction { get; set; }
|
public Action LogInSuccessAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
|
|||||||
@@ -81,12 +81,10 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||||
string ssoToken;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _apiService.PreValidateSso(OrgIdentifier);
|
await _apiService.PreValidateSso(OrgIdentifier);
|
||||||
ssoToken = response.Token;
|
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
@@ -114,8 +112,7 @@ namespace Bit.App.Pages
|
|||||||
"response_type=code&scope=api%20offline_access&" +
|
"response_type=code&scope=api%20offline_access&" +
|
||||||
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
||||||
"code_challenge_method=S256&response_mode=query&" +
|
"code_challenge_method=S256&response_mode=query&" +
|
||||||
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
|
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
|
||||||
"ssoToken=" + Uri.EscapeDataString(ssoToken);
|
|
||||||
|
|
||||||
WebAuthenticatorResult authResult = null;
|
WebAuthenticatorResult authResult = null;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -68,8 +68,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -107,8 +106,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
|
|||||||
@@ -107,8 +107,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -146,8 +145,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
@@ -219,8 +220,7 @@ namespace Bit.App.Pages
|
|||||||
// Request
|
// Request
|
||||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
ResetPasswordKey = encryptedKey.EncryptedString,
|
ResetPasswordKey = encryptedKey.EncryptedString
|
||||||
MasterPasswordHash = masterPasswordHash,
|
|
||||||
};
|
};
|
||||||
var userId = await _stateService.GetActiveUserIdAsync();
|
var userId = await _stateService.GetActiveUserIdAsync();
|
||||||
// Enroll user
|
// Enroll user
|
||||||
|
|||||||
@@ -105,8 +105,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
@@ -141,8 +140,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -444,8 +444,7 @@
|
|||||||
Command="{Binding TogglePasswordCommand}"
|
Command="{Binding TogglePasswordCommand}"
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordInfo}"
|
Text="{u:I18n PasswordInfo}"
|
||||||
|
|||||||
@@ -241,7 +241,8 @@ namespace Bit.App.Pages
|
|||||||
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
||||||
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
||||||
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
||||||
|
|
||||||
|
|||||||
@@ -104,8 +104,7 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
|
||||||
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ConfirmYourIdentity}"
|
Text="{u:I18n ConfirmYourIdentity}"
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
|
|
||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -161,8 +161,7 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
|||||||
@@ -299,7 +299,8 @@ namespace Bit.App.Pages
|
|||||||
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
||||||
public bool AllowPersonal { get; set; }
|
public bool AllowPersonal { get; set; }
|
||||||
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -15,7 +18,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class CiphersPageViewModel : VaultFilterViewModel
|
public class CiphersPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
@@ -28,9 +31,12 @@ namespace Bit.App.Pages
|
|||||||
private CancellationTokenSource _searchCancellationTokenSource;
|
private CancellationTokenSource _searchCancellationTokenSource;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private bool _showVaultFilter;
|
||||||
|
private string _vaultFilterSelection;
|
||||||
private bool _showNoData;
|
private bool _showNoData;
|
||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
|
private List<Organization> _organizations;
|
||||||
|
|
||||||
public CiphersPageViewModel()
|
public CiphersPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -46,19 +52,18 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => _logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command CipherOptionsCommand { get; set; }
|
public Command CipherOptionsCommand { get; set; }
|
||||||
|
public ICommand VaultFilterCommand { get; }
|
||||||
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
||||||
public Func<CipherView, bool> Filter { get; set; }
|
public Func<CipherView, bool> Filter { get; set; }
|
||||||
public string AutofillUrl { get; set; }
|
public string AutofillUrl { get; set; }
|
||||||
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,6 +81,23 @@ namespace Bit.App.Pages
|
|||||||
nameof(ShowSearchDirection)
|
nameof(ShowSearchDirection)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public bool ShowVaultFilter
|
||||||
|
{
|
||||||
|
get => _showVaultFilter;
|
||||||
|
set => SetProperty(ref _showVaultFilter, value);
|
||||||
|
}
|
||||||
|
public string VaultFilterDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||||
|
}
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _vaultFilterSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
||||||
|
|
||||||
@@ -87,7 +109,12 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
await InitVaultFilterAsync(true);
|
_organizations = await _organizationService.GetAllAsync();
|
||||||
|
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
||||||
|
if (ShowVaultFilter && _vaultFilterSelection == null)
|
||||||
|
{
|
||||||
|
_vaultFilterSelection = AppResources.AllVaults;
|
||||||
|
}
|
||||||
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
PerformSearchIfPopulated();
|
PerformSearchIfPopulated();
|
||||||
}
|
}
|
||||||
@@ -210,11 +237,50 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnVaultFilterSelectedAsync()
|
private async Task VaultFilterOptionsAsync()
|
||||||
{
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||||
|
if (_organizations.Any())
|
||||||
|
{
|
||||||
|
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
||||||
|
}
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||||
|
options.ToArray());
|
||||||
|
if (selection == null || selection == AppResources.Cancel ||
|
||||||
|
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||||
|
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultFilterDescription = selection;
|
||||||
PerformSearchIfPopulated();
|
PerformSearchIfPopulated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<CipherView>> GetAllCiphersAsync()
|
||||||
|
{
|
||||||
|
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
return decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||||
|
}
|
||||||
|
if (IsVaultFilterOrgVault)
|
||||||
|
{
|
||||||
|
var orgId = GetVaultFilterOrgId();
|
||||||
|
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
return decCiphers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
|
private string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
private async void CipherOptionsAsync(CipherView cipher)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
@@ -16,7 +17,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GroupingsPageViewModel : VaultFilterViewModel
|
public class GroupingsPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private const int NoFolderListSize = 100;
|
private const int NoFolderListSize = 100;
|
||||||
|
|
||||||
@@ -29,7 +30,10 @@ namespace Bit.App.Pages
|
|||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
private bool _syncRefreshing;
|
private bool _syncRefreshing;
|
||||||
|
private bool _showVaultFilter;
|
||||||
|
private string _vaultFilterSelection;
|
||||||
private string _noDataText;
|
private string _noDataText;
|
||||||
|
private List<Organization> _organizations;
|
||||||
private List<CipherView> _allCiphers;
|
private List<CipherView> _allCiphers;
|
||||||
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
||||||
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
||||||
@@ -74,6 +78,9 @@ namespace Bit.App.Pages
|
|||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
});
|
});
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => _logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
{
|
{
|
||||||
@@ -101,11 +108,6 @@ 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;
|
||||||
@@ -151,12 +153,30 @@ namespace Bit.App.Pages
|
|||||||
get => _websiteIconsEnabled;
|
get => _websiteIconsEnabled;
|
||||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
}
|
}
|
||||||
|
public bool ShowVaultFilter
|
||||||
|
{
|
||||||
|
get => _showVaultFilter;
|
||||||
|
set => SetProperty(ref _showVaultFilter, value);
|
||||||
|
}
|
||||||
|
public string VaultFilterDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||||
|
}
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _vaultFilterSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
|
|
||||||
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||||
|
public ICommand VaultFilterCommand { get; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
@@ -181,9 +201,14 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await InitVaultFilterAsync(MainPage);
|
_organizations = await _organizationService.GetAllAsync();
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,11 +394,30 @@ namespace Bit.App.Pages
|
|||||||
SyncRefreshing = false;
|
SyncRefreshing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnVaultFilterSelectedAsync()
|
public async Task VaultFilterOptionsAsync()
|
||||||
{
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||||
|
if (_organizations.Any())
|
||||||
|
{
|
||||||
|
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
||||||
|
}
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||||
|
options.ToArray());
|
||||||
|
if (selection == null || selection == AppResources.Cancel ||
|
||||||
|
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||||
|
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultFilterDescription = selection;
|
||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SelectCipherAsync(CipherView cipher)
|
public async Task SelectCipherAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
var page = new ViewPage(cipher.Id);
|
var page = new ViewPage(cipher.Id);
|
||||||
@@ -457,8 +501,8 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
|
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
|
||||||
NoDataText = AppResources.NoItems;
|
NoDataText = AppResources.NoItems;
|
||||||
_allCiphers = await GetAllCiphersAsync();
|
|
||||||
HasCiphers = _allCiphers.Any();
|
HasCiphers = _allCiphers.Any();
|
||||||
FavoriteCiphers?.Clear();
|
FavoriteCiphers?.Clear();
|
||||||
NoFolderCiphers?.Clear();
|
NoFolderCiphers?.Clear();
|
||||||
@@ -472,7 +516,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
await FillFoldersAndCollectionsAsync();
|
await FillFoldersAndCollectionsAsync(orgId);
|
||||||
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;
|
||||||
@@ -596,9 +640,28 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task FillFoldersAndCollectionsAsync()
|
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
|
||||||
|
{
|
||||||
|
string orgId = null;
|
||||||
|
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
_allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||||
|
}
|
||||||
|
else if (IsVaultFilterOrgVault)
|
||||||
|
{
|
||||||
|
orgId = GetVaultFilterOrgId();
|
||||||
|
_allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_allCiphers = decCiphers;
|
||||||
|
}
|
||||||
|
return orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FillFoldersAndCollectionsAsync(string orgId)
|
||||||
{
|
{
|
||||||
var 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)
|
||||||
@@ -618,6 +681,11 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
@@ -144,8 +144,7 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
|||||||
@@ -214,7 +214,8 @@ namespace Bit.App.Pages
|
|||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string TotpCodeFormatted
|
public string TotpCodeFormatted
|
||||||
{
|
{
|
||||||
get => _totpCodeFormatted;
|
get => _totpCodeFormatted;
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9135
src/App/Resources/AppResources.Designer.cs
generated
9135
src/App/Resources/AppResources.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -2223,11 +2223,11 @@
|
|||||||
<data name="TapToGoBack" xml:space="preserve">
|
<data name="TapToGoBack" xml:space="preserve">
|
||||||
<value>Tap to go back</value>
|
<value>Tap to go back</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
|
<data name="VisibilityTogglePasswordIsVisibleActivateToHide" xml:space="preserve">
|
||||||
<value>Password is visible, tap to hide.</value>
|
<value>Visibility Toggle, Password is visible, activate to hide.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
|
<data name="VisibilityTogglePasswordIsNotVisibleActivateToHide" xml:space="preserve">
|
||||||
<value>Password is not visible, tap to show.</value>
|
<value>Visibility Toggle, Password is not visible, activate to show.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FilterByVault" xml:space="preserve">
|
<data name="FilterByVault" xml:space="preserve">
|
||||||
<value>Filter items by vault</value>
|
<value>Filter items by vault</value>
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ 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,
|
||||||
@@ -33,7 +32,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(
|
||||||
|
|||||||
@@ -6,11 +6,21 @@ using Bit.App.Resources;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Utilities.AccountManagement
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
{
|
{
|
||||||
|
public static class AccountsManagerMessageCommands
|
||||||
|
{
|
||||||
|
public const string LOCKED = "locked";
|
||||||
|
public const string LOCK_VAULT = "lockVault";
|
||||||
|
public const string LOGOUT = "logout";
|
||||||
|
public const string LOGGED_OUT = "loggedOut";
|
||||||
|
public const string ADD_ACCOUNT = "addAccount";
|
||||||
|
public const string ACCOUNT_ADDED = "accountAdded";
|
||||||
|
public const string SWITCHED_ACCOUNT = "switchedAccount";
|
||||||
|
}
|
||||||
|
|
||||||
public class AccountsManager : IAccountsManager
|
public class AccountsManager : IAccountsManager
|
||||||
{
|
{
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
@@ -199,7 +209,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
private async Task SwitchedAccountAsync()
|
private async Task SwitchedAccountAsync()
|
||||||
{
|
{
|
||||||
await AppHelpers.OnAccountSwitchAsync();
|
await AppHelpers.OnAccountSwitchAsync();
|
||||||
await Device.InvokeOnMainThreadAsync(async () =>
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task PutDeleteCipherAsync(string id);
|
Task PutDeleteCipherAsync(string id);
|
||||||
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
||||||
Task RefreshIdentityTokenAsync();
|
Task RefreshIdentityTokenAsync();
|
||||||
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
|
Task<object> PreValidateSso(string identifier);
|
||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrls urls);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ 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);
|
||||||
@@ -146,6 +145,5 @@ 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,7 +25,6 @@
|
|||||||
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";
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
{
|
{
|
||||||
public class OrganizationUserResetPasswordEnrollmentRequest
|
public class OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
public string MasterPasswordHash { get; set; }
|
|
||||||
public string ResetPasswordKey { get; set; }
|
public string ResetPasswordKey { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Bit.Core.Models.Response
|
|
||||||
{
|
|
||||||
public class SsoPrevalidateResponse
|
|
||||||
{
|
|
||||||
public string Token { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -547,7 +547,7 @@ namespace Bit.Core.Services
|
|||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
|
public async Task<object> PreValidateSso(string identifier)
|
||||||
{
|
{
|
||||||
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
||||||
using (var requestMessage = new HttpRequestMessage())
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
@@ -571,8 +571,7 @@ namespace Bit.Core.Services
|
|||||||
var error = await HandleErrorAsync(response, false, true);
|
var error = await HandleErrorAsync(response, false, true);
|
||||||
throw new ApiException(error);
|
throw new ApiException(error);
|
||||||
}
|
}
|
||||||
var responseJsonString = await response.Content.ReadAsStringAsync();
|
return null;
|
||||||
return JsonConvert.DeserializeObject<SsoPrevalidateResponse>(responseJsonString);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,12 +249,6 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<bool> ShouldShowVaultFilterAsync()
|
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||||
{
|
{
|
||||||
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
|
||||||
var singleOrgPolicyApplies = await PolicyAppliesToUser(PolicyType.OnlyOrg);
|
|
||||||
if (personalOwnershipPolicyApplies && singleOrgPolicyApplies)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var organizations = await _organizationService.GetAllAsync();
|
var organizations = await _organizationService.GetAllAsync();
|
||||||
return organizations?.Any() ?? false;
|
return organizations?.Any() ?? false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,20 +15,16 @@ 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,
|
public StateService(IStorageService storageService, IStorageService secureStorageService)
|
||||||
IStorageService secureStorageService,
|
|
||||||
IMessagingService messagingService)
|
|
||||||
{
|
{
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
_secureStorageService = secureStorageService;
|
_secureStorageService = secureStorageService;
|
||||||
_messagingService = messagingService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetActiveUserIdAsync()
|
public async Task<string> GetActiveUserIdAsync()
|
||||||
@@ -71,28 +67,6 @@ 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;
|
||||||
@@ -1536,16 +1510,6 @@ 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)
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.6.1</string>
|
<string>2022.05.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -32,22 +32,11 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
{
|
{
|
||||||
var overlay = new AccountSwitchingOverlayView()
|
var overlay = new AccountSwitchingOverlayView()
|
||||||
{
|
{
|
||||||
LongPressAccountEnabled = false,
|
LongPressAccountEnabled = false
|
||||||
AfterHide = () =>
|
|
||||||
{
|
|
||||||
if (containerView != null)
|
|
||||||
{
|
|
||||||
containerView.Hidden = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var vm = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
var vm = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger);
|
||||||
{
|
|
||||||
FromIOSExtension = true
|
|
||||||
};
|
|
||||||
overlay.BindingContext = vm;
|
overlay.BindingContext = vm;
|
||||||
overlay.IsVisible = false;
|
|
||||||
|
|
||||||
var renderer = Platform.CreateRenderer(overlay.Content);
|
var renderer = Platform.CreateRenderer(overlay.Content);
|
||||||
renderer.SetElementSize(new Size(containerView.Frame.Size.Width, containerView.Frame.Size.Height));
|
renderer.SetElementSize(new Size(containerView.Frame.Size.Width, containerView.Frame.Size.Height));
|
||||||
@@ -71,13 +60,8 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
public void OnToolbarItemActivated(AccountSwitchingOverlayView accountSwitchingOverlayView, UIView containerView)
|
public void OnToolbarItemActivated(AccountSwitchingOverlayView accountSwitchingOverlayView, UIView containerView)
|
||||||
{
|
{
|
||||||
var overlayVisible = accountSwitchingOverlayView.IsVisible;
|
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);
|
accountSwitchingOverlayView.ToggleVisibililtyCommand.Execute(null);
|
||||||
|
containerView.Hidden = false;
|
||||||
containerView.UserInteractionEnabled = !overlayVisible;
|
containerView.UserInteractionEnabled = !overlayVisible;
|
||||||
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
|
containerView.Subviews[0].UserInteractionEnabled = !overlayVisible;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ 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, messagingService);
|
var stateService = new StateService(mobileStorageService, secureStorageService);
|
||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.6.1</string>
|
<string>2022.05.1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.6.1</string>
|
<string>2022.05.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden</string>
|
<string>com.8bit.bitwarden</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.6.1</string>
|
<string>2022.05.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user