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

Compare commits

..

4 Commits

Author SHA1 Message Date
github-actions[bot]
43b7782872 Bumped version to 2.18.0 (#1893)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit cdc41d3bef)
2022-04-27 08:25:37 -07:00
Federico Maccaroni
8f66e2a315 PS-291 Fix password history to update the collection on the main thread to load correctly (#1890) 2022-04-26 09:08:58 -04:00
sneakernuts
8e333e6d14 Switched org
(cherry picked from commit 9fd8dab9d6)
2022-04-22 07:44:22 -06:00
mp-bw
ac7a680a1b update XF and revert old workarounds (#1885) 2022-04-21 21:31:02 -04:00
380 changed files with 2926 additions and 7945 deletions

View File

@@ -1,12 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-format": {
"version": "5.1.250801",
"commands": [
"dotnet-format"
]
}
}
}

View File

@@ -6,7 +6,6 @@ root = true
# Don't use tabs for indentation.
[*]
indent_style = space
end_of_line = lf
# (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files

View File

@@ -1,2 +0,0 @@
# .NET format https://github.com/bitwarden/mobile/pull/1738
04539af2a66668b6e85476d5cf318c9150ec4357

2
.gitattributes vendored
View File

@@ -1,7 +1,7 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto eol=lf
* text=auto
###############################################################################
# Set default behavior for command prompt diff.

View File

@@ -21,8 +21,12 @@
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## 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)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

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

View File

@@ -60,11 +60,6 @@ jobs:
runs-on: windows-2019
needs: setup
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
@@ -110,14 +105,6 @@ jobs:
- name: Restore packages
run: nuget restore
- name: Restore tools
run: dotnet tool restore
shell: pwsh
- name: Verify Format
run: dotnet tool run dotnet-format --check
shell: pwsh
- name: Run Core tests
run: dotnet test test/Core.Test/Core.Test.csproj
@@ -214,11 +201,6 @@ jobs:
name: F-Droid Build
runs-on: windows-2019
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
@@ -314,6 +296,18 @@ jobs:
$xml.Save($androidPath);
Write-Output "########################################"
Write-Output "##### Uninstall from App.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($appPath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);
Write-Output "########################################"
Write-Output "##### Uninstall from Core.csproj"
Write-Output "########################################"
@@ -378,11 +372,6 @@ jobs:
runs-on: macos-11
needs: setup
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with:
nuget-version: 5.9.0
- name: Print environment
run: |
nuget help | grep Version

View File

@@ -13,11 +13,6 @@ on:
- Initial Release
- Redeploy
- Dry Run
fdroid_publish:
description: 'Publish to f-droid store'
required: true
default: true
type: boolean
jobs:
release:
@@ -39,13 +34,29 @@ jobs:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin
file: src/Android/Properties/AndroidManifest.xml
- name: Retrieve Mobile release version
id: retrieve-mobile-version
run: |
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' \
./src/Android/Properties/AndroidManifest.xml | tr -d '"')
echo "::set-output name=mobile_version::${ver}"
shell: bash
- 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
id: branch
@@ -72,8 +83,8 @@ jobs:
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden iOS.zip"
commit: ${{ github.sha }}
tag: v${{ steps.version.outputs.version }}
name: Version ${{ steps.version.outputs.version }}
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
@@ -83,7 +94,6 @@ jobs:
name: F-Droid Release
runs-on: ubuntu-20.04
needs: release
if: inputs.fdroid_publish
steps:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -84,7 +84,7 @@
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.3</Version>
<Version>1.7.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>122.0.0</Version>
@@ -145,12 +145,12 @@
<Compile Include="Tiles\GeneratorTileService.cs" />
<Compile Include="Tiles\MyVaultTileService.cs" />
<Compile Include="Utilities\AndroidHelpers.cs" />
<Compile Include="Utilities\AppCenterHelper.cs" />
<Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
<Compile Include="Services\ClipboardService.cs" />
<Compile Include="Utilities\IntentExtensions.cs" />
<Compile Include="Renderers\CustomPageRenderer.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" />

View File

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

View File

@@ -64,13 +64,15 @@ namespace Bit.Droid
Intent?.Validate();
base.OnCreate(savedInstanceState);
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
if (!CoreHelpers.InDebugMode())
{
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
});
}
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
#if !DEBUG && !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
var appCenterTask = appCenterHelper.InitAsync();
#endif
var toplayout = Window?.DecorView?.RootView;
if (toplayout != null)
@@ -83,7 +85,6 @@ namespace Bit.Droid
_appOptions = GetOptions();
LoadApplication(new App.App(_appOptions));
_broadcasterService.Subscribe(_activityKey, (message) =>
{
if (message.Command == "startEventTimer")

View File

@@ -12,6 +12,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Droid.Services;
using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
using Plugin.Fingerprint;
using Xamarin.Android.Net;
@@ -19,8 +20,6 @@ using System.Net.Http;
using System.Net;
using Bit.App.Utilities;
using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
using Bit.App.Controls;
#if !FDROID
using Android.Gms.Security;
#endif
@@ -63,16 +62,6 @@ namespace Bit.Droid
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
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.Resolve<ILogger>("logger"));
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
}
#if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -101,13 +90,12 @@ namespace Bit.Droid
{
ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
#if FDROID
var logger = new StubLogger();
ServiceContainer.Register<ILogger>("logger", new StubLogger());
#elif DEBUG
var logger = DebugLogger.Instance;
ServiceContainer.Register<ILogger>("logger", DebugLogger.Instance);
#else
var logger = Logger.Instance;
ServiceContainer.Register<ILogger>("logger", Logger.Instance);
#endif
ServiceContainer.Register("logger", logger);
// Note: This might cause a race condition. Investigate more.
Task.Run(() =>
@@ -127,20 +115,19 @@ namespace Bit.Droid
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
var localizeService = new LocalizeService();
var broadcasterService = new BroadcasterService(logger);
var broadcasterService = new BroadcasterService();
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
var stateService = new StateService(mobileStorageService, secureStorageService);
var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var clipboardService = new ClipboardService(stateService);
var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
var deviceActionService = new DeviceActionService(stateService, messagingService,
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
@@ -155,14 +142,13 @@ namespace Bit.Droid
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
// Push
#if FDROID

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.6.2" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.18.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
@@ -12,7 +12,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>

View File

@@ -1,31 +0,0 @@
using System;
using Android.App;
using Android.Content;
using AndroidX.AppCompat.Widget;
using Bit.App.Resources;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomPageRenderer : PageRenderer
{
public CustomPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
Activity context = (Activity)this.Context;
var toolbar = context.FindViewById<Toolbar>(Resource.Id.toolbar);
if(toolbar != null)
{
toolbar.NavigationContentDescription = AppResources.TapToGoBack;
}
}
}
}

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -2,5 +2,4 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

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

View File

@@ -3,10 +3,11 @@ using System.Threading.Tasks;
using Android.OS;
using Android.Security.Keystore;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Java.Security;
using Javax.Crypto;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
namespace Bit.Droid.Services
{
@@ -73,7 +74,9 @@ namespace Bit.Droid.Services
catch (InvalidKeyException e)
{
// Fallback for old bitwarden users without a key
LoggerHelper.LogEvenIfCantBeResolved(e);
#if !FDROID
Crashes.TrackError(e);
#endif
CreateKey();
}
@@ -98,7 +101,9 @@ namespace Bit.Droid.Services
{
// Catch silently to allow biometrics to function on devices that are in a state where key generation
// is not functioning
LoggerHelper.LogEvenIfCantBeResolved(e);
#if !FDROID
Crashes.TrackError(e);
#endif
}
}
}

View File

@@ -2,7 +2,7 @@
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.OS;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Plugin.CurrentActivity;
@@ -26,47 +26,11 @@ namespace Bit.Droid.Services
PendingIntentFlags.UpdateCurrent));
}
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
public async Task CopyTextAsync(string text, int expiresInMs = -1)
{
try
{
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
if ((int)Build.VERSION.SdkInt < 33)
{
await Clipboard.SetTextAsync(text);
}
else
{
CopyToClipboard(text, isSensitive);
}
await Clipboard.SetTextAsync(text);
await ClearClipboardAlarmAsync(expiresInMs);
}
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
{
// #1962 Just ignore, the content is copied either way but there is some app interfiering in the process
// that the OS catches and just throws this exception.
}
}
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;
await ClearClipboardAlarmAsync(expiresInMs);
}
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)

View File

@@ -35,7 +35,6 @@ namespace Bit.Droid.Services
{
public class DeviceActionService : IDeviceActionService
{
private readonly IClipboardService _clipboardService;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
@@ -48,13 +47,11 @@ namespace Bit.Droid.Services
private string _userAgent;
public DeviceActionService(
IClipboardService clipboardService,
IStateService stateService,
IMessagingService messagingService,
IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc)
{
_clipboardService = clipboardService;
_stateService = stateService;
_messagingService = messagingService;
_broadcasterService = broadcasterService;
@@ -932,12 +929,20 @@ namespace Bit.Droid.Services
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (totp != null)
{
await _clipboardService.CopyTextAsync(totp);
CopyToClipboard(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()
{
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
@@ -948,21 +953,5 @@ namespace Bit.Droid.Services
{
// for any Android-specific cleanup required after switching accounts
}
public async Task SetScreenCaptureAllowedAsync()
{
if (CoreHelpers.ForceScreenCaptureEnabled())
{
return;
}
var activity = CrossCurrentActivity.Current?.Activity;
if (await _stateService.GetScreenCaptureAllowedAsync())
{
activity.RunOnUiThread(() => activity.Window.ClearFlags(WindowManagerFlags.Secure));
return;
}
activity.RunOnUiThread(() => activity.Window.AddFlags(WindowManagerFlags.Secure));
}
}
}

View File

@@ -0,0 +1,58 @@
#if !FDROID
using Bit.Core.Abstractions;
using System.Threading.Tasks;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Crashes;
using Newtonsoft.Json;
namespace Bit.Droid.Utilities
{
public class AppCenterHelper
{
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
private readonly IAppIdService _appIdService;
private readonly IStateService _stateService;
private string _userId;
private string _appId;
public AppCenterHelper(
IAppIdService appIdService,
IStateService stateService)
{
_appIdService = appIdService;
_stateService = stateService;
}
public async Task InitAsync()
{
_userId = await _stateService.GetActiveUserIdAsync();
_appId = await _appIdService.GetAppIdAsync();
AppCenter.Start(AppSecret, typeof(Crashes));
AppCenter.SetUserId(_userId);
Crashes.GetErrorAttachments = (ErrorReport report) =>
{
return new ErrorAttachmentLog[]
{
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
};
};
}
public string Description
{
get
{
return JsonConvert.SerializeObject(new
{
AppId = _appId,
UserId = _userId
}, Formatting.Indented);
}
}
}
}
#endif

View File

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

View File

@@ -1,13 +0,0 @@
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);
Task LogOutAsync(string userId, bool userInitiated, bool expired);
}
}

View File

@@ -1,14 +0,0 @@
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();
}
}

View File

@@ -1,6 +1,6 @@
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using System.Threading.Tasks;
namespace Bit.App.Abstractions
{
@@ -48,6 +48,5 @@ namespace Bit.App.Abstractions
bool SupportsFido2();
float GetSystemFontSizeScale();
Task OnAccountSwitchCompleteAsync();
Task SetScreenCaptureAllowedAsync();
}
}

View File

@@ -7,7 +7,7 @@ namespace Bit.App.Abstractions
string[] ProtectedFields { get; }
Task<bool> ShowPasswordPromptAsync();
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
Task<bool> Enabled();

View File

@@ -1,5 +1,5 @@
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
namespace Bit.App.Abstractions
{

View File

@@ -13,12 +13,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.2" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.1" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2478" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2401" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
</ItemGroup>
@@ -128,11 +129,12 @@
<Folder Include="Resources\" />
<Folder Include="Behaviors\" />
<Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" />
<Folder Include="Controls\DateTime\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
</ItemGroup>
@@ -160,6 +162,12 @@
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Styles\Base.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\AppResources.cs.Designer.cs">
<DependentUpon>AppResources.cs.resx</DependentUpon>
@@ -413,7 +421,5 @@
<None Remove="Behaviors\" />
<None Remove="Xamarin.CommunityToolkit" />
<None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" />
<None Remove="Controls\DateTime\" />
</ItemGroup>
</Project>

View File

@@ -1,24 +1,22 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Pages;
using Bit.App.Resources;
using Bit.App.Services;
using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Services;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App
{
public partial class App : Application, IAccountsManagerHost
public partial class App : Application
{
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
@@ -29,7 +27,6 @@ namespace Bit.App
private readonly IAuthService _authService;
private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService;
private readonly IAccountsManager _accountsManager;
private static bool _isResumed;
@@ -50,101 +47,128 @@ namespace Bit.App
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_accountsManager.Init(() => Options, this);
Bootstrap();
_broadcasterService.Subscribe(nameof(App), async (message) =>
{
try
if (message.Command == "showDialog")
{
if (message.Command == "showDialog")
var details = message.Data as DialogDetails;
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
Device.BeginInvokeOnMainThread(async () =>
{
var details = message.Data as DialogDetails;
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
Device.BeginInvokeOnMainThread(async () =>
if (!string.IsNullOrWhiteSpace(details.CancelText))
{
if (!string.IsNullOrWhiteSpace(details.CancelText))
{
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
else
{
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
else if (message.Command == "resumed")
{
if (Device.RuntimePlatform == Device.iOS)
{
ResumedAsync().FireAndForget();
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
}
else if (message.Command == "slept")
{
if (Device.RuntimePlatform == Device.iOS)
else
{
await SleptAsync();
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}
}
else if (message.Command == "migrated")
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
else if (message.Command == "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")
{
if (Device.RuntimePlatform == Device.iOS)
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend" ||
message.Command == "popAllAndGoToAutofillCiphers")
{
Device.BeginInvokeOnMainThread(async () =>
{
if (Current.MainPage is TabsPage tabsPage)
{
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == "popAllAndGoToAutofillCiphers")
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (message.Command == "popAllAndGoToTabMyVault")
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == "popAllAndGoToTabGenerator")
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
ResumedAsync().FireAndForget();
}
}
catch (Exception ex)
else if (message.Command == "slept")
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
if (Device.RuntimePlatform == Device.iOS)
{
await SleptAsync();
}
}
else if (message.Command == "addAccount")
{
await AddAccount();
}
else if (message.Command == "accountAdded")
{
await UpdateThemeAsync();
}
else if (message.Command == "switchedAccount")
{
await SwitchedAccountAsync();
}
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await SetMainPageAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend" ||
message.Command == "popAllAndGoToAutofillCiphers")
{
Device.BeginInvokeOnMainThread(async () =>
{
if (Current.MainPage is TabsPage tabsPage)
{
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == "popAllAndGoToAutofillCiphers")
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (message.Command == "popAllAndGoToTabMyVault")
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == "popAllAndGoToTabGenerator")
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
});
}
@@ -184,10 +208,7 @@ namespace Bit.App
{
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
if (!SetTabsPageFromAutofill(isLocked))
{
ClearAutofillUri();
}
SetTabsPageFromAutofill(isLocked);
await SleptAsync();
}
}
@@ -210,7 +231,6 @@ namespace Bit.App
private async Task ResumedAsync()
{
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("startEventTimer");
await UpdateThemeAsync();
@@ -240,6 +260,102 @@ namespace Bit.App
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()
{
var lastClear = await _stateService.GetLastFileCacheClearAsync();
@@ -249,15 +365,7 @@ namespace Bit.App
}
}
private void ClearAutofillUri()
{
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri))
{
Options.Uri = null;
}
}
private bool SetTabsPageFromAutofill(bool isLocked)
private void SetTabsPageFromAutofill(bool isLocked)
{
if (Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(Options.Uri) &&
!Options.FromAutofillFramework)
@@ -277,9 +385,7 @@ namespace Bit.App
}
});
});
return true;
}
return false;
}
private void Prime()
@@ -301,7 +407,7 @@ namespace Bit.App
UpdateThemeAsync();
};
Current.MainPage = new NavigationPage(new HomePage(Options));
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
}
@@ -322,8 +428,23 @@ namespace Bit.App
});
}
public async Task SetPreviousPageInfoAsync()
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;
}
}
PreviousPageInfo lastPageBeforeLock = null;
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
{
@@ -349,44 +470,8 @@ namespace Bit.App
}
}
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
}
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;
}
var lockPage = new LockPage(Options, autoPromptBiometric);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
}
}
}

View File

@@ -63,10 +63,6 @@ namespace Bit.App.Controls
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
public bool LongPressAccountEnabled { get; set; } = true;
public Action AfterHide { get; set; }
public async Task ToggleVisibilityAsync()
{
if (IsVisible)
@@ -103,7 +99,7 @@ namespace Bit.App.Controls
Opacity = 0;
IsVisible = true;
this.FadeTo(1, 100);
if (Device.RuntimePlatform == Device.Android && MainFab != null)
{
// start fab fade-out
@@ -139,8 +135,6 @@ namespace Bit.App.Controls
// remove overlay
IsVisible = false;
AfterHide?.Invoke();
});
}
@@ -173,7 +167,7 @@ namespace Bit.App.Controls
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
{
if (!LongPressAccountEnabled || !item.IsAccount)
if (!item.IsAccount)
{
return;
}

View File

@@ -22,11 +22,11 @@ namespace Bit.App.Controls
{
_stateService = stateService;
_messagingService = messagingService;
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
LongPressAccountCommand = new AsyncCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
@@ -45,28 +45,23 @@ namespace Bit.App.Controls
public ICommand LongPressAccountCommand { get; }
public bool FromIOSExtension { get; set; }
private async Task SelectAccountAsync(AccountViewCellViewModel item)
{
if (!item.AccountView.IsAccount)
if (item.AccountView.IsAccount)
{
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
return;
}
if (!item.AccountView.IsActive)
{
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
if (FromIOSExtension)
if (!item.AccountView.IsActive)
{
await _stateService.SaveExtensionActiveUserIdToStorageAsync(item.AccountView.UserId);
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send("switchedAccount");
}
else if (AllowActiveAccountSelection)
{
_messagingService.Send("switchedAccount");
}
}
else if (AllowActiveAccountSelection)
else
{
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
_messagingService.Send("addAccount");
}
}

View File

@@ -1,5 +1,5 @@
using System.Windows.Input;
using Bit.Core.Models.View;
using System.Windows.Input;
using Xamarin.Forms;
namespace Bit.App.Controls

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -108,7 +108,7 @@
<controls:MiButton
Grid.Row="0"
Grid.Column="2"
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
Text="&#xe5d3;"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked"
VerticalOptions="CenterAndExpand"

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,5 @@ namespace Bit.App.Controls
{
public class ExtendedCollectionView : CollectionView
{
public string ExtraDataForLogging { get; set; }
}
}

View File

@@ -6,7 +6,7 @@ namespace Bit.App.Controls
{
public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
@@ -15,7 +15,7 @@ namespace Bit.App.Controls
get => (Color)GetValue(StepperBackgroundColorProperty);
set => SetValue(StepperBackgroundColorProperty, value);
}
public Color StepperForegroundColor
{
get => (Color)GetValue(StepperForegroundColorProperty);

View File

@@ -122,7 +122,7 @@
<controls:MiButton
Grid.Row="0"
Grid.Column="2"
Text="{Binding Source={x:Static core:BitwardenIcons.ViewCellMenu}}"
Text="&#xe5d3;"
IsVisible="{Binding ShowOptions, Mode=OneWay}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked"

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Bit.App.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
@@ -13,7 +13,7 @@ namespace Bit.App.Controls
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
nameof(ButtonCommand), typeof(Command<SendView>), typeof(SendViewCell));
public static readonly BindableProperty ShowOptionsProperty = BindableProperty.Create(
nameof(ShowOptions), typeof(bool), typeof(SendViewCell), true, BindingMode.OneWay);

View File

@@ -4,8 +4,8 @@ namespace Bit.App.Effects
{
public class FabShadowEffect : RoutingEffect
{
public FabShadowEffect()
: base("Bitwarden.FabShadowEffect")
public FabShadowEffect()
: base("Bitwarden.FabShadowEffect")
{ }
}
}

View File

@@ -1,12 +1,12 @@
using System.Collections.Generic;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Essentials;
namespace Bit.App.Pages
@@ -46,11 +46,7 @@ namespace Bit.App.Pages
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[]
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
}
public bool IsPolicyInEffect
@@ -70,9 +66,8 @@ namespace Bit.App.Pages
get => _policy;
set => SetProperty(ref _policy, value);
}
public string ShowPasswordIcon => ShowPassword ? "" : "";
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }
@@ -89,7 +84,7 @@ namespace Bit.App.Pages
await CheckPasswordPolicy();
}
}
private async Task CheckPasswordPolicy()
{
Policy = await _policyService.GetMasterPasswordPolicyOptions();
@@ -171,7 +166,7 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred, AppResources.Ok);
return false;
}
return true;
}

View File

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

View File

@@ -1,8 +1,8 @@
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -36,6 +36,14 @@ namespace Bit.App.Pages
};
}
private async void Submit_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.SubmitAsync();
}
}
private async Task SubmitSuccessAsync()
{
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);

View File

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

View File

@@ -1,9 +1,9 @@
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages

View File

@@ -54,7 +54,7 @@ namespace Bit.App.Pages
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
{
if (message.Command == "updatedTheme")
{
@@ -87,7 +87,7 @@ namespace Bit.App.Pages
{
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
}
private void Cancel_Clicked(object sender, EventArgs e)
{
if (DoOnce())
@@ -117,7 +117,7 @@ namespace Bit.App.Pages
_vm.StartRegisterAction();
}
}
private async Task StartRegisterAsync()
{
var page = new RegisterPage(this);
@@ -145,7 +145,7 @@ namespace Bit.App.Pages
_vm.StartEnvironmentAction();
}
}
private async Task StartEnvironmentAsync()
{
await _accountListOverlay.HideAsync();

View File

@@ -80,8 +80,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Grid
x:Name="_passwordGrid"
@@ -120,8 +119,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout
StyleClass="box-row"

View File

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

View File

@@ -10,7 +10,6 @@ using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.Helpers;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -28,7 +27,6 @@ namespace Bit.App.Pages
private readonly IBiometricService _biometricService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly ILogger _logger;
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
private string _email;
private bool _showPassword;
@@ -74,8 +72,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText),
nameof(ShowPasswordIcon)
});
}
@@ -131,15 +128,9 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; }
public string Pin { get; set; }
public Action UnlockedAction { get; set; }
public event Action<int?> FocusSecretEntry
{
add => _secretEntryFocusWeakEventManager.AddEventHandler(value);
remove => _secretEntryFocusWeakEventManager.RemoveEventHandler(value);
}
public async Task InitAsync()
{
@@ -281,7 +272,7 @@ namespace Bit.App.Pages
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
var passwordValid = false;
if (storedKeyHash != null)
{
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
@@ -353,8 +344,11 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
var secret = PinLock ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
var page = (Page as LockPage);
var entry = PinLock ? page.PinEntry : page.MasterPasswordEntry;
var str = PinLock ? Pin : MasterPassword;
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(str) ? 0 : str.Length;
}
public async Task PromptBiometricAsync()
@@ -365,8 +359,18 @@ namespace Bit.App.Pages
return;
}
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
PinLock ? AppResources.PIN : AppResources.MasterPassword, () =>
{
var page = Page as LockPage;
if (PinLock)
{
page.PinEntry.Focus();
}
else
{
page.MasterPasswordEntry.Focus();
}
});
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{

View File

@@ -101,8 +101,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">

View File

@@ -177,7 +177,7 @@ namespace Bit.App.Pages
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();

View File

@@ -58,8 +58,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
nameof(ShowPasswordIcon)
});
}
@@ -68,7 +67,7 @@ namespace Bit.App.Pages
get => _showCancelButton;
set => SetProperty(ref _showCancelButton, value);
}
public string Email
{
get => _email;
@@ -86,7 +85,6 @@ namespace Bit.App.Pages
public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }

View File

@@ -1,9 +1,9 @@
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.App.Models;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -99,7 +99,7 @@ namespace Bit.App.Pages
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();

View File

@@ -1,13 +1,13 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -81,12 +81,10 @@ namespace Bit.App.Pages
}
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
string ssoToken;
try
{
var response = await _apiService.PreValidateSso(OrgIdentifier);
ssoToken = response.Token;
await _apiService.PreValidateSso(OrgIdentifier);
}
catch (ApiException e)
{
@@ -114,8 +112,7 @@ namespace Bit.App.Pages
"response_type=code&scope=api%20offline_access&" +
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
"code_challenge_method=S256&response_mode=query&" +
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
"ssoToken=" + Uri.EscapeDataString(ssoToken);
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
WebAuthenticatorResult authResult = null;
try
@@ -173,7 +170,7 @@ namespace Bit.App.Pages
else if (response.ResetMasterPassword)
{
StartSetPasswordAction?.Invoke();
}
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();

View File

@@ -68,8 +68,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Label
Text="{u:I18n MasterPasswordDescription}"
@@ -107,8 +106,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label

View File

@@ -55,7 +55,7 @@ namespace Bit.App.Pages
await _vm.SubmitAsync();
}
}
private async Task RegistrationSuccessAsync(HomePage homePage)
{
if (homePage != null)

View File

@@ -1,14 +1,14 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.Core;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -39,7 +39,7 @@ namespace Bit.App.Pages
SubmitCommand = new Command(async () => await SubmitAsync());
ShowTerms = !_platformUtilsService.IsSelfHost();
}
public ICommand PoliciesClickCommand => new Command<string>((url) =>
{
_platformUtilsService.LaunchUri(url);
@@ -51,30 +51,28 @@ namespace Bit.App.Pages
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[]
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
nameof(ShowPasswordIcon)
});
}
public bool AcceptPolicies
{
get => _acceptPolicies;
set => SetProperty(ref _acceptPolicies, value);
}
public Thickness SwitchMargin
{
get => Device.RuntimePlatform == Device.Android
? new Thickness(0, 0, 0, 0)
get => Device.RuntimePlatform == Device.Android
? new Thickness(0, 0, 0, 0)
: new Thickness(0, 0, 10, 0);
}
public bool ShowTerms { get; set; }
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string Name { get; set; }
public string Email { get; set; }
public string MasterPassword { get; set; }
@@ -138,7 +136,7 @@ namespace Bit.App.Pages
}
// TODO: Password strength check?
if (showLoading)
{
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);

View File

@@ -107,8 +107,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Label
Text="{u:I18n MasterPasswordDescription}"
@@ -146,8 +145,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label

View File

@@ -1,17 +1,17 @@
using System;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -55,11 +55,7 @@ namespace Bit.App.Pages
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[]
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
});
additionalPropertyNames: new[] { nameof(ShowPasswordIcon) });
}
public bool IsPolicyInEffect
@@ -67,7 +63,7 @@ namespace Bit.App.Pages
get => _isPolicyInEffect;
set => SetProperty(ref _isPolicyInEffect, value);
}
public bool ResetPasswordAutoEnroll
{
get => _resetPasswordAutoEnroll;
@@ -90,7 +86,6 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }
@@ -219,14 +214,13 @@ namespace Bit.App.Pages
// Request
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
{
ResetPasswordKey = encryptedKey.EncryptedString,
MasterPasswordHash = masterPasswordHash,
ResetPasswordKey = encryptedKey.EncryptedString
};
var userId = await _stateService.GetActiveUserIdAsync();
// Enroll user
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest);
}
await _deviceActionService.HideLoadingAsync();
SetPasswordSuccessAction?.Invoke();
}

View File

@@ -1,11 +1,11 @@
using System;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -47,8 +47,7 @@ namespace Bit.App.Pages
if (Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_moreItem);
}
else
} else
{
ToolbarItems.Add(_useAnotherTwoStepMethod);
}
@@ -93,8 +92,7 @@ namespace Bit.App.Pages
if (_vm.TotpMethod)
{
RequestFocus(_totpEntry);
}
else if (_vm.YubikeyMethod)
} else if (_vm.YubikeyMethod)
{
RequestFocus(_yubikeyTokenEntry);
}
@@ -189,7 +187,7 @@ namespace Bit.App.Pages
var page = new SetPasswordPage(_appOptions, _orgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();

View File

@@ -1,16 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -29,7 +29,6 @@ namespace Bit.App.Pages
private readonly IBroadcasterService _broadcasterService;
private readonly IStateService _stateService;
private readonly II18nService _i18nService;
private readonly IAppIdService _appIdService;
private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction;
@@ -50,7 +49,6 @@ namespace Bit.App.Pages
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
@@ -115,7 +113,7 @@ namespace Bit.App.Pages
public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService;
protected override IDeviceActionService deviceActionService => _deviceActionService;
@@ -295,7 +293,7 @@ namespace Bit.App.Pages
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
}
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, _captchaToken, Remember);
if (result.CaptchaNeeded)
{
if (await HandleCaptchaAsync(result.CaptchaSiteKey))
@@ -306,12 +304,12 @@ namespace Bit.App.Pages
return;
}
_captchaToken = null;
var task = Task.Run(() => _syncService.FullSyncAsync(true));
await _deviceActionService.HideLoadingAsync();
_messagingService.Send("listenYubiKeyOTP", false);
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
if (_authingWithSso && result.ResetMasterPassword)
{
StartSetPasswordAction?.Invoke();
@@ -319,7 +317,7 @@ namespace Bit.App.Pages
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
}
else
{
TwoFactorAuthSuccessAction?.Invoke();
@@ -382,8 +380,7 @@ namespace Bit.App.Pages
var request = new TwoFactorEmailRequest
{
Email = _authService.Email,
MasterPasswordHash = _authService.MasterPasswordHash,
DeviceIdentifier = await _appIdService.GetAppIdAsync()
MasterPasswordHash = _authService.MasterPasswordHash
};
await _apiService.PostTwoFactorEmailAsync(request);
if (showLoading)

View File

@@ -105,8 +105,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout StyleClass="box">
@@ -141,8 +140,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label

View File

@@ -1,7 +1,7 @@
using System;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using Bit.App.Resources;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -14,29 +14,29 @@ namespace Bit.App.Pages
private readonly string _pageName;
public UpdateTempPasswordPage()
{
{
// Service Init
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
// Binding
InitializeComponent();
_pageName = string.Concat(nameof(UpdateTempPasswordPage), "_", DateTime.UtcNow.Ticks);
_vm = BindingContext as UpdateTempPasswordPageViewModel;
_vm.Page = this;
SetActivityIndicator();
// Actions Declaration
_vm.LogOutAction = () =>
{
_messagingService.Send("logout");
};
_vm.UpdateTempPasswordSuccessAction = () => Device.BeginInvokeOnMainThread(UpdateTempPasswordSuccess);
// Link fields that will be referenced in codebehind
MasterPasswordEntry = _masterPassword;
ConfirmMasterPasswordEntry = _confirmMasterPassword;
// Return Types and Commands
_masterPassword.ReturnType = ReturnType.Next;
_masterPassword.ReturnCommand = new Command(() => _confirmMasterPassword.Focus());
@@ -77,7 +77,7 @@ namespace Bit.App.Pages
}
}
}
private void UpdateTempPasswordSuccess()
{
_messagingService.Send("logout");

View File

@@ -1,6 +1,6 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using System;
using System.Threading.Tasks;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Xamarin.Forms;
@@ -16,7 +16,7 @@ namespace Bit.App.Pages
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
SubmitCommand = new Command(async () => await SubmitAsync());
}
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
@@ -37,23 +37,23 @@ namespace Bit.App.Pages
public async Task SubmitAsync()
{
if (!await ValidateMasterPasswordAsync())
if (!await ValidateMasterPasswordAsync())
{
return;
}
// Retrieve details for key generation
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _stateService.GetEmailAsync();
// Create new key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
// Create new encKey for the User
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
// Create request
var request = new UpdateTempPasswordRequest
{
@@ -61,7 +61,7 @@ namespace Bit.App.Pages
NewMasterPasswordHash = masterPasswordHash,
MasterPasswordHint = Hint
};
// Initiate API action
try
{

View File

@@ -36,7 +36,7 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.VerificationCode;
TogglePasswordCommand = new Command(TogglePassword);
MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false);
RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false);
@@ -60,7 +60,7 @@ namespace Bit.App.Pages
get => _mainActionText;
set => SetProperty(ref _mainActionText, value);
}
public string SendCodeStatus
{
get => _sendCodeStatus;

View File

@@ -35,12 +35,12 @@ namespace Bit.App.Pages
protected async override void OnAppearing()
{
base.OnAppearing();
if (IsThemeDirty)
{
UpdateOnThemeChanged();
}
await SaveActivityAsync();
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
@@ -37,14 +37,11 @@ namespace Bit.App.Pages
bool cancelled = false;
try
{
// PrefersEphemeralWebBrowserSession should be false to allow access to the hCaptcha accessibility
// cookie set in the default browser
// https://www.hcaptcha.com/accessibility
var options = new WebAuthenticatorOptions
{
Url = new Uri(url),
CallbackUrl = new Uri(callbackUri),
PrefersEphemeralWebBrowserSession = false,
PrefersEphemeralWebBrowserSession = true,
};
authResult = await WebAuthenticator.AuthenticateAsync(options);
}

View File

@@ -48,8 +48,7 @@
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding History}"
VerticalOptions="FillAndExpand"
StyleClass="list, list-platform"
ExtraDataForLogging="Generator History Page">
StyleClass="list, list-platform">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="domain:GeneratedPasswordHistory">
<Grid

View File

@@ -31,8 +31,7 @@ namespace Bit.App.Pages
{
base.OnAppearing();
await LoadOnAppearedAsync(_mainLayout, true, async () =>
{
await LoadOnAppearedAsync(_mainLayout, true, async () => {
await _vm.InitAsync();
});
}

View File

@@ -58,7 +58,8 @@ namespace Bit.App.Pages
private async void CopyAsync(GeneratedPasswordHistory ph)
{
await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
public async Task UpdateOnThemeChanged()

View File

@@ -183,68 +183,52 @@
<Label
Text="A-Z"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Uppercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="a-z"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Lowercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n LowercaseAtoZ}" />
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="0-9"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Number}"
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="!@#$%^&amp;*"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Special}"
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper">
@@ -293,7 +277,7 @@
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding AvoidAmbiguousChars}"
IsToggled="{Binding AvoidAmbiguous}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>

View File

@@ -12,7 +12,7 @@ namespace Bit.App.Pages
public partial class GeneratorPage : BaseContentPage, IThemeDirtablePage
{
private readonly IBroadcasterService _broadcasterService;
private GeneratorPageViewModel _vm;
private readonly bool _fromTabPage;
private readonly Action<string> _selectAction;
@@ -77,7 +77,7 @@ namespace Bit.App.Pages
}
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();

View File

@@ -1,10 +1,11 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -22,7 +23,7 @@ namespace Bit.App.Pages
private bool _lowercase;
private bool _number;
private bool _special;
private bool _allowAmbiguousChars;
private bool _avoidAmbiguous;
private int _minNumber;
private int _minSpecial;
private int _length = 5;
@@ -129,29 +130,19 @@ namespace Bit.App.Pages
}
}
public bool AllowAmbiguousChars
public bool AvoidAmbiguous
{
get => _allowAmbiguousChars;
get => _avoidAmbiguous;
set
{
if (SetProperty(ref _allowAmbiguousChars, value,
additionalPropertyNames: new string[]
{
nameof(AvoidAmbiguousChars)
}))
if (SetProperty(ref _avoidAmbiguous, value))
{
_options.AllowAmbiguousChar = value;
_options.Ambiguous = !value;
var task = SaveOptionsAsync();
}
}
}
public bool AvoidAmbiguousChars
{
get => !AllowAmbiguousChars;
set => AllowAmbiguousChars = !value;
}
public int MinNumber
{
get => _minNumber;
@@ -234,7 +225,7 @@ namespace Bit.App.Pages
}
}
}
public PasswordGeneratorPolicyOptions EnforcedPolicyOptions
{
get => _enforcedPolicyOptions;
@@ -318,12 +309,13 @@ namespace Bit.App.Pages
public async Task CopyAsync()
{
await _clipboardService.CopyTextAsync(Password);
_platformUtilsService.ShowToastForCopiedValue(AppResources.Password);
_platformUtilsService.ShowToast("success", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
private void LoadFromOptions()
{
AllowAmbiguousChars = _options.AllowAmbiguousChar.GetValueOrDefault();
AvoidAmbiguous = !_options.Ambiguous.GetValueOrDefault();
TypeSelectedIndex = _options.Type == "passphrase" ? 1 : 0;
IsPassword = TypeSelectedIndex == 0;
MinNumber = _options.MinNumber.GetValueOrDefault();
@@ -341,7 +333,7 @@ namespace Bit.App.Pages
private void SetOptions()
{
_options.AllowAmbiguousChar = AllowAmbiguousChars;
_options.Ambiguous = !AvoidAmbiguous;
_options.Type = TypeSelectedIndex == 1 ? "passphrase" : "password";
_options.MinNumber = MinNumber;
_options.MinSpecial = MinSpecial;

View File

@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="Bit.App.Pages.SendAddEditPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
@@ -122,7 +121,6 @@
Clicked="FileType_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n File}"
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
Grid.Column="0">
</Button>
<Button
@@ -134,7 +132,6 @@
Clicked="TextType_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Text}"
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
Grid.Column="1">
</Button>
</Grid>
@@ -253,31 +250,28 @@
</StackLayout>
<StackLayout
Orientation="Horizontal"
Spacing="0"
xct:TouchEffect.Command="{Binding ToggleOptionsCommand}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{Binding OptionsAccessilibityText}">
Spacing="0">
<Button
Text="{u:I18n Options}"
x:Name="_btnOptions"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
Margin="0"
AutomationProperties.IsInAccessibleTree="False"/>
Clicked="ToggleOptions_Clicked"/>
<controls:IconButton
x:Name="_btnOptionsUp"
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
IsVisible="{Binding ShowOptions}"
AutomationProperties.IsInAccessibleTree="False"/>
Clicked="ToggleOptions_Clicked"
IsVisible="{Binding ShowOptions}" />
<controls:IconButton
x:Name="_btnOptionsDown"
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
AutomationProperties.IsInAccessibleTree="False"/>
Clicked="ToggleOptions_Clicked"
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" />
</StackLayout>
<StackLayout IsVisible="{Binding ShowOptions}">
<StackLayout
@@ -303,14 +297,14 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:ExtendedDatePicker
NullableDate="{Binding DeletionDateTimeViewModel.Date, Mode=TwoWay}"
NullableDate="{Binding DeletionDate, Mode=TwoWay}"
Format="d"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionDate}"
Grid.Column="0" />
<controls:ExtendedTimePicker
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
NullableTime="{Binding DeletionTime, Mode=TwoWay}"
Format="t"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True"
@@ -343,7 +337,7 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:ExtendedDatePicker
NullableDate="{Binding ExpirationDateTimeViewModel.Date, Mode=TwoWay}"
NullableDate="{Binding ExpirationDate, Mode=TwoWay}"
PlaceHolder="mm/dd/yyyy"
Format="d"
IsEnabled="{Binding SendEnabled}"
@@ -351,7 +345,7 @@
AutomationProperties.Name="{u:I18n ExpirationDate}"
Grid.Column="0" />
<controls:ExtendedTimePicker
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
NullableTime="{Binding ExpirationTime, Mode=TwoWay}"
PlaceHolder="--:-- --"
Format="t"
IsEnabled="{Binding SendEnabled}"
@@ -444,8 +438,7 @@
Command="{Binding TogglePasswordCommand}"
Margin="10,0,0,0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</StackLayout>
<Label
Text="{u:I18n PasswordInfo}"

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
@@ -23,6 +23,7 @@ namespace Bit.App.Pages
private AppOptions _appOptions;
private SendAddEditPageViewModel _vm;
public Action OnClose { get; set; }
public Action AfterSubmit { get; set; }
public SendAddEditPage(
@@ -135,7 +136,14 @@ namespace Bit.App.Pages
private async Task CloseAsync()
{
await Navigation.PopModalAsync();
if (OnClose is null)
{
await Navigation.PopModalAsync();
}
else
{
OnClose();
}
}
protected override bool OnBackButtonPressed()
@@ -201,6 +209,11 @@ namespace Bit.App.Pages
}
}
private void ToggleOptions_Clicked(object sender, EventArgs e)
{
_vm.ToggleOptions();
}
private void ClearExpirationDate_Clicked(object sender, EventArgs e)
{
if (DoOnce())
@@ -324,10 +337,10 @@ namespace Bit.App.Pages
_vm.IsAddFromShare = true;
_vm.CopyInsteadOfShareAfterSaving = _appOptions.CopyInsteadOfShareAfterSaving;
var name = _appOptions.CreateSend.Item2;
_vm.Send.Name = name;
var type = _appOptions.CreateSend.Item1;
if (type == SendType.File)
{

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
@@ -24,7 +23,7 @@ namespace Bit.App.Pages
private readonly IStateService _stateService;
private readonly ISendService _sendService;
private readonly ILogger _logger;
private bool _sendEnabled = true;
private bool _sendEnabled;
private bool _canAccessPremium;
private bool _emailVerified;
private SendView _send;
@@ -34,15 +33,17 @@ namespace Bit.App.Pages
private int _deletionDateTypeSelectedIndex;
private int _expirationDateTypeSelectedIndex;
private DateTime _simpleDeletionDateTime;
private DateTime _deletionDate;
private TimeSpan _deletionTime;
private DateTime? _simpleExpirationDateTime;
private DateTime? _expirationDate;
private TimeSpan? _expirationTime;
private bool _isOverridingPickers;
private int? _maxAccessCount;
private string[] _additionalSendProperties = new[]
private string[] _additionalSendProperties = new []
{
nameof(IsText),
nameof(IsFile),
nameof(FileTypeAccessibilityLabel),
nameof(TextTypeAccessibilityLabel)
};
private bool _disableHideEmail;
private bool _sendOptionsPolicyInEffect;
@@ -58,7 +59,6 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>("logger");
TogglePasswordCommand = new Command(TogglePassword);
ToggleOptionsCommand = new Command(ToggleOptions);
TypeOptions = new List<KeyValuePair<string, SendType>>
{
@@ -86,36 +86,9 @@ namespace Bit.App.Pages
new KeyValuePair<string, string>(AppResources.ThirtyDays, AppResources.ThirtyDays),
new KeyValuePair<string, string>(AppResources.Custom, AppResources.Custom),
};
DeletionDateTimeViewModel = new DateTimeViewModel(AppResources.DeletionDate, AppResources.DeletionTime);
ExpirationDateTimeViewModel = new DateTimeViewModel(AppResources.ExpirationDate, AppResources.ExpirationTime)
{
OnDateChanged = date =>
{
if (!_isOverridingPickers && !ExpirationDateTimeViewModel.Time.HasValue)
{
// auto-set time to current time upon setting date
ExpirationDateTimeViewModel.Time = DateTimeNow().TimeOfDay;
}
},
OnTimeChanged = time =>
{
if (!_isOverridingPickers && !ExpirationDateTimeViewModel.Date.HasValue)
{
// auto-set date to current date upon setting time
ExpirationDateTimeViewModel.Date = DateTime.Today;
}
},
DatePlaceholder = "mm/dd/yyyy",
TimePlaceholder = "--:-- --"
};
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command TogglePasswordCommand { get; set; }
public Command ToggleOptionsCommand { get; set; }
public string SendId { get; set; }
public int SegmentedButtonHeight { get; set; }
public int SegmentedButtonFontSize { get; set; }
@@ -129,7 +102,6 @@ namespace Bit.App.Pages
public bool DisableHideEmailControl { get; set; }
public bool IsAddFromShare { get; set; }
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
@@ -149,15 +121,20 @@ namespace Bit.App.Pages
}
}
}
public DateTime DeletionDate
{
get => _deletionDate;
set => SetProperty(ref _deletionDate, value);
}
public TimeSpan DeletionTime
{
get => _deletionTime;
set => SetProperty(ref _deletionTime, value);
}
public bool ShowOptions
{
get => _showOptions;
set => SetProperty(ref _showOptions, value,
additionalPropertyNames: new[]
{
nameof(OptionsAccessilibityText),
nameof(OptionsShowHideIcon)
});
set => SetProperty(ref _showOptions, value);
}
public int ExpirationDateTypeSelectedIndex
{
@@ -170,7 +147,28 @@ namespace Bit.App.Pages
}
}
}
public DateTime? ExpirationDate
{
get => _expirationDate;
set
{
if (SetProperty(ref _expirationDate, value))
{
ExpirationDateChanged();
}
}
}
public TimeSpan? ExpirationTime
{
get => _expirationTime;
set
{
if (SetProperty(ref _expirationTime, value))
{
ExpirationTimeChanged();
}
}
}
public int? MaxAccessCount
{
get => _maxAccessCount;
@@ -198,7 +196,7 @@ namespace Bit.App.Pages
}
public string FileName
{
get => _fileName ?? AppResources.NoFileChosen;
get => _fileName;
set
{
if (SetProperty(ref _fileName, value))
@@ -211,10 +209,9 @@ namespace Bit.App.Pages
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new[]
additionalPropertyNames: new []
{
nameof(ShowPasswordIcon),
nameof(PasswordVisibilityAccessibilityText)
nameof(ShowPasswordIcon)
});
}
public bool DisableHideEmail
@@ -233,20 +230,14 @@ namespace Bit.App.Pages
public bool IsFile => Send?.Type == SendType.File;
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
public DateTimeViewModel DeletionDateTimeViewModel { get; }
public DateTimeViewModel ExpirationDateTimeViewModel { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
public string OptionsShowHideIcon => ShowOptions ? BitwardenIcons.ChevronUp : BitwardenIcons.AngleDown;
public async Task InitAsync()
{
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
_canAccessPremium = await _stateService.CanAccessPremiumAsync();
_emailVerified = await _stateService.GetEmailVerifiedAsync();
SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync();
SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail;
}
@@ -264,8 +255,10 @@ namespace Bit.App.Pages
return false;
}
Send = await send.DecryptAsync();
DeletionDateTimeViewModel.DateTime = Send.DeletionDate.ToLocalTime();
ExpirationDateTimeViewModel.DateTime = Send.ExpirationDate?.ToLocalTime();
DeletionDate = Send.DeletionDate.ToLocalTime();
DeletionTime = DeletionDate.TimeOfDay;
ExpirationDate = Send.ExpirationDate?.ToLocalTime();
ExpirationTime = ExpirationDate?.TimeOfDay;
}
else
{
@@ -274,7 +267,8 @@ namespace Bit.App.Pages
{
Type = Type.GetValueOrDefault(defaultType),
};
DeletionDateTimeViewModel.DateTime = DateTimeNow().AddDays(7);
_deletionDate = DateTimeNow().AddDays(7);
_deletionTime = DeletionDate.TimeOfDay;
DeletionDateTypeSelectedIndex = 4;
ExpirationDateTypeSelectedIndex = 0;
}
@@ -298,22 +292,23 @@ namespace Bit.App.Pages
public void ClearExpirationDate()
{
_isOverridingPickers = true;
ExpirationDateTimeViewModel.DateTime = null;
ExpirationDate = null;
ExpirationTime = null;
_isOverridingPickers = false;
}
private void UpdateSendData()
{
// filename
if (Send.File != null && _fileName != null)
if (Send.File != null && FileName != null)
{
Send.File.FileName = _fileName;
Send.File.FileName = FileName;
}
// deletion date
if (ShowDeletionCustomPickers)
{
Send.DeletionDate = DeletionDateTimeViewModel.DateTime.Value.ToUniversalTime();
Send.DeletionDate = DeletionDate.Date.Add(DeletionTime).ToUniversalTime();
}
else
{
@@ -321,9 +316,9 @@ namespace Bit.App.Pages
}
// expiration date
if (ShowExpirationCustomPickers && ExpirationDateTimeViewModel.DateTime.HasValue)
if (ShowExpirationCustomPickers && ExpirationDate.HasValue && ExpirationTime.HasValue)
{
Send.ExpirationDate = ExpirationDateTimeViewModel.DateTime.Value.ToUniversalTime();
Send.ExpirationDate = ExpirationDate.Value.Date.Add(ExpirationTime.Value).ToUniversalTime();
}
else if (_simpleExpirationDateTime.HasValue)
{
@@ -386,7 +381,7 @@ namespace Bit.App.Pages
UpdateSendData();
if (string.IsNullOrWhiteSpace(NewPassword))
if (string.IsNullOrWhiteSpace(NewPassword))
{
NewPassword = null;
}
@@ -476,7 +471,7 @@ namespace Bit.App.Pages
return;
}
if (Page is SendAddOnlyPage sendPage && sendPage.OnClose != null)
if (Page is SendAddEditPage sendPage && sendPage.OnClose != null)
{
sendPage.OnClose();
return;
@@ -526,7 +521,7 @@ namespace Bit.App.Pages
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
}
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
@@ -617,6 +612,24 @@ namespace Bit.App.Pages
}
}
private void ExpirationDateChanged()
{
if (!_isOverridingPickers && !ExpirationTime.HasValue)
{
// auto-set time to current time upon setting date
ExpirationTime = DateTimeNow().TimeOfDay;
}
}
private void ExpirationTimeChanged()
{
if (!_isOverridingPickers && !ExpirationDate.HasValue)
{
// auto-set date to current date upon setting time
ExpirationDate = DateTime.Today;
}
}
private void MaxAccessCountChanged()
{
Send.MaxAccessCount = _maxAccessCount;
@@ -640,10 +653,5 @@ namespace Bit.App.Pages
DateTimeKind.Local
);
}
internal void TriggerSendTextPropertyChanged()
{
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(Send)));
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.SendGroupingsPage"
@@ -138,8 +138,7 @@
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Send Groupings Page" />
StyleClass="list, list-platform" />
</RefreshView>
</StackLayout>
</ResourceDictionary>

View File

@@ -1,11 +1,10 @@
using System;
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -69,28 +68,21 @@ namespace Bit.App.Pages
_broadcasterService.Subscribe(_pageName, async (message) =>
{
try
if (message.Command == "syncStarted")
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted" || message.Command == "sendUpdated")
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
Device.BeginInvokeOnMainThread(() => IsBusy = true);
}
catch (Exception ex)
else if (message.Command == "syncCompleted" || message.Command == "sendUpdated")
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
});
@@ -141,7 +133,7 @@ namespace Bit.App.Pages
}
}
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ExtendedCollectionView)sender).SelectedItem = null;
@@ -168,7 +160,7 @@ namespace Bit.App.Pages
{
if (DoOnce())
{
var page = new SendsPage(_vm.Filter, _vm.Type);
var page = new SendsPage(_vm.Filter, _vm.Type != null);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
@@ -182,7 +174,7 @@ namespace Bit.App.Pages
{
await _vaultTimeoutService.LockAsync(true, true);
}
private void About_Clicked(object sender, EventArgs e)
{
_vm.ShowAbout();

View File

@@ -9,6 +9,6 @@
}
public string Title { get; }
public string ItemCount { get; }
public string ItemCount { get; }
}
}

View File

@@ -136,7 +136,7 @@ namespace Bit.App.Pages
ShowNoData = false;
Loading = true;
ShowList = false;
SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
var groupedSends = new List<SendGroupingsPageListGroup>();
var page = Page as SendGroupingsPage;

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
@@ -66,8 +66,7 @@
VerticalOptions="FillAndExpand"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Sends Page">
StyleClass="list, list-platform">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:SendView">
<controls:SendViewCell

View File

@@ -2,7 +2,6 @@
using System.Linq;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Xamarin.Forms;
@@ -13,22 +12,15 @@ namespace Bit.App.Pages
private SendsPageViewModel _vm;
private bool _hasFocused;
public SendsPage(Func<SendView, bool> filter, SendType? type = null)
public SendsPage(Func<SendView, bool> filter, bool type = false)
{
InitializeComponent();
_vm = BindingContext as SendsPageViewModel;
_vm.Page = this;
_vm.Filter = filter;
if (type != null)
if (type)
{
if (type == SendType.File)
{
_vm.PageTitle = AppResources.SearchFileSends;
}
else if (type == SendType.Text)
{
_vm.PageTitle = AppResources.SearchTextSends;
}
_vm.PageTitle = AppResources.SearchType;
}
else
{
@@ -41,7 +33,6 @@ namespace Bit.App.Pages
_searchBar.Placeholder = AppResources.Search;
_mainLayout.Children.Insert(0, _searchBar);
_mainLayout.Children.Insert(1, _separator);
ShowModalAnimationDelay = 0;
}
else
{

View File

@@ -35,11 +35,11 @@ namespace Bit.App.Pages
get => _sendEnabled;
set => SetProperty(ref _sendEnabled, value);
}
public bool ShowNoData
{
get => _showNoData;
set => SetProperty(ref _showNoData, value, additionalPropertyNames: new[]
set => SetProperty(ref _showNoData, value, additionalPropertyNames: new []
{
nameof(ShowSearchDirection)
});
@@ -48,7 +48,7 @@ namespace Bit.App.Pages
public bool ShowList
{
get => _showList;
set => SetProperty(ref _showList, value, additionalPropertyNames: new[]
set => SetProperty(ref _showList, value, additionalPropertyNames: new []
{
nameof(ShowSearchDirection)
});
@@ -58,7 +58,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
SendEnabled = !await AppHelpers.IsSendDisabledByPolicyAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
if (!string.IsNullOrWhiteSpace((Page as SendsPage).SearchBar.Text))
{
Search((Page as SendsPage).SearchBar.Text, 200);

View File

@@ -49,12 +49,12 @@ namespace Bit.App.Pages
_vm.ToggleAutofillService();
}
}
private void ToggleInlineAutofill(object sender, EventArgs e)
{
_vm.ToggleInlineAutofill();
}
private void ToggleAccessibility(object sender, EventArgs e)
{
if (DoOnce())
@@ -62,7 +62,7 @@ namespace Bit.App.Pages
_vm.ToggleAccessibility();
}
}
private void ToggleDrawOver(object sender, EventArgs e)
{
if (DoOnce())

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