mirror of
https://github.com/bitwarden/mobile
synced 2025-12-14 23:33:34 +00:00
Compare commits
1 Commits
v2022.8.0
...
bug/ps-675
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b54371dacd |
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -57,14 +57,9 @@ jobs:
|
|||||||
|
|
||||||
android:
|
android:
|
||||||
name: Android
|
name: Android
|
||||||
runs-on: windows-2022
|
runs-on: windows-2019
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
- name: Setup NuGet
|
|
||||||
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
|
||||||
with:
|
|
||||||
nuget-version: 5.9.0
|
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
@@ -212,13 +207,8 @@ jobs:
|
|||||||
|
|
||||||
f-droid:
|
f-droid:
|
||||||
name: F-Droid Build
|
name: F-Droid Build
|
||||||
runs-on: windows-2022
|
runs-on: windows-2019
|
||||||
steps:
|
steps:
|
||||||
- name: Setup NuGet
|
|
||||||
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
|
||||||
with:
|
|
||||||
nuget-version: 5.9.0
|
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
@@ -378,11 +368,6 @@ jobs:
|
|||||||
runs-on: macos-11
|
runs-on: macos-11
|
||||||
needs: setup
|
needs: setup
|
||||||
steps:
|
steps:
|
||||||
- name: Setup NuGet
|
|
||||||
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
|
||||||
with:
|
|
||||||
nuget-version: 5.9.0
|
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
nuget help | grep Version
|
nuget help | grep Version
|
||||||
|
|||||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -13,11 +13,6 @@ on:
|
|||||||
- Initial Release
|
- Initial Release
|
||||||
- Redeploy
|
- Redeploy
|
||||||
- Dry Run
|
- Dry Run
|
||||||
fdroid_publish:
|
|
||||||
description: 'Publish to f-droid store'
|
|
||||||
required: true
|
|
||||||
default: true
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -54,26 +49,17 @@ jobs:
|
|||||||
echo "::set-output name=branch-name::$BRANCH_NAME"
|
echo "::set-output name=branch-name::$BRANCH_NAME"
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
|
||||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
branch: ${{ steps.branch.outputs.branch-name }}
|
branch: ${{ steps.branch.outputs.branch-name }}
|
||||||
|
|
||||||
- name: Download all artifacts
|
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
|
||||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
|
||||||
with:
|
|
||||||
workflow: build.yml
|
|
||||||
workflow_conclusion: success
|
|
||||||
branch: master
|
|
||||||
|
|
||||||
- name: Prep Bitwarden iOS release asset
|
- name: Prep Bitwarden iOS release asset
|
||||||
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
|
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: github.event.inputs.release_type != 'Dry Run'
|
||||||
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
|
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
|
||||||
with:
|
with:
|
||||||
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
|
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
|
||||||
@@ -92,13 +78,11 @@ jobs:
|
|||||||
name: F-Droid Release
|
name: F-Droid Release
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
needs: release
|
needs: release
|
||||||
if: inputs.fdroid_publish
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||||
|
|
||||||
- name: Download F-Droid .apk artifact
|
- name: Download F-Droid .apk artifact
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
|
||||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
@@ -106,15 +90,6 @@ jobs:
|
|||||||
branch: ${{ needs.release.outputs.branch-name }}
|
branch: ${{ needs.release.outputs.branch-name }}
|
||||||
name: com.x8bit.bitwarden-fdroid.apk
|
name: com.x8bit.bitwarden-fdroid.apk
|
||||||
|
|
||||||
- name: Download F-Droid .apk artifact
|
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
|
||||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
|
||||||
with:
|
|
||||||
workflow: build.yml
|
|
||||||
workflow_conclusion: success
|
|
||||||
branch: master
|
|
||||||
name: com.x8bit.bitwarden-fdroid.apk
|
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
|
uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
|
||||||
with:
|
with:
|
||||||
@@ -180,5 +155,5 @@ jobs:
|
|||||||
cd $GITHUB_WORKSPACE
|
cd $GITHUB_WORKSPACE
|
||||||
|
|
||||||
- name: Deploy to gh-pages
|
- name: Deploy to gh-pages
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: github.event.inputs.release_type != 'Dry Run'
|
||||||
run: npm run deploy
|
run: npm run deploy
|
||||||
|
|||||||
24
.github/workflows/version-bump.yml
vendored
24
.github/workflows/version-bump.yml
vendored
@@ -19,6 +19,12 @@ jobs:
|
|||||||
- name: Create Version Branch
|
- name: Create Version Branch
|
||||||
run: |
|
run: |
|
||||||
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
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
|
- name: Bump Version - Android XML
|
||||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||||
@@ -50,32 +56,16 @@ jobs:
|
|||||||
version: ${{ github.event.inputs.version_number }}
|
version: ${{ github.event.inputs.version_number }}
|
||||||
file_path: "./src/iOS/Info.plist"
|
file_path: "./src/iOS/Info.plist"
|
||||||
|
|
||||||
- name: Setup git
|
- name: Commit files
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "github-actions[bot]"
|
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
|
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
||||||
|
|
||||||
- name: Push changes
|
- name: Push changes
|
||||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
|
||||||
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
- name: Create Version PR
|
- name: Create Version PR
|
||||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
|
||||||
env:
|
env:
|
||||||
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
|
|||||||
|
|
||||||
# Build/Run
|
# 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.
|
Please refer to the [Mobile section](https://contributing.bitwarden.com/clients/mobile) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
|
||||||
|
|
||||||
# We're Hiring!
|
# We're Hiring!
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ namespace Bit.Droid.Accessibility
|
|||||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||||
new Browser("com.google.android.apps.chrome_dev", "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.
|
// 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.jamal2367.styx", "search"),
|
||||||
new Browser("com.kiwibrowser.browser", "url_bar"),
|
new Browser("com.kiwibrowser.browser", "url_bar"),
|
||||||
new Browser("com.kiwibrowser.browser.dev", "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.naver.whale", "url_bar"),
|
||||||
new Browser("com.opera.browser", "url_field"),
|
new Browser("com.opera.browser", "url_field"),
|
||||||
new Browser("com.opera.browser.beta", "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", "url_field"),
|
||||||
new Browser("com.opera.mini.native.beta", "url_field"),
|
new Browser("com.opera.mini.native.beta", "url_field"),
|
||||||
new Browser("com.opera.touch", "addressbarEdit"),
|
new Browser("com.opera.touch", "addressbarEdit"),
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ namespace Bit.Droid.Autofill
|
|||||||
"com.google.android.apps.chrome",
|
"com.google.android.apps.chrome",
|
||||||
"com.google.android.apps.chrome_dev",
|
"com.google.android.apps.chrome_dev",
|
||||||
"com.google.android.captiveportallogin",
|
"com.google.android.captiveportallogin",
|
||||||
"com.iode.firefox",
|
|
||||||
"com.jamal2367.styx",
|
"com.jamal2367.styx",
|
||||||
"com.kiwibrowser.browser",
|
"com.kiwibrowser.browser",
|
||||||
"com.kiwibrowser.browser.dev",
|
"com.kiwibrowser.browser.dev",
|
||||||
@@ -87,7 +86,6 @@ namespace Bit.Droid.Autofill
|
|||||||
"com.naver.whale",
|
"com.naver.whale",
|
||||||
"com.opera.browser",
|
"com.opera.browser",
|
||||||
"com.opera.browser.beta",
|
"com.opera.browser.beta",
|
||||||
"com.opera.gx",
|
|
||||||
"com.opera.mini.native",
|
"com.opera.mini.native",
|
||||||
"com.opera.mini.native.beta",
|
"com.opera.mini.native.beta",
|
||||||
"com.opera.touch",
|
"com.opera.touch",
|
||||||
|
|||||||
@@ -64,11 +64,10 @@ namespace Bit.Droid
|
|||||||
Intent?.Validate();
|
Intent?.Validate();
|
||||||
|
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
|
if (!CoreHelpers.InDebugMode())
|
||||||
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
|
|
||||||
{
|
{
|
||||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||||
});
|
}
|
||||||
|
|
||||||
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
ServiceContainer.Resolve<ILogger>("logger").InitAsync();
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ using System.Net;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.App.Pages;
|
using Bit.App.Pages;
|
||||||
using Bit.App.Utilities.AccountManagement;
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.App.Controls;
|
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
using Android.Gms.Security;
|
using Android.Gms.Security;
|
||||||
#endif
|
#endif
|
||||||
@@ -70,8 +69,7 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||||
ServiceContainer.Resolve<IAuthService>("authService"),
|
ServiceContainer.Resolve<IAuthService>("authService"));
|
||||||
ServiceContainer.Resolve<ILogger>("logger"));
|
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
@@ -101,13 +99,12 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
|
ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
|
||||||
#if FDROID
|
#if FDROID
|
||||||
var logger = new StubLogger();
|
ServiceContainer.Register<ILogger>("logger", new StubLogger());
|
||||||
#elif DEBUG
|
#elif DEBUG
|
||||||
var logger = DebugLogger.Instance;
|
ServiceContainer.Register<ILogger>("logger", DebugLogger.Instance);
|
||||||
#else
|
#else
|
||||||
var logger = Logger.Instance;
|
ServiceContainer.Register<ILogger>("logger", Logger.Instance);
|
||||||
#endif
|
#endif
|
||||||
ServiceContainer.Register("logger", logger);
|
|
||||||
|
|
||||||
// Note: This might cause a race condition. Investigate more.
|
// Note: This might cause a race condition. Investigate more.
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
@@ -127,13 +124,13 @@ namespace Bit.Droid
|
|||||||
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
||||||
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
||||||
var localizeService = new LocalizeService();
|
var localizeService = new LocalizeService();
|
||||||
var broadcasterService = new BroadcasterService(logger);
|
var broadcasterService = new BroadcasterService();
|
||||||
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
||||||
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
||||||
var secureStorageService = new SecureStorageService();
|
var secureStorageService = new SecureStorageService();
|
||||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||||
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
|
var stateService = new StateService(mobileStorageService, secureStorageService);
|
||||||
var stateMigrationService =
|
var stateMigrationService =
|
||||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||||
var clipboardService = new ClipboardService(stateService);
|
var clipboardService = new ClipboardService(stateService);
|
||||||
@@ -162,7 +159,6 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||||
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
|
||||||
|
|
||||||
// Push
|
// Push
|
||||||
#if FDROID
|
#if FDROID
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.8.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.05.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
@@ -54,4 +54,4 @@
|
|||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -77,9 +77,6 @@
|
|||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.google.android.captiveportallogin"
|
android:name="com.google.android.captiveportallogin"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
|
||||||
android:name="com.iode.firefox"
|
|
||||||
android:maxLongVersionCode="10000000000"/>
|
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.jamal2367.styx"
|
android:name="com.jamal2367.styx"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
@@ -119,9 +116,6 @@
|
|||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.opera.browser.beta"
|
android:name="com.opera.browser.beta"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
|
||||||
android:name="com.opera.gx"
|
|
||||||
android:maxLongVersionCode="10000000000"/>
|
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.opera.mini.native"
|
android:name="com.opera.mini.native"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
|||||||
@@ -28,25 +28,17 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||||
{
|
{
|
||||||
try
|
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
|
||||||
|
if ((int)Build.VERSION.SdkInt < 33)
|
||||||
{
|
{
|
||||||
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
|
await Clipboard.SetTextAsync(text);
|
||||||
if ((int)Build.VERSION.SdkInt < 33)
|
}
|
||||||
{
|
else
|
||||||
await Clipboard.SetTextAsync(text);
|
{
|
||||||
}
|
CopyToClipboard(text, isSensitive);
|
||||||
else
|
}
|
||||||
{
|
|
||||||
CopyToClipboard(text, isSensitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
await ClearClipboardAlarmAsync(expiresInMs);
|
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()
|
public bool IsCopyNotificationHandledByPlatform()
|
||||||
|
|||||||
@@ -948,21 +948,5 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
// for any Android-specific cleanup required after switching accounts
|
// 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ namespace Bit.Droid.Utilities
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(theme) && osDarkModeEnabled)
|
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;
|
LightTheme = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,5 @@ namespace Bit.App.Abstractions
|
|||||||
{
|
{
|
||||||
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
|
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
|
||||||
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
|
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
|
||||||
Task LogOutAsync(string userId, bool userInitiated, bool expired);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,5 @@ namespace Bit.App.Abstractions
|
|||||||
bool SupportsFido2();
|
bool SupportsFido2();
|
||||||
float GetSystemFontSizeScale();
|
float GetSystemFontSizeScale();
|
||||||
Task OnAccountSwitchCompleteAsync();
|
Task OnAccountSwitchCompleteAsync();
|
||||||
Task SetScreenCaptureAllowedAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,10 +129,12 @@
|
|||||||
<Folder Include="Behaviors\" />
|
<Folder Include="Behaviors\" />
|
||||||
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
<Folder Include="Controls\AccountSwitchingOverlay\" />
|
||||||
<Folder Include="Utilities\AccountManagement\" />
|
<Folder Include="Utilities\AccountManagement\" />
|
||||||
<Folder Include="Controls\DateTime\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
|
||||||
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
|
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@@ -160,6 +162,12 @@
|
|||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="Styles\Base.xaml">
|
||||||
|
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Resources\AppResources.cs.Designer.cs">
|
<Compile Update="Resources\AppResources.cs.Designer.cs">
|
||||||
<DependentUpon>AppResources.cs.resx</DependentUpon>
|
<DependentUpon>AppResources.cs.resx</DependentUpon>
|
||||||
@@ -414,6 +422,5 @@
|
|||||||
<None Remove="Xamarin.CommunityToolkit" />
|
<None Remove="Xamarin.CommunityToolkit" />
|
||||||
<None Remove="Controls\AccountSwitchingOverlay\" />
|
<None Remove="Controls\AccountSwitchingOverlay\" />
|
||||||
<None Remove="Utilities\AccountManagement\" />
|
<None Remove="Utilities\AccountManagement\" />
|
||||||
<None Remove="Controls\DateTime\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using Bit.App.Utilities.AccountManagement;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.Xaml;
|
using Xamarin.Forms.Xaml;
|
||||||
@@ -57,93 +56,86 @@ namespace Bit.App
|
|||||||
Bootstrap();
|
Bootstrap();
|
||||||
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
_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;
|
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
||||||
var confirmed = true;
|
|
||||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
|
||||||
AppResources.Ok : details.ConfirmText;
|
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
||||||
{
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else if (message.Command == "slept")
|
|
||||||
{
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
|
||||||
{
|
{
|
||||||
await SleptAsync();
|
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
|
||||||
}
|
}
|
||||||
}
|
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||||
else if (message.Command == "migrated")
|
});
|
||||||
|
}
|
||||||
|
else if (message.Command == "resumed")
|
||||||
|
{
|
||||||
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
ResumedAsync().FireAndForget();
|
||||||
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()));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else if (message.Command == "slept")
|
||||||
{
|
{
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
await SleptAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == "migrated")
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||||
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
||||||
|
message.Command == "popAllAndGoToTabMyVault" ||
|
||||||
|
message.Command == "popAllAndGoToTabSend" ||
|
||||||
|
message.Command == "popAllAndGoToAutofillCiphers")
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
if (Current.MainPage is TabsPage tabsPage)
|
||||||
|
{
|
||||||
|
while (tabsPage.Navigation.ModalStack.Count > 0)
|
||||||
|
{
|
||||||
|
await tabsPage.Navigation.PopModalAsync(false);
|
||||||
|
}
|
||||||
|
if (message.Command == "popAllAndGoToAutofillCiphers")
|
||||||
|
{
|
||||||
|
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
|
||||||
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabMyVault")
|
||||||
|
{
|
||||||
|
Options.MyVaultTile = false;
|
||||||
|
tabsPage.ResetToVaultPage();
|
||||||
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabGenerator")
|
||||||
|
{
|
||||||
|
Options.GeneratorTile = false;
|
||||||
|
tabsPage.ResetToGeneratorPage();
|
||||||
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabSend")
|
||||||
|
{
|
||||||
|
tabsPage.ResetToSendPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (message.Command == "convertAccountToKeyConnector")
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
await Application.Current.MainPage.Navigation.PushModalAsync(
|
||||||
|
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -210,7 +202,6 @@ namespace Bit.App
|
|||||||
|
|
||||||
private async Task ResumedAsync()
|
private async Task ResumedAsync()
|
||||||
{
|
{
|
||||||
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
_messagingService.Send("startEventTimer");
|
_messagingService.Send("startEventTimer");
|
||||||
await UpdateThemeAsync();
|
await UpdateThemeAsync();
|
||||||
@@ -301,7 +292,7 @@ namespace Bit.App
|
|||||||
UpdateThemeAsync();
|
UpdateThemeAsync();
|
||||||
};
|
};
|
||||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||||
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
|
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
|
||||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public bool LongPressAccountEnabled { get; set; } = true;
|
public bool LongPressAccountEnabled { get; set; } = true;
|
||||||
|
|
||||||
public Action AfterHide { get; set; }
|
|
||||||
|
|
||||||
public async Task ToggleVisibilityAsync()
|
public async Task ToggleVisibilityAsync()
|
||||||
{
|
{
|
||||||
if (IsVisible)
|
if (IsVisible)
|
||||||
@@ -139,8 +137,6 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
// remove overlay
|
// remove overlay
|
||||||
IsVisible = false;
|
IsVisible = false;
|
||||||
|
|
||||||
AfterHide?.Invoke();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,28 +45,23 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public ICommand LongPressAccountCommand { get; }
|
public ICommand LongPressAccountCommand { get; }
|
||||||
|
|
||||||
public bool FromIOSExtension { get; set; }
|
|
||||||
|
|
||||||
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
private async Task SelectAccountAsync(AccountViewCellViewModel item)
|
||||||
{
|
{
|
||||||
if (!item.AccountView.IsAccount)
|
if (item.AccountView.IsAccount)
|
||||||
{
|
{
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.ADD_ACCOUNT);
|
if (!item.AccountView.IsActive)
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item.AccountView.IsActive)
|
|
||||||
{
|
|
||||||
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
|
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
|
||||||
if (FromIOSExtension)
|
|
||||||
{
|
{
|
||||||
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ namespace Bit.App.Controls
|
|||||||
public AccountViewCellViewModel(AccountView accountView)
|
public AccountViewCellViewModel(AccountView accountView)
|
||||||
{
|
{
|
||||||
AccountView = accountView;
|
AccountView = accountView;
|
||||||
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
|
AvatarImageSource = new AvatarImageSource(AccountView.Name, AccountView.Email);
|
||||||
?.GetOrCreateAvatar(AccountView.Name, AccountView.Email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountView AccountView
|
public AccountView AccountView
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
@@ -51,7 +50,7 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
private Stream Draw()
|
private Stream Draw()
|
||||||
{
|
{
|
||||||
string chars;
|
string chars = null;
|
||||||
string upperData = null;
|
string upperData = null;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_data))
|
if (string.IsNullOrEmpty(_data))
|
||||||
@@ -72,83 +71,62 @@ namespace Bit.App.Controls
|
|||||||
var textColor = Color.White;
|
var textColor = Color.White;
|
||||||
var size = 50;
|
var size = 50;
|
||||||
|
|
||||||
using (var bitmap = new SKBitmap(size * 2,
|
var bitmap = new SKBitmap(
|
||||||
|
size * 2,
|
||||||
size * 2,
|
size * 2,
|
||||||
SKImageInfo.PlatformColorType,
|
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))
|
IsAntialias = true,
|
||||||
{
|
Style = SKPaintStyle.Fill,
|
||||||
canvas.Clear(SKColors.Transparent);
|
StrokeJoin = SKStrokeJoin.Miter,
|
||||||
using (var paint = new SKPaint
|
Color = SKColor.Parse(bgColor.ToHex())
|
||||||
{
|
};
|
||||||
IsAntialias = true,
|
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
||||||
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;
|
|
||||||
|
|
||||||
using (var circlePaint = new SKPaint
|
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
|
||||||
{
|
var textSize = midX / 1.3f;
|
||||||
IsAntialias = true,
|
var textPaint = new SKPaint
|
||||||
Style = SKPaintStyle.Fill,
|
{
|
||||||
StrokeJoin = SKStrokeJoin.Miter,
|
IsAntialias = true,
|
||||||
Color = SKColor.Parse(bgColor.ToHex())
|
Style = SKPaintStyle.Fill,
|
||||||
})
|
Color = SKColor.Parse(textColor.ToHex()),
|
||||||
{
|
TextSize = textSize,
|
||||||
canvas.DrawCircle(midX, midY, radius, circlePaint);
|
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);
|
return SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100).AsStream();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFirstLetters(string data, int charCount)
|
private string GetFirstLetters(string data, int charCount)
|
||||||
{
|
{
|
||||||
var sanitizedData = data.Trim();
|
var parts = data.Split();
|
||||||
var parts = sanitizedData.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
if (parts.Length > 1 && charCount <= 2)
|
if (parts.Length > 1 && charCount <= 2)
|
||||||
{
|
{
|
||||||
var text = string.Empty;
|
var text = "";
|
||||||
for (var i = 0; i < charCount; i++)
|
for (int i = 0; i < charCount; i++)
|
||||||
{
|
{
|
||||||
text += parts[i][0];
|
text += parts[i].Substring(0, 1);
|
||||||
}
|
}
|
||||||
return text;
|
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)
|
private Color StringToColor(string str)
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -72,7 +72,8 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
<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>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
|
|||||||
@@ -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()
|
private async Task SubmitSuccessAsync()
|
||||||
{
|
{
|
||||||
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
|
_platformUtilsService.ShowToast("success", null, AppResources.EnvironmentSaved);
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class EnvironmentPageViewModel : BaseViewModel
|
public class EnvironmentPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IEnvironmentService _environmentService;
|
private readonly IEnvironmentService _environmentService;
|
||||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
|
||||||
|
|
||||||
public EnvironmentPageViewModel()
|
public EnvironmentPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -24,10 +22,10 @@ namespace Bit.App.Pages
|
|||||||
IdentityUrl = _environmentService.IdentityUrl;
|
IdentityUrl = _environmentService.IdentityUrl;
|
||||||
IconsUrl = _environmentService.IconsUrl;
|
IconsUrl = _environmentService.IconsUrl;
|
||||||
NotificationsUrls = _environmentService.NotificationsUrl;
|
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 BaseUrl { get; set; }
|
||||||
public string ApiUrl { get; set; }
|
public string ApiUrl { get; set; }
|
||||||
public string IdentityUrl { get; set; }
|
public string IdentityUrl { get; set; }
|
||||||
@@ -39,12 +37,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task SubmitAsync()
|
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
|
var resUrls = await _environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
|
||||||
{
|
{
|
||||||
Base = BaseUrl,
|
Base = BaseUrl,
|
||||||
@@ -65,25 +57,5 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
SubmitSuccessAction?.Invoke();
|
SubmitSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ValidateUrls()
|
|
||||||
{
|
|
||||||
bool IsUrlValid(string url)
|
|
||||||
{
|
|
||||||
return string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute);
|
|
||||||
}
|
|
||||||
|
|
||||||
return IsUrlValid(BaseUrl)
|
|
||||||
&& IsUrlValid(ApiUrl)
|
|
||||||
&& IsUrlValid(IdentityUrl)
|
|
||||||
&& IsUrlValid(WebVaultUrl)
|
|
||||||
&& IsUrlValid(IconsUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSubmitException(Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Value.Exception(ex);
|
|
||||||
Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||||
}
|
}
|
||||||
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
|
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "updatedTheme")
|
if (message.Command == "updatedTheme")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -80,8 +80,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="_passwordGrid"
|
x:Name="_passwordGrid"
|
||||||
@@ -120,7 +119,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ namespace Bit.App.Pages
|
|||||||
_vm = BindingContext as LockPageViewModel;
|
_vm = BindingContext as LockPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
|
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
|
||||||
|
MasterPasswordEntry = _masterPassword;
|
||||||
|
PinEntry = _pin;
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
@@ -36,17 +38,8 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entry SecretEntry
|
public Entry MasterPasswordEntry { get; set; }
|
||||||
{
|
public Entry PinEntry { get; set; }
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_vm?.PinLock ?? false)
|
|
||||||
{
|
|
||||||
return _pin;
|
|
||||||
}
|
|
||||||
return _masterPassword;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PromptBiometricAfterResumeAsync()
|
public async Task PromptBiometricAfterResumeAsync()
|
||||||
{
|
{
|
||||||
@@ -77,12 +70,16 @@ namespace Bit.App.Pages
|
|||||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||||
|
|
||||||
await _vm.InitAsync();
|
await _vm.InitAsync();
|
||||||
|
|
||||||
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
|
||||||
|
|
||||||
if (!_vm.BiometricLock)
|
if (!_vm.BiometricLock)
|
||||||
{
|
{
|
||||||
RequestFocus(SecretEntry);
|
if (_vm.PinLock)
|
||||||
|
{
|
||||||
|
RequestFocus(PinEntry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RequestFocus(MasterPasswordEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
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()
|
protected override bool OnBackButtonPressed()
|
||||||
{
|
{
|
||||||
if (_accountListOverlay.IsVisible)
|
if (_accountListOverlay.IsVisible)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.Helpers;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -28,7 +27,6 @@ namespace Bit.App.Pages
|
|||||||
private readonly IBiometricService _biometricService;
|
private readonly IBiometricService _biometricService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
|
||||||
|
|
||||||
private string _email;
|
private string _email;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
@@ -131,15 +129,11 @@ namespace Bit.App.Pages
|
|||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string Pin { get; set; }
|
public string Pin { get; set; }
|
||||||
public Action UnlockedAction { get; set; }
|
public Action UnlockedAction { get; set; }
|
||||||
public event Action<int?> FocusSecretEntry
|
|
||||||
{
|
|
||||||
add => _secretEntryFocusWeakEventManager.AddEventHandler(value);
|
|
||||||
remove => _secretEntryFocusWeakEventManager.RemoveEventHandler(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
@@ -353,8 +347,11 @@ namespace Bit.App.Pages
|
|||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
ShowPassword = !ShowPassword;
|
ShowPassword = !ShowPassword;
|
||||||
var secret = PinLock ? Pin : MasterPassword;
|
var page = (Page as LockPage);
|
||||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
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()
|
public async Task PromptBiometricAsync()
|
||||||
@@ -365,8 +362,18 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
PinLock ? AppResources.PIN : AppResources.MasterPassword,
|
PinLock ? AppResources.PIN : AppResources.MasterPassword, () =>
|
||||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
{
|
||||||
|
var page = Page as LockPage;
|
||||||
|
if (PinLock)
|
||||||
|
{
|
||||||
|
page.PinEntry.Focus();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
page.MasterPasswordEntry.Focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
await _stateService.SetBiometricLockedAsync(!success);
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -101,8 +101,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Padding="10, 0">
|
<StackLayout Padding="10, 0">
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ namespace Bit.App.Pages
|
|||||||
public Command LogInCommand { get; }
|
public Command LogInCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action LogInSuccessAction { get; set; }
|
public Action LogInSuccessAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
|
|||||||
@@ -81,12 +81,10 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||||
string ssoToken;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _apiService.PreValidateSso(OrgIdentifier);
|
await _apiService.PreValidateSso(OrgIdentifier);
|
||||||
ssoToken = response.Token;
|
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
@@ -114,8 +112,7 @@ namespace Bit.App.Pages
|
|||||||
"response_type=code&scope=api%20offline_access&" +
|
"response_type=code&scope=api%20offline_access&" +
|
||||||
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
||||||
"code_challenge_method=S256&response_mode=query&" +
|
"code_challenge_method=S256&response_mode=query&" +
|
||||||
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
|
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
|
||||||
"ssoToken=" + Uri.EscapeDataString(ssoToken);
|
|
||||||
|
|
||||||
WebAuthenticatorResult authResult = null;
|
WebAuthenticatorResult authResult = null;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -68,8 +68,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -107,8 +106,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
|
|||||||
@@ -107,8 +107,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -146,8 +145,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public Command ToggleConfirmPasswordCommand { get; }
|
public Command ToggleConfirmPasswordCommand { get; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string ConfirmMasterPassword { get; set; }
|
public string ConfirmMasterPassword { get; set; }
|
||||||
public string Hint { get; set; }
|
public string Hint { get; set; }
|
||||||
@@ -219,8 +220,7 @@ namespace Bit.App.Pages
|
|||||||
// Request
|
// Request
|
||||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
ResetPasswordKey = encryptedKey.EncryptedString,
|
ResetPasswordKey = encryptedKey.EncryptedString
|
||||||
MasterPasswordHash = masterPasswordHash,
|
|
||||||
};
|
};
|
||||||
var userId = await _stateService.GetActiveUserIdAsync();
|
var userId = await _stateService.GetActiveUserIdAsync();
|
||||||
// Enroll user
|
// Enroll user
|
||||||
|
|||||||
@@ -105,8 +105,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
@@ -141,8 +140,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
@@ -303,14 +303,14 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<controls:ExtendedDatePicker
|
<controls:ExtendedDatePicker
|
||||||
NullableDate="{Binding DeletionDateTimeViewModel.Date, Mode=TwoWay}"
|
NullableDate="{Binding DeletionDate, Mode=TwoWay}"
|
||||||
Format="d"
|
Format="d"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n DeletionDate}"
|
AutomationProperties.Name="{u:I18n DeletionDate}"
|
||||||
Grid.Column="0" />
|
Grid.Column="0" />
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
|
NullableTime="{Binding DeletionTime, Mode=TwoWay}"
|
||||||
Format="t"
|
Format="t"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
@@ -343,7 +343,7 @@
|
|||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<controls:ExtendedDatePicker
|
<controls:ExtendedDatePicker
|
||||||
NullableDate="{Binding ExpirationDateTimeViewModel.Date, Mode=TwoWay}"
|
NullableDate="{Binding ExpirationDate, Mode=TwoWay}"
|
||||||
PlaceHolder="mm/dd/yyyy"
|
PlaceHolder="mm/dd/yyyy"
|
||||||
Format="d"
|
Format="d"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
@@ -351,7 +351,7 @@
|
|||||||
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
||||||
Grid.Column="0" />
|
Grid.Column="0" />
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
|
NullableTime="{Binding ExpirationTime, Mode=TwoWay}"
|
||||||
PlaceHolder="--:-- --"
|
PlaceHolder="--:-- --"
|
||||||
Format="t"
|
Format="t"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
@@ -444,8 +444,7 @@
|
|||||||
Command="{Binding TogglePasswordCommand}"
|
Command="{Binding TogglePasswordCommand}"
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"/>
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordInfo}"
|
Text="{u:I18n PasswordInfo}"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace Bit.App.Pages
|
|||||||
private AppOptions _appOptions;
|
private AppOptions _appOptions;
|
||||||
private SendAddEditPageViewModel _vm;
|
private SendAddEditPageViewModel _vm;
|
||||||
|
|
||||||
|
public Action OnClose { get; set; }
|
||||||
public Action AfterSubmit { get; set; }
|
public Action AfterSubmit { get; set; }
|
||||||
|
|
||||||
public SendAddEditPage(
|
public SendAddEditPage(
|
||||||
@@ -135,7 +136,14 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task CloseAsync()
|
private async Task CloseAsync()
|
||||||
{
|
{
|
||||||
await Navigation.PopModalAsync();
|
if (OnClose is null)
|
||||||
|
{
|
||||||
|
await Navigation.PopModalAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnClose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnBackButtonPressed()
|
protected override bool OnBackButtonPressed()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
@@ -24,7 +23,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly ISendService _sendService;
|
private readonly ISendService _sendService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private bool _sendEnabled = true;
|
private bool _sendEnabled;
|
||||||
private bool _canAccessPremium;
|
private bool _canAccessPremium;
|
||||||
private bool _emailVerified;
|
private bool _emailVerified;
|
||||||
private SendView _send;
|
private SendView _send;
|
||||||
@@ -34,7 +33,11 @@ namespace Bit.App.Pages
|
|||||||
private int _deletionDateTypeSelectedIndex;
|
private int _deletionDateTypeSelectedIndex;
|
||||||
private int _expirationDateTypeSelectedIndex;
|
private int _expirationDateTypeSelectedIndex;
|
||||||
private DateTime _simpleDeletionDateTime;
|
private DateTime _simpleDeletionDateTime;
|
||||||
|
private DateTime _deletionDate;
|
||||||
|
private TimeSpan _deletionTime;
|
||||||
private DateTime? _simpleExpirationDateTime;
|
private DateTime? _simpleExpirationDateTime;
|
||||||
|
private DateTime? _expirationDate;
|
||||||
|
private TimeSpan? _expirationTime;
|
||||||
private bool _isOverridingPickers;
|
private bool _isOverridingPickers;
|
||||||
private int? _maxAccessCount;
|
private int? _maxAccessCount;
|
||||||
private string[] _additionalSendProperties = new[]
|
private string[] _additionalSendProperties = new[]
|
||||||
@@ -86,34 +89,8 @@ namespace Bit.App.Pages
|
|||||||
new KeyValuePair<string, string>(AppResources.ThirtyDays, AppResources.ThirtyDays),
|
new KeyValuePair<string, string>(AppResources.ThirtyDays, AppResources.ThirtyDays),
|
||||||
new KeyValuePair<string, string>(AppResources.Custom, AppResources.Custom),
|
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 TogglePasswordCommand { get; set; }
|
||||||
public Command ToggleOptionsCommand { get; set; }
|
public Command ToggleOptionsCommand { get; set; }
|
||||||
public string SendId { get; set; }
|
public string SendId { get; set; }
|
||||||
@@ -149,14 +126,23 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public DateTime DeletionDate
|
||||||
|
{
|
||||||
|
get => _deletionDate;
|
||||||
|
set => SetProperty(ref _deletionDate, value);
|
||||||
|
}
|
||||||
|
public TimeSpan DeletionTime
|
||||||
|
{
|
||||||
|
get => _deletionTime;
|
||||||
|
set => SetProperty(ref _deletionTime, value);
|
||||||
|
}
|
||||||
public bool ShowOptions
|
public bool ShowOptions
|
||||||
{
|
{
|
||||||
get => _showOptions;
|
get => _showOptions;
|
||||||
set => SetProperty(ref _showOptions, value,
|
set => SetProperty(ref _showOptions, value,
|
||||||
additionalPropertyNames: new[]
|
additionalPropertyNames: new[]
|
||||||
{
|
{
|
||||||
nameof(OptionsAccessilibityText),
|
nameof(OptionsAccessilibityText)
|
||||||
nameof(OptionsShowHideIcon)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public int ExpirationDateTypeSelectedIndex
|
public int ExpirationDateTypeSelectedIndex
|
||||||
@@ -170,7 +156,28 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public DateTime? ExpirationDate
|
||||||
|
{
|
||||||
|
get => _expirationDate;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _expirationDate, value))
|
||||||
|
{
|
||||||
|
ExpirationDateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public TimeSpan? ExpirationTime
|
||||||
|
{
|
||||||
|
get => _expirationTime;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _expirationTime, value))
|
||||||
|
{
|
||||||
|
ExpirationTimeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public int? MaxAccessCount
|
public int? MaxAccessCount
|
||||||
{
|
{
|
||||||
get => _maxAccessCount;
|
get => _maxAccessCount;
|
||||||
@@ -198,7 +205,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
public string FileName
|
public string FileName
|
||||||
{
|
{
|
||||||
get => _fileName ?? AppResources.NoFileChosen;
|
get => _fileName;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _fileName, value))
|
if (SetProperty(ref _fileName, value))
|
||||||
@@ -233,13 +240,11 @@ namespace Bit.App.Pages
|
|||||||
public bool IsFile => Send?.Type == SendType.File;
|
public bool IsFile => Send?.Type == SendType.File;
|
||||||
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
public bool ShowDeletionCustomPickers => EditMode || DeletionDateTypeSelectedIndex == 6;
|
||||||
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
public bool ShowExpirationCustomPickers => EditMode || ExpirationDateTypeSelectedIndex == 7;
|
||||||
public DateTimeViewModel DeletionDateTimeViewModel { get; }
|
|
||||||
public DateTimeViewModel ExpirationDateTimeViewModel { get; }
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
public string FileTypeAccessibilityLabel => IsFile ? AppResources.FileTypeIsSelected : AppResources.FileTypeIsNotSelected;
|
||||||
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
public string TextTypeAccessibilityLabel => IsText ? AppResources.TextTypeIsSelected : AppResources.TextTypeIsNotSelected;
|
||||||
public string OptionsShowHideIcon => ShowOptions ? BitwardenIcons.ChevronUp : BitwardenIcons.AngleDown;
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
@@ -264,8 +269,10 @@ namespace Bit.App.Pages
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Send = await send.DecryptAsync();
|
Send = await send.DecryptAsync();
|
||||||
DeletionDateTimeViewModel.DateTime = Send.DeletionDate.ToLocalTime();
|
DeletionDate = Send.DeletionDate.ToLocalTime();
|
||||||
ExpirationDateTimeViewModel.DateTime = Send.ExpirationDate?.ToLocalTime();
|
DeletionTime = DeletionDate.TimeOfDay;
|
||||||
|
ExpirationDate = Send.ExpirationDate?.ToLocalTime();
|
||||||
|
ExpirationTime = ExpirationDate?.TimeOfDay;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -274,7 +281,8 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
Type = Type.GetValueOrDefault(defaultType),
|
Type = Type.GetValueOrDefault(defaultType),
|
||||||
};
|
};
|
||||||
DeletionDateTimeViewModel.DateTime = DateTimeNow().AddDays(7);
|
_deletionDate = DateTimeNow().AddDays(7);
|
||||||
|
_deletionTime = DeletionDate.TimeOfDay;
|
||||||
DeletionDateTypeSelectedIndex = 4;
|
DeletionDateTypeSelectedIndex = 4;
|
||||||
ExpirationDateTypeSelectedIndex = 0;
|
ExpirationDateTypeSelectedIndex = 0;
|
||||||
}
|
}
|
||||||
@@ -298,22 +306,23 @@ namespace Bit.App.Pages
|
|||||||
public void ClearExpirationDate()
|
public void ClearExpirationDate()
|
||||||
{
|
{
|
||||||
_isOverridingPickers = true;
|
_isOverridingPickers = true;
|
||||||
ExpirationDateTimeViewModel.DateTime = null;
|
ExpirationDate = null;
|
||||||
|
ExpirationTime = null;
|
||||||
_isOverridingPickers = false;
|
_isOverridingPickers = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateSendData()
|
private void UpdateSendData()
|
||||||
{
|
{
|
||||||
// filename
|
// filename
|
||||||
if (Send.File != null && _fileName != null)
|
if (Send.File != null && FileName != null)
|
||||||
{
|
{
|
||||||
Send.File.FileName = _fileName;
|
Send.File.FileName = FileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// deletion date
|
// deletion date
|
||||||
if (ShowDeletionCustomPickers)
|
if (ShowDeletionCustomPickers)
|
||||||
{
|
{
|
||||||
Send.DeletionDate = DeletionDateTimeViewModel.DateTime.Value.ToUniversalTime();
|
Send.DeletionDate = DeletionDate.Date.Add(DeletionTime).ToUniversalTime();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -321,9 +330,9 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
// expiration date
|
// 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)
|
else if (_simpleExpirationDateTime.HasValue)
|
||||||
{
|
{
|
||||||
@@ -476,7 +485,7 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Page is SendAddOnlyPage sendPage && sendPage.OnClose != null)
|
if (Page is SendAddEditPage sendPage && sendPage.OnClose != null)
|
||||||
{
|
{
|
||||||
sendPage.OnClose();
|
sendPage.OnClose();
|
||||||
return;
|
return;
|
||||||
@@ -617,6 +626,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()
|
private void MaxAccessCountChanged()
|
||||||
{
|
{
|
||||||
Send.MaxAccessCount = _maxAccessCount;
|
Send.MaxAccessCount = _maxAccessCount;
|
||||||
@@ -640,10 +667,5 @@ namespace Bit.App.Pages
|
|||||||
DateTimeKind.Local
|
DateTimeKind.Local
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void TriggerSendTextPropertyChanged()
|
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(Send)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ using Bit.App.Controls;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -69,28 +68,21 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(_pageName, async (message) =>
|
_broadcasterService.Subscribe(_pageName, async (message) =>
|
||||||
{
|
{
|
||||||
try
|
if (message.Command == "syncStarted")
|
||||||
{
|
{
|
||||||
if (message.Command == "syncStarted")
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -104,8 +104,7 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
|
||||||
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ConfirmYourIdentity}"
|
Text="{u:I18n ConfirmYourIdentity}"
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ namespace Bit.App.Pages
|
|||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
|
|
||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,23 +33,6 @@
|
|||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
Text="{u:I18n ThemeDescription}" />
|
Text="{u:I18n ThemeDescription}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
|
||||||
StyleClass="box"
|
|
||||||
IsVisible="{Binding ShowAutoDarkThemeOptions}">
|
|
||||||
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n DefaultDarkTheme}"
|
|
||||||
StyleClass="box-label" />
|
|
||||||
<Picker
|
|
||||||
x:Name="_autoDarkThemePicker"
|
|
||||||
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
|
|
||||||
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
|
|
||||||
StyleClass="box-value" />
|
|
||||||
</StackLayout>
|
|
||||||
<Label
|
|
||||||
StyleClass="box-footer-label"
|
|
||||||
Text="{u:I18n DefaultDarkThemeDescription}" />
|
|
||||||
</StackLayout>
|
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
|
||||||
<Label
|
<Label
|
||||||
@@ -83,31 +66,31 @@
|
|||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n CopyTotpAutomatically}"
|
Text="{u:I18n DisableAutoTotpCopy}"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AutoTotpCopy}"
|
IsToggled="{Binding DisableAutoTotpCopy}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n CopyTotpAutomaticallyDescription}"
|
Text="{u:I18n DisableAutoTotpCopyDescription}"
|
||||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ShowWebsiteIcons}"
|
Text="{u:I18n DisableWebsiteIcons}"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Favicon}"
|
IsToggled="{Binding DisableFavicon}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ShowWebsiteIconsDescription}"
|
Text="{u:I18n DisableWebsiteIconsDescription}"
|
||||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
||||||
@@ -117,35 +100,35 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n AskToAddLogin}"
|
Text="{u:I18n DisableSavePrompt}"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand" />
|
HorizontalOptions="StartAndExpand" />
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AutofillSavePrompt}"
|
IsToggled="{Binding AutofillDisableSavePrompt}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End" />
|
HorizontalOptions="End" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n AskToAddLoginDescription}"
|
Text="{u:I18n DisableSavePromptDescription}"
|
||||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n AutofillBlockedUris}"
|
Text="{u:I18n BlacklistedUris}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label" />
|
||||||
<Editor
|
<Editor
|
||||||
x:Name="_autofillBlockedUrisEditor"
|
x:Name="_blacklistedUrisEditor"
|
||||||
Text="{Binding AutofillBlockedUris}"
|
Text="{Binding AutofillBlacklistedUris}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutoSize="TextChanges"
|
AutoSize="TextChanges"
|
||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
Keyboard="Url"
|
Keyboard="Url"
|
||||||
Unfocused="AutofillBlockedUrisEditor_Unfocused" />
|
Unfocused="BlacklistedUrisEditor_Unfocused" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n AutofillBlockedUrisDescription}"
|
Text="{u:I18n BlacklistedUrisDescription}"
|
||||||
StyleClass="box-footer-label" />
|
StyleClass="box-footer-label" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace Bit.App.Pages
|
|||||||
_vm = BindingContext as OptionsPageViewModel;
|
_vm = BindingContext as OptionsPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_themePicker.ItemDisplayBinding = new Binding("Value");
|
_themePicker.ItemDisplayBinding = new Binding("Value");
|
||||||
_autoDarkThemePicker.ItemDisplayBinding = new Binding("Value");
|
|
||||||
_uriMatchPicker.ItemDisplayBinding = new Binding("Value");
|
_uriMatchPicker.ItemDisplayBinding = new Binding("Value");
|
||||||
_clearClipboardPicker.ItemDisplayBinding = new Binding("Value");
|
_clearClipboardPicker.ItemDisplayBinding = new Binding("Value");
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
@@ -30,7 +29,6 @@ namespace Bit.App.Pages
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
_themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||||
_autoDarkThemePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
|
||||||
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||||
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
|
||||||
}
|
}
|
||||||
@@ -45,12 +43,12 @@ namespace Bit.App.Pages
|
|||||||
protected async override void OnDisappearing()
|
protected async override void OnDisappearing()
|
||||||
{
|
{
|
||||||
base.OnDisappearing();
|
base.OnDisappearing();
|
||||||
await _vm.UpdateAutofillBlockedUris();
|
await _vm.UpdateAutofillBlacklistedUris();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void AutofillBlockedUrisEditor_Unfocused(object sender, FocusEventArgs e)
|
private async void BlacklistedUrisEditor_Unfocused(object sender, FocusEventArgs e)
|
||||||
{
|
{
|
||||||
await _vm.UpdateAutofillBlockedUris();
|
await _vm.UpdateAutofillBlacklistedUris();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
private async void Close_Clicked(object sender, System.EventArgs e)
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
public class OptionsPageViewModel : BaseViewModel
|
public class OptionsPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
|
private readonly ITotpService _totpService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
|
|
||||||
|
|
||||||
private bool _autofillSavePrompt;
|
private bool _autofillDisableSavePrompt;
|
||||||
private string _autofillBlockedUris;
|
private string _autofillBlacklistedUris;
|
||||||
private bool _favicon;
|
private bool _disableFavicon;
|
||||||
private bool _autoTotpCopy;
|
private bool _disableAutoTotpCopy;
|
||||||
private int _clearClipboardSelectedIndex;
|
private int _clearClipboardSelectedIndex;
|
||||||
private int _themeSelectedIndex;
|
private int _themeSelectedIndex;
|
||||||
private int _autoDarkThemeSelectedIndex;
|
|
||||||
private int _uriMatchSelectedIndex;
|
private int _uriMatchSelectedIndex;
|
||||||
private bool _inited;
|
private bool _inited;
|
||||||
private bool _updatingAutofill;
|
private bool _updatingAutofill;
|
||||||
@@ -30,6 +30,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public OptionsPageViewModel()
|
public OptionsPageViewModel()
|
||||||
{
|
{
|
||||||
|
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
|
||||||
@@ -52,16 +53,10 @@ namespace Bit.App.Pages
|
|||||||
ThemeOptions = new List<KeyValuePair<string, string>>
|
ThemeOptions = new List<KeyValuePair<string, string>>
|
||||||
{
|
{
|
||||||
new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
|
new KeyValuePair<string, string>(null, AppResources.ThemeDefault),
|
||||||
new KeyValuePair<string, string>(ThemeManager.Light, AppResources.Light),
|
new KeyValuePair<string, string>("light", AppResources.Light),
|
||||||
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
new KeyValuePair<string, string>("dark", AppResources.Dark),
|
||||||
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
new KeyValuePair<string, string>("black", AppResources.Black),
|
||||||
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
new KeyValuePair<string, string>("nord", "Nord"),
|
||||||
};
|
|
||||||
AutoDarkThemeOptions = new List<KeyValuePair<string, string>>
|
|
||||||
{
|
|
||||||
new KeyValuePair<string, string>(ThemeManager.Dark, AppResources.Dark),
|
|
||||||
new KeyValuePair<string, string>(ThemeManager.Black, AppResources.Black),
|
|
||||||
new KeyValuePair<string, string>(ThemeManager.Nord, AppResources.Nord),
|
|
||||||
};
|
};
|
||||||
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
|
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
|
||||||
{
|
{
|
||||||
@@ -76,7 +71,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
||||||
public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
|
public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
|
||||||
public List<KeyValuePair<string, string>> AutoDarkThemeOptions { get; set; }
|
|
||||||
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
|
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
|
||||||
|
|
||||||
public int ClearClipboardSelectedIndex
|
public int ClearClipboardSelectedIndex
|
||||||
@@ -86,7 +80,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref _clearClipboardSelectedIndex, value))
|
if (SetProperty(ref _clearClipboardSelectedIndex, value))
|
||||||
{
|
{
|
||||||
SaveClipboardChangedAsync().FireAndForget();
|
var task = SaveClipboardChangedAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,25 +90,9 @@ namespace Bit.App.Pages
|
|||||||
get => _themeSelectedIndex;
|
get => _themeSelectedIndex;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _themeSelectedIndex, value,
|
if (SetProperty(ref _themeSelectedIndex, value))
|
||||||
additionalPropertyNames: new[] { nameof(ShowAutoDarkThemeOptions) })
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
SaveThemeAsync().FireAndForget();
|
var task = SaveThemeAsync();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowAutoDarkThemeOptions => ThemeOptions[ThemeSelectedIndex].Key == null;
|
|
||||||
|
|
||||||
public int AutoDarkThemeSelectedIndex
|
|
||||||
{
|
|
||||||
get => _autoDarkThemeSelectedIndex;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetProperty(ref _autoDarkThemeSelectedIndex, value))
|
|
||||||
{
|
|
||||||
SaveThemeAsync().FireAndForget();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,51 +104,51 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref _uriMatchSelectedIndex, value))
|
if (SetProperty(ref _uriMatchSelectedIndex, value))
|
||||||
{
|
{
|
||||||
SaveDefaultUriAsync().FireAndForget();
|
var task = SaveDefaultUriAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Favicon
|
public bool DisableFavicon
|
||||||
{
|
{
|
||||||
get => _favicon;
|
get => _disableFavicon;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _favicon, value))
|
if (SetProperty(ref _disableFavicon, value))
|
||||||
{
|
{
|
||||||
UpdateFaviconAsync().FireAndForget();
|
var task = UpdateDisableFaviconAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AutoTotpCopy
|
public bool DisableAutoTotpCopy
|
||||||
{
|
{
|
||||||
get => _autoTotpCopy;
|
get => _disableAutoTotpCopy;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _autoTotpCopy, value))
|
if (SetProperty(ref _disableAutoTotpCopy, value))
|
||||||
{
|
{
|
||||||
UpdateAutoTotpCopyAsync().FireAndForget();
|
var task = UpdateAutoTotpCopyAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AutofillSavePrompt
|
public bool AutofillDisableSavePrompt
|
||||||
{
|
{
|
||||||
get => _autofillSavePrompt;
|
get => _autofillDisableSavePrompt;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _autofillSavePrompt, value))
|
if (SetProperty(ref _autofillDisableSavePrompt, value))
|
||||||
{
|
{
|
||||||
UpdateAutofillSavePromptAsync().FireAndForget();
|
var task = UpdateAutofillDisableSavePromptAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AutofillBlockedUris
|
public string AutofillBlacklistedUris
|
||||||
{
|
{
|
||||||
get => _autofillBlockedUris;
|
get => _autofillBlacklistedUris;
|
||||||
set => SetProperty(ref _autofillBlockedUris, value);
|
set => SetProperty(ref _autofillBlacklistedUris, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowAndroidAutofillSettings
|
public bool ShowAndroidAutofillSettings
|
||||||
@@ -181,15 +159,13 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
AutofillDisableSavePrompt = (await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
||||||
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
var blacklistedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
||||||
AutofillBlockedUris = blockedUrisList != null ? string.Join(", ", blockedUrisList) : null;
|
AutofillBlacklistedUris = blacklistedUrisList != null ? string.Join(", ", blacklistedUrisList) : null;
|
||||||
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
|
DisableAutoTotpCopy = !(await _totpService.IsAutoCopyEnabledAsync());
|
||||||
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
var theme = await _stateService.GetThemeAsync();
|
var theme = await _stateService.GetThemeAsync();
|
||||||
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
|
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
|
||||||
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
|
|
||||||
AutoDarkThemeSelectedIndex = AutoDarkThemeOptions.FindIndex(k => k.Key == autoDarkTheme);
|
|
||||||
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
|
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
|
||||||
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
|
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
|
||||||
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
|
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
|
||||||
@@ -202,17 +178,15 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (_inited)
|
if (_inited)
|
||||||
{
|
{
|
||||||
// TODO: [PS-961] Fix negative function names
|
await _stateService.SetDisableAutoTotpCopyAsync(DisableAutoTotpCopy);
|
||||||
await _stateService.SetDisableAutoTotpCopyAsync(!AutoTotpCopy);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateFaviconAsync()
|
private async Task UpdateDisableFaviconAsync()
|
||||||
{
|
{
|
||||||
if (_inited)
|
if (_inited)
|
||||||
{
|
{
|
||||||
// TODO: [PS-961] Fix negative function names
|
await _stateService.SetDisableFaviconAsync(DisableFavicon);
|
||||||
await _stateService.SetDisableFaviconAsync(!Favicon);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,8 +202,8 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (_inited && ThemeSelectedIndex > -1)
|
if (_inited && ThemeSelectedIndex > -1)
|
||||||
{
|
{
|
||||||
await _stateService.SetThemeAsync(ThemeOptions[ThemeSelectedIndex].Key);
|
var theme = ThemeOptions[ThemeSelectedIndex].Key;
|
||||||
await _stateService.SetAutoDarkThemeAsync(AutoDarkThemeOptions[AutoDarkThemeSelectedIndex].Key);
|
await _stateService.SetThemeAsync(theme);
|
||||||
ThemeManager.SetTheme(Application.Current.Resources);
|
ThemeManager.SetTheme(Application.Current.Resources);
|
||||||
_messagingService.Send("updatedTheme");
|
_messagingService.Send("updatedTheme");
|
||||||
}
|
}
|
||||||
@@ -243,28 +217,27 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateAutofillSavePromptAsync()
|
private async Task UpdateAutofillDisableSavePromptAsync()
|
||||||
{
|
{
|
||||||
if (_inited)
|
if (_inited)
|
||||||
{
|
{
|
||||||
// TODO: [PS-961] Fix negative function names
|
await _stateService.SetAutofillDisableSavePromptAsync(AutofillDisableSavePrompt);
|
||||||
await _stateService.SetAutofillDisableSavePromptAsync(!AutofillSavePrompt);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateAutofillBlockedUris()
|
public async Task UpdateAutofillBlacklistedUris()
|
||||||
{
|
{
|
||||||
if (_inited)
|
if (_inited)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(AutofillBlockedUris))
|
if (string.IsNullOrWhiteSpace(AutofillBlacklistedUris))
|
||||||
{
|
{
|
||||||
await _stateService.SetAutofillBlacklistedUrisAsync(null);
|
await _stateService.SetAutofillBlacklistedUrisAsync(null);
|
||||||
AutofillBlockedUris = null;
|
AutofillBlacklistedUris = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var csv = AutofillBlockedUris;
|
var csv = AutofillBlacklistedUris;
|
||||||
var urisList = new List<string>();
|
var urisList = new List<string>();
|
||||||
foreach (var uri in csv.Split(','))
|
foreach (var uri in csv.Split(','))
|
||||||
{
|
{
|
||||||
@@ -281,7 +254,7 @@ namespace Bit.App.Pages
|
|||||||
urisList.Add(cleanedUri);
|
urisList.Add(cleanedUri);
|
||||||
}
|
}
|
||||||
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
|
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
|
||||||
AutofillBlockedUris = string.Join(", ", urisList);
|
AutofillBlacklistedUris = string.Join(", ", urisList);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,18 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
|
using Bit.App.Pages.Accounts;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public partial class SettingsPage : BaseContentPage
|
public partial class SettingsPage : BaseContentPage
|
||||||
{
|
{
|
||||||
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly TabsPage _tabsPage;
|
private readonly TabsPage _tabsPage;
|
||||||
private SettingsPageViewModel _vm;
|
private SettingsPageViewModel _vm;
|
||||||
|
|
||||||
@@ -16,6 +21,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_tabsPage = tabsPage;
|
_tabsPage = tabsPage;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_vm = BindingContext as SettingsPageViewModel;
|
_vm = BindingContext as SettingsPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
}
|
}
|
||||||
@@ -61,12 +67,122 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RowSelected(object sender, SelectionChangedEventArgs e)
|
private async void RowSelected(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
((ExtendedCollectionView)sender).SelectedItem = null;
|
((ExtendedCollectionView)sender).SelectedItem = null;
|
||||||
if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item)
|
if (!DoOnce())
|
||||||
{
|
{
|
||||||
_vm?.ExecuteSettingItemCommand.Execute(item);
|
return;
|
||||||
|
}
|
||||||
|
if (!(e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Name == AppResources.Sync)
|
||||||
|
{
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(new SyncPage()));
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.AutofillServices)
|
||||||
|
{
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(this)));
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.PasswordAutofill)
|
||||||
|
{
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(new AutofillPage()));
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.AppExtension)
|
||||||
|
{
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()));
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.Options)
|
||||||
|
{
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(new OptionsPage()));
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.Folders)
|
||||||
|
{
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(new FoldersPage()));
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.About)
|
||||||
|
{
|
||||||
|
await _vm.AboutAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.HelpAndFeedback)
|
||||||
|
{
|
||||||
|
_vm.Help();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.FingerprintPhrase)
|
||||||
|
{
|
||||||
|
await _vm.FingerprintAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.RateTheApp)
|
||||||
|
{
|
||||||
|
_vm.Rate();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.ImportItems)
|
||||||
|
{
|
||||||
|
_vm.Import();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.ExportVault)
|
||||||
|
{
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage()));
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.LearnOrg)
|
||||||
|
{
|
||||||
|
await _vm.ShareAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.WebVault)
|
||||||
|
{
|
||||||
|
_vm.WebVault();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.ChangeMasterPassword)
|
||||||
|
{
|
||||||
|
await _vm.ChangePasswordAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.TwoStepLogin)
|
||||||
|
{
|
||||||
|
await _vm.TwoStepAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.LogOut)
|
||||||
|
{
|
||||||
|
await _vm.LogOutAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.DeleteAccount)
|
||||||
|
{
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()));
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.LockNow)
|
||||||
|
{
|
||||||
|
await _vm.LockAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.VaultTimeout)
|
||||||
|
{
|
||||||
|
await _vm.VaultTimeoutAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.VaultTimeoutAction)
|
||||||
|
{
|
||||||
|
await _vm.VaultTimeoutActionAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.UnlockWithPIN)
|
||||||
|
{
|
||||||
|
await _vm.UpdatePinAsync();
|
||||||
|
}
|
||||||
|
else if (item.Name == AppResources.SubmitCrashLogs)
|
||||||
|
{
|
||||||
|
await _vm.LoggerReportingAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var biometricName = AppResources.Biometrics;
|
||||||
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
|
||||||
|
biometricName = supportsFace ? AppResources.FaceID : AppResources.TouchID;
|
||||||
|
}
|
||||||
|
if (item.Name == string.Format(AppResources.UnlockWith, biometricName))
|
||||||
|
{
|
||||||
|
await _vm.UpdateBiometricAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
@@ -13,9 +12,7 @@ namespace Bit.App.Pages
|
|||||||
public string SubLabel { get; set; }
|
public string SubLabel { get; set; }
|
||||||
public TimeSpan? Time { get; set; }
|
public TimeSpan? Time { get; set; }
|
||||||
public bool UseFrame { get; set; }
|
public bool UseFrame { get; set; }
|
||||||
public Func<Task> ExecuteAsync { get; set; }
|
public bool SubLabelTextEnabled => SubLabel == AppResources.Enabled;
|
||||||
|
|
||||||
public bool SubLabelTextEnabled => SubLabel == AppResources.On;
|
|
||||||
public string LineBreakMode => SubLabel == null ? "TailTruncation" : "";
|
public string LineBreakMode => SubLabel == null ? "TailTruncation" : "";
|
||||||
public bool ShowSubLabel => SubLabel.Length != 0;
|
public bool ShowSubLabel => SubLabel.Length != 0;
|
||||||
public bool ShowTimeInput => Time != null;
|
public bool ShowTimeInput => Time != null;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Pages.Accounts;
|
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
@@ -30,13 +30,11 @@ namespace Bit.App.Pages
|
|||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly ILogger _loggerService;
|
private readonly ILogger _loggerService;
|
||||||
|
|
||||||
private const int CustomVaultTimeoutValue = -100;
|
private const int CustomVaultTimeoutValue = -100;
|
||||||
|
|
||||||
private bool _supportsBiometric;
|
private bool _supportsBiometric;
|
||||||
private bool _pin;
|
private bool _pin;
|
||||||
private bool _biometric;
|
private bool _biometric;
|
||||||
private bool _screenCaptureAllowed;
|
|
||||||
private string _lastSyncDate;
|
private string _lastSyncDate;
|
||||||
private string _vaultTimeoutDisplayValue;
|
private string _vaultTimeoutDisplayValue;
|
||||||
private string _vaultTimeoutActionDisplayValue;
|
private string _vaultTimeoutActionDisplayValue;
|
||||||
@@ -86,14 +84,10 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
|
|
||||||
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
||||||
|
|
||||||
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
||||||
@@ -123,7 +117,6 @@ namespace Bit.App.Pages
|
|||||||
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
_pin = pinSet.Item1 || pinSet.Item2;
|
_pin = pinSet.Item1 || pinSet.Item2;
|
||||||
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
||||||
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
|
|
||||||
|
|
||||||
if (_vaultTimeoutDisplayValue == null)
|
if (_vaultTimeoutDisplayValue == null)
|
||||||
{
|
{
|
||||||
@@ -264,17 +257,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
var cleanSelection = selection.Replace("✓ ", string.Empty);
|
var cleanSelection = selection.Replace("✓ ", string.Empty);
|
||||||
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
|
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
|
||||||
|
|
||||||
// Check if the selected Timeout action is "Never" and if it's different from the previous selected value
|
|
||||||
if (selectionOption.Value == null && selectionOption.Value != oldTimeout)
|
|
||||||
{
|
|
||||||
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.NeverLockWarning,
|
|
||||||
AppResources.Warning, AppResources.Yes, AppResources.Cancel);
|
|
||||||
if (!confirmed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_vaultTimeoutDisplayValue = selectionOption.Key;
|
_vaultTimeoutDisplayValue = selectionOption.Key;
|
||||||
newTimeout = selectionOption.Value;
|
newTimeout = selectionOption.Value;
|
||||||
}
|
}
|
||||||
@@ -441,8 +423,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public void BuildList()
|
public void BuildList()
|
||||||
{
|
{
|
||||||
//TODO: Refactor this once navigation is abstracted so that it doesn't depend on Page, e.g. Page.Navigation.PushModalAsync...
|
|
||||||
|
|
||||||
var doUpper = Device.RuntimePlatform != Device.Android;
|
var doUpper = Device.RuntimePlatform != Device.Android;
|
||||||
var autofillItems = new List<SettingsPageListItem>();
|
var autofillItems = new List<SettingsPageListItem>();
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
@@ -450,69 +430,38 @@ namespace Bit.App.Pages
|
|||||||
autofillItems.Add(new SettingsPageListItem
|
autofillItems.Add(new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.AutofillServices,
|
Name = AppResources.AutofillServices,
|
||||||
SubLabel = _deviceActionService.AutofillServicesEnabled() ? AppResources.On : AppResources.Off,
|
SubLabel = _deviceActionService.AutofillServicesEnabled() ?
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillServicesPage(Page as SettingsPage)))
|
AppResources.Enabled : AppResources.Disabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||||
{
|
{
|
||||||
autofillItems.Add(new SettingsPageListItem
|
autofillItems.Add(new SettingsPageListItem { Name = AppResources.PasswordAutofill });
|
||||||
{
|
|
||||||
Name = AppResources.PasswordAutofill,
|
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new AutofillPage()))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
autofillItems.Add(new SettingsPageListItem
|
autofillItems.Add(new SettingsPageListItem { Name = AppResources.AppExtension });
|
||||||
{
|
|
||||||
Name = AppResources.AppExtension,
|
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
var manageItems = new List<SettingsPageListItem>
|
var manageItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem { Name = AppResources.Folders },
|
||||||
{
|
new SettingsPageListItem { Name = AppResources.Sync, SubLabel = _lastSyncDate }
|
||||||
Name = AppResources.Folders,
|
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new FoldersPage()))
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.Sync,
|
|
||||||
SubLabel = _lastSyncDate,
|
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new SyncPage()))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
var securityItems = new List<SettingsPageListItem>
|
var securityItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem { Name = AppResources.VaultTimeout, SubLabel = _vaultTimeoutDisplayValue },
|
||||||
{
|
|
||||||
Name = AppResources.VaultTimeout,
|
|
||||||
SubLabel = _vaultTimeoutDisplayValue,
|
|
||||||
ExecuteAsync = () => VaultTimeoutAsync() },
|
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.VaultTimeoutAction,
|
Name = AppResources.VaultTimeoutAction,
|
||||||
SubLabel = _vaultTimeoutActionDisplayValue,
|
SubLabel = _vaultTimeoutActionDisplayValue
|
||||||
ExecuteAsync = () => VaultTimeoutActionAsync()
|
|
||||||
},
|
},
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.UnlockWithPIN,
|
Name = AppResources.UnlockWithPIN,
|
||||||
SubLabel = _pin ? AppResources.On : AppResources.Off,
|
SubLabel = _pin ? AppResources.Enabled : AppResources.Disabled
|
||||||
ExecuteAsync = () => UpdatePinAsync()
|
|
||||||
},
|
},
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem { Name = AppResources.LockNow },
|
||||||
{
|
new SettingsPageListItem { Name = AppResources.TwoStepLogin }
|
||||||
Name = AppResources.LockNow,
|
|
||||||
ExecuteAsync = () => LockAsync()
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.TwoStepLogin,
|
|
||||||
ExecuteAsync = () => TwoStepAsync()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if (_supportsBiometric || _biometric)
|
if (_supportsBiometric || _biometric)
|
||||||
{
|
{
|
||||||
@@ -525,8 +474,7 @@ namespace Bit.App.Pages
|
|||||||
var item = new SettingsPageListItem
|
var item = new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = string.Format(AppResources.UnlockWith, biometricName),
|
Name = string.Format(AppResources.UnlockWith, biometricName),
|
||||||
SubLabel = _biometric ? AppResources.On : AppResources.Off,
|
SubLabel = _biometric ? AppResources.Enabled : AppResources.Disabled
|
||||||
ExecuteAsync = () => UpdateBiometricAsync()
|
|
||||||
};
|
};
|
||||||
securityItems.Insert(2, item);
|
securityItems.Insert(2, item);
|
||||||
}
|
}
|
||||||
@@ -549,98 +497,40 @@ namespace Bit.App.Pages
|
|||||||
UseFrame = true,
|
UseFrame = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
|
||||||
{
|
|
||||||
securityItems.Add(new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.AllowScreenCapture,
|
|
||||||
SubLabel = _screenCaptureAllowed ? AppResources.On : AppResources.Off,
|
|
||||||
ExecuteAsync = () => SetScreenCaptureAllowedAsync()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var accountItems = new List<SettingsPageListItem>
|
var accountItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem { Name = AppResources.FingerprintPhrase },
|
||||||
{
|
new SettingsPageListItem { Name = AppResources.LogOut }
|
||||||
Name = AppResources.FingerprintPhrase,
|
|
||||||
ExecuteAsync = () => FingerprintAsync()
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.LogOut,
|
|
||||||
ExecuteAsync = () => LogOutAsync()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if (_showChangeMasterPassword)
|
if (_showChangeMasterPassword)
|
||||||
{
|
{
|
||||||
accountItems.Insert(0, new SettingsPageListItem
|
accountItems.Insert(0, new SettingsPageListItem { Name = AppResources.ChangeMasterPassword });
|
||||||
{
|
|
||||||
Name = AppResources.ChangeMasterPassword,
|
|
||||||
ExecuteAsync = () => ChangePasswordAsync()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
var toolsItems = new List<SettingsPageListItem>
|
var toolsItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem { Name = AppResources.ImportItems },
|
||||||
{
|
new SettingsPageListItem { Name = AppResources.ExportVault }
|
||||||
Name = AppResources.ImportItems,
|
|
||||||
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Import())
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.ExportVault,
|
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage()))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if (IncludeLinksWithSubscriptionInfo())
|
if (IncludeLinksWithSubscriptionInfo())
|
||||||
{
|
{
|
||||||
toolsItems.Add(new SettingsPageListItem
|
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
|
||||||
{
|
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
|
||||||
Name = AppResources.LearnOrg,
|
|
||||||
ExecuteAsync = () => ShareAsync()
|
|
||||||
});
|
|
||||||
toolsItems.Add(new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.WebVault,
|
|
||||||
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => WebVault())
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var otherItems = new List<SettingsPageListItem>
|
var otherItems = new List<SettingsPageListItem>
|
||||||
{
|
{
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem { Name = AppResources.Options },
|
||||||
{
|
new SettingsPageListItem { Name = AppResources.About },
|
||||||
Name = AppResources.Options,
|
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new OptionsPage()))
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.About,
|
|
||||||
ExecuteAsync = () => AboutAsync()
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.HelpAndFeedback,
|
|
||||||
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Help())
|
|
||||||
},
|
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
{
|
||||||
Name = AppResources.SubmitCrashLogs,
|
Name = AppResources.SubmitCrashLogs,
|
||||||
SubLabel = _reportLoggingEnabled ? AppResources.On : AppResources.Off,
|
SubLabel = _reportLoggingEnabled ? AppResources.Enabled : AppResources.Disabled,
|
||||||
ExecuteAsync = () => LoggerReportingAsync()
|
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem { Name = AppResources.RateTheApp },
|
||||||
{
|
new SettingsPageListItem { Name = AppResources.DeleteAccount }
|
||||||
Name = AppResources.RateTheApp,
|
|
||||||
ExecuteAsync = () => Device.InvokeOnMainThreadAsync(() => Rate())
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
|
||||||
Name = AppResources.DeleteAccount,
|
|
||||||
ExecuteAsync = () => Page.Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: improve this. Leaving this as is to reduce error possibility on the hotfix.
|
// TODO: improve this. Leaving this as is to reduce error possibility on the hotfix.
|
||||||
@@ -720,33 +610,5 @@ namespace Bit.App.Pages
|
|||||||
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
||||||
|
|
||||||
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
||||||
|
|
||||||
public async Task SetScreenCaptureAllowedAsync()
|
|
||||||
{
|
|
||||||
if (CoreHelpers.ForceScreenCaptureEnabled())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!_screenCaptureAllowed
|
|
||||||
&&
|
|
||||||
!await Page.DisplayAlert(AppResources.AllowScreenCapture, AppResources.AreYouSureYouWantToEnableScreenCapture, AppResources.Yes, AppResources.No))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _stateService.SetScreenCaptureAllowedAsync(!_screenCaptureAllowed);
|
|
||||||
_screenCaptureAllowed = !_screenCaptureAllowed;
|
|
||||||
await _deviceActionService.SetScreenCaptureAllowedAsync();
|
|
||||||
BuildList();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_loggerService.Exception(ex);
|
|
||||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,8 +161,7 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
|||||||
@@ -299,7 +299,8 @@ namespace Bit.App.Pages
|
|||||||
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
|
||||||
public bool AllowPersonal { get; set; }
|
public bool AllowPersonal { get; set; }
|
||||||
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using Bit.App.Models;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -56,28 +55,21 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) =>
|
_broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) =>
|
||||||
{
|
{
|
||||||
try
|
if (message.Command == "syncStarted")
|
||||||
{
|
{
|
||||||
if (message.Command == "syncStarted")
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted")
|
|
||||||
{
|
|
||||||
await Task.Delay(500);
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
|
||||||
IsBusy = false;
|
|
||||||
if (_vm.LoadedOnce)
|
|
||||||
{
|
|
||||||
var task = _vm.LoadAsync();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else if (message.Command == "syncCompleted")
|
||||||
{
|
{
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
await Task.Delay(500);
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
if (_vm.LoadedOnce)
|
||||||
|
{
|
||||||
|
var task = _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -15,7 +18,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class CiphersPageViewModel : VaultFilterViewModel
|
public class CiphersPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
@@ -28,9 +31,12 @@ namespace Bit.App.Pages
|
|||||||
private CancellationTokenSource _searchCancellationTokenSource;
|
private CancellationTokenSource _searchCancellationTokenSource;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private bool _showVaultFilter;
|
||||||
|
private string _vaultFilterSelection;
|
||||||
private bool _showNoData;
|
private bool _showNoData;
|
||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
|
private List<Organization> _organizations;
|
||||||
|
|
||||||
public CiphersPageViewModel()
|
public CiphersPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -46,19 +52,18 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
Ciphers = new ExtendedObservableCollection<CipherView>();
|
Ciphers = new ExtendedObservableCollection<CipherView>();
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => _logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command CipherOptionsCommand { get; set; }
|
public Command CipherOptionsCommand { get; set; }
|
||||||
|
public ICommand VaultFilterCommand { get; }
|
||||||
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
|
||||||
public Func<CipherView, bool> Filter { get; set; }
|
public Func<CipherView, bool> Filter { get; set; }
|
||||||
public string AutofillUrl { get; set; }
|
public string AutofillUrl { get; set; }
|
||||||
public bool Deleted { get; set; }
|
public bool Deleted { get; set; }
|
||||||
|
|
||||||
protected override ICipherService cipherService => _cipherService;
|
|
||||||
protected override IPolicyService policyService => _policyService;
|
|
||||||
protected override IOrganizationService organizationService => _organizationService;
|
|
||||||
protected override ILogger logger => _logger;
|
|
||||||
|
|
||||||
public bool ShowNoData
|
public bool ShowNoData
|
||||||
{
|
{
|
||||||
get => _showNoData;
|
get => _showNoData;
|
||||||
@@ -76,6 +81,23 @@ namespace Bit.App.Pages
|
|||||||
nameof(ShowSearchDirection)
|
nameof(ShowSearchDirection)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public bool ShowVaultFilter
|
||||||
|
{
|
||||||
|
get => _showVaultFilter;
|
||||||
|
set => SetProperty(ref _showVaultFilter, value);
|
||||||
|
}
|
||||||
|
public string VaultFilterDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||||
|
}
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _vaultFilterSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
public bool ShowSearchDirection => !ShowList && !ShowNoData;
|
||||||
|
|
||||||
@@ -87,7 +109,12 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
await InitVaultFilterAsync(true);
|
_organizations = await _organizationService.GetAllAsync();
|
||||||
|
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
||||||
|
if (ShowVaultFilter && _vaultFilterSelection == null)
|
||||||
|
{
|
||||||
|
_vaultFilterSelection = AppResources.AllVaults;
|
||||||
|
}
|
||||||
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
PerformSearchIfPopulated();
|
PerformSearchIfPopulated();
|
||||||
}
|
}
|
||||||
@@ -210,11 +237,50 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnVaultFilterSelectedAsync()
|
private async Task VaultFilterOptionsAsync()
|
||||||
{
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||||
|
if (_organizations.Any())
|
||||||
|
{
|
||||||
|
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
||||||
|
}
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||||
|
options.ToArray());
|
||||||
|
if (selection == null || selection == AppResources.Cancel ||
|
||||||
|
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||||
|
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultFilterDescription = selection;
|
||||||
PerformSearchIfPopulated();
|
PerformSearchIfPopulated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<CipherView>> GetAllCiphersAsync()
|
||||||
|
{
|
||||||
|
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
return decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||||
|
}
|
||||||
|
if (IsVaultFilterOrgVault)
|
||||||
|
{
|
||||||
|
var orgId = GetVaultFilterOrgId();
|
||||||
|
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
return decCiphers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
|
private string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
private async void CipherOptionsAsync(CipherView cipher)
|
private async void CipherOptionsAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
if ((Page as BaseContentPage).DoOnce())
|
if ((Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using Bit.App.Resources;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -96,28 +95,21 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(_pageName, async (message) =>
|
_broadcasterService.Subscribe(_pageName, async (message) =>
|
||||||
{
|
{
|
||||||
try
|
if (message.Command == "syncStarted")
|
||||||
{
|
{
|
||||||
if (message.Command == "syncStarted")
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted")
|
|
||||||
{
|
|
||||||
await Task.Delay(500);
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
|
||||||
IsBusy = false;
|
|
||||||
if (_vm.LoadedOnce)
|
|
||||||
{
|
|
||||||
var task = _vm.LoadAsync();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else if (message.Command == "syncCompleted")
|
||||||
{
|
{
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
await Task.Delay(500);
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
if (_vm.LoadedOnce)
|
||||||
|
{
|
||||||
|
var task = _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
@@ -16,7 +17,7 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class GroupingsPageViewModel : VaultFilterViewModel
|
public class GroupingsPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private const int NoFolderListSize = 100;
|
private const int NoFolderListSize = 100;
|
||||||
|
|
||||||
@@ -29,7 +30,10 @@ namespace Bit.App.Pages
|
|||||||
private bool _showList;
|
private bool _showList;
|
||||||
private bool _websiteIconsEnabled;
|
private bool _websiteIconsEnabled;
|
||||||
private bool _syncRefreshing;
|
private bool _syncRefreshing;
|
||||||
|
private bool _showVaultFilter;
|
||||||
|
private string _vaultFilterSelection;
|
||||||
private string _noDataText;
|
private string _noDataText;
|
||||||
|
private List<Organization> _organizations;
|
||||||
private List<CipherView> _allCiphers;
|
private List<CipherView> _allCiphers;
|
||||||
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _folderCounts = new Dictionary<string, int>();
|
||||||
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
private Dictionary<string, int> _collectionCounts = new Dictionary<string, int>();
|
||||||
@@ -74,6 +78,9 @@ namespace Bit.App.Pages
|
|||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
});
|
});
|
||||||
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
|
||||||
|
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
||||||
|
onException: ex => _logger.Exception(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
{
|
{
|
||||||
@@ -101,11 +108,6 @@ namespace Bit.App.Pages
|
|||||||
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
public List<Core.Models.View.CollectionView> Collections { get; set; }
|
||||||
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
|
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
|
||||||
|
|
||||||
protected override ICipherService cipherService => _cipherService;
|
|
||||||
protected override IPolicyService policyService => _policyService;
|
|
||||||
protected override IOrganizationService organizationService => _organizationService;
|
|
||||||
protected override ILogger logger => _logger;
|
|
||||||
|
|
||||||
public bool Refreshing
|
public bool Refreshing
|
||||||
{
|
{
|
||||||
get => _refreshing;
|
get => _refreshing;
|
||||||
@@ -151,12 +153,30 @@ namespace Bit.App.Pages
|
|||||||
get => _websiteIconsEnabled;
|
get => _websiteIconsEnabled;
|
||||||
set => SetProperty(ref _websiteIconsEnabled, value);
|
set => SetProperty(ref _websiteIconsEnabled, value);
|
||||||
}
|
}
|
||||||
|
public bool ShowVaultFilter
|
||||||
|
{
|
||||||
|
get => _showVaultFilter;
|
||||||
|
set => SetProperty(ref _showVaultFilter, value);
|
||||||
|
}
|
||||||
|
public string VaultFilterDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
||||||
|
{
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
||||||
|
}
|
||||||
|
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _vaultFilterSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
|
|
||||||
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
|
||||||
public Command RefreshCommand { get; set; }
|
public Command RefreshCommand { get; set; }
|
||||||
public Command<CipherView> CipherOptionsCommand { get; set; }
|
public Command<CipherView> CipherOptionsCommand { get; set; }
|
||||||
|
public ICommand VaultFilterCommand { get; }
|
||||||
public bool LoadedOnce { get; set; }
|
public bool LoadedOnce { get; set; }
|
||||||
|
|
||||||
public async Task LoadAsync()
|
public async Task LoadAsync()
|
||||||
@@ -181,11 +201,14 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget();
|
_organizations = await _organizationService.GetAllAsync();
|
||||||
|
|
||||||
await InitVaultFilterAsync(MainPage);
|
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
|
ShowVaultFilter = await _policyService.ShouldShowVaultFilterAsync();
|
||||||
|
if (ShowVaultFilter && _vaultFilterSelection == null)
|
||||||
|
{
|
||||||
|
_vaultFilterSelection = AppResources.AllVaults;
|
||||||
|
}
|
||||||
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
PageTitle = ShowVaultFilter ? AppResources.Vaults : AppResources.MyVault;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,11 +394,30 @@ namespace Bit.App.Pages
|
|||||||
SyncRefreshing = false;
|
SyncRefreshing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnVaultFilterSelectedAsync()
|
public async Task VaultFilterOptionsAsync()
|
||||||
{
|
{
|
||||||
|
var options = new List<string> { AppResources.AllVaults, AppResources.MyVault };
|
||||||
|
if (_organizations.Any())
|
||||||
|
{
|
||||||
|
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
||||||
|
}
|
||||||
|
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
||||||
|
options.ToArray());
|
||||||
|
if (selection == null || selection == AppResources.Cancel ||
|
||||||
|
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
||||||
|
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultFilterDescription = selection;
|
||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetVaultFilterOrgId()
|
||||||
|
{
|
||||||
|
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SelectCipherAsync(CipherView cipher)
|
public async Task SelectCipherAsync(CipherView cipher)
|
||||||
{
|
{
|
||||||
var page = new ViewPage(cipher.Id);
|
var page = new ViewPage(cipher.Id);
|
||||||
@@ -459,8 +501,8 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
|
var orgId = await FillAllCiphersAndGetOrgIdIfNeededAsync();
|
||||||
NoDataText = AppResources.NoItems;
|
NoDataText = AppResources.NoItems;
|
||||||
_allCiphers = await GetAllCiphersAsync();
|
|
||||||
HasCiphers = _allCiphers.Any();
|
HasCiphers = _allCiphers.Any();
|
||||||
FavoriteCiphers?.Clear();
|
FavoriteCiphers?.Clear();
|
||||||
NoFolderCiphers?.Clear();
|
NoFolderCiphers?.Clear();
|
||||||
@@ -474,7 +516,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if (MainPage)
|
if (MainPage)
|
||||||
{
|
{
|
||||||
await FillFoldersAndCollectionsAsync();
|
await FillFoldersAndCollectionsAsync(orgId);
|
||||||
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
NestedFolders = await _folderService.GetAllNestedAsync(Folders);
|
||||||
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
|
||||||
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
NestedCollections = Collections != null ? await _collectionService.GetAllNestedAsync(Collections) : null;
|
||||||
@@ -598,9 +640,28 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task FillFoldersAndCollectionsAsync()
|
private async Task<string> FillAllCiphersAndGetOrgIdIfNeededAsync()
|
||||||
|
{
|
||||||
|
string orgId = null;
|
||||||
|
var decCiphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
if (IsVaultFilterMyVault)
|
||||||
|
{
|
||||||
|
_allCiphers = decCiphers.Where(c => c.OrganizationId == null).ToList();
|
||||||
|
}
|
||||||
|
else if (IsVaultFilterOrgVault)
|
||||||
|
{
|
||||||
|
orgId = GetVaultFilterOrgId();
|
||||||
|
_allCiphers = decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_allCiphers = decCiphers;
|
||||||
|
}
|
||||||
|
return orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FillFoldersAndCollectionsAsync(string orgId)
|
||||||
{
|
{
|
||||||
var orgId = GetVaultFilterOrgId();
|
|
||||||
var decFolders = await _folderService.GetAllDecryptedAsync();
|
var decFolders = await _folderService.GetAllDecryptedAsync();
|
||||||
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
var decCollections = await _collectionService.GetAllDecryptedAsync();
|
||||||
if (IsVaultFilterMyVault)
|
if (IsVaultFilterMyVault)
|
||||||
@@ -620,6 +681,11 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
||||||
|
|
||||||
|
private bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
||||||
|
_vaultFilterSelection != AppResources.MyVault;
|
||||||
|
|
||||||
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
private List<FolderView> BuildFolders(List<FolderView> decFolders)
|
||||||
{
|
{
|
||||||
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
|
||||||
|
|||||||
@@ -144,8 +144,7 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
|
||||||
IsVisible="{Binding Cipher.ViewPassword}" />
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
@@ -571,8 +570,6 @@
|
|||||||
IsVisible="{Binding IsLinkedType}" />
|
IsVisible="{Binding IsLinkedType}" />
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
AutomationProperties.IsInAccessibleTree="true"
|
|
||||||
AutomationProperties.Name="{Binding ValueAccessibilityText, Mode=OneWay}"
|
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -57,44 +56,37 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(nameof(ViewPage), async (message) =>
|
_broadcasterService.Subscribe(nameof(ViewPage), async (message) =>
|
||||||
{
|
{
|
||||||
try
|
if (message.Command == "syncStarted")
|
||||||
{
|
{
|
||||||
if (message.Command == "syncStarted")
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted")
|
|
||||||
{
|
|
||||||
await Task.Delay(500);
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
|
||||||
IsBusy = false;
|
|
||||||
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
|
|
||||||
{
|
|
||||||
var success = data["successfully"] as bool?;
|
|
||||||
if (success.GetValueOrDefault())
|
|
||||||
{
|
|
||||||
var task = _vm.LoadAsync(() => AdjustToolbar());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (message.Command == "selectSaveFileResult")
|
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
|
||||||
var data = message.Data as Tuple<string, string>;
|
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_vm.SaveFileSelected(data.Item1, data.Item2);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else if (message.Command == "syncCompleted")
|
||||||
{
|
{
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
await Task.Delay(500);
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
|
||||||
|
{
|
||||||
|
var success = data["successfully"] as bool?;
|
||||||
|
if (success.GetValueOrDefault())
|
||||||
|
{
|
||||||
|
var task = _vm.LoadAsync(() => AdjustToolbar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (message.Command == "selectSaveFileResult")
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
var data = message.Data as Tuple<string, string>;
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_vm.SaveFileSelected(data.Item1, data.Item2);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await LoadOnAppearedAsync(_scrollView, true, async () =>
|
await LoadOnAppearedAsync(_scrollView, true, async () =>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
@@ -12,7 +11,6 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -30,7 +28,6 @@ namespace Bit.App.Pages
|
|||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
private readonly ILocalizeService _localizeService;
|
private readonly ILocalizeService _localizeService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
private CipherView _cipher;
|
private CipherView _cipher;
|
||||||
private List<ViewPageFieldViewModel> _fields;
|
private List<ViewPageFieldViewModel> _fields;
|
||||||
@@ -61,11 +58,10 @@ namespace Bit.App.Pages
|
|||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
|
||||||
|
|
||||||
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
CopyCommand = new Command<string>((id) => CopyAsync(id, null));
|
||||||
CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
CopyUriCommand = new Command<LoginUriView>(CopyUri);
|
||||||
CopyFieldCommand = new AsyncCommand<FieldView>(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
CopyFieldCommand = new Command<FieldView>(CopyField);
|
||||||
LaunchUriCommand = new Command<LoginUriView>(LaunchUri);
|
LaunchUriCommand = new Command<LoginUriView>(LaunchUri);
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
||||||
@@ -76,9 +72,9 @@ namespace Bit.App.Pages
|
|||||||
PageTitle = AppResources.ViewItem;
|
PageTitle = AppResources.ViewItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommand CopyCommand { get; set; }
|
public Command CopyCommand { get; set; }
|
||||||
public ICommand CopyUriCommand { get; set; }
|
public Command CopyUriCommand { get; set; }
|
||||||
public ICommand CopyFieldCommand { get; set; }
|
public Command CopyFieldCommand { get; set; }
|
||||||
public Command LaunchUriCommand { get; set; }
|
public Command LaunchUriCommand { get; set; }
|
||||||
public Command TogglePasswordCommand { get; set; }
|
public Command TogglePasswordCommand { get; set; }
|
||||||
public Command ToggleCardNumberCommand { get; set; }
|
public Command ToggleCardNumberCommand { get; set; }
|
||||||
@@ -218,7 +214,8 @@ namespace Bit.App.Pages
|
|||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.VisibilityTogglePasswordIsVisibleActivateToHide :
|
||||||
|
AppResources.VisibilityTogglePasswordIsNotVisibleActivateToHide;
|
||||||
public string TotpCodeFormatted
|
public string TotpCodeFormatted
|
||||||
{
|
{
|
||||||
get => _totpCodeFormatted;
|
get => _totpCodeFormatted;
|
||||||
@@ -620,7 +617,7 @@ namespace Bit.App.Pages
|
|||||||
_attachmentFilename = null;
|
_attachmentFilename = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CopyAsync(string id, string text = null)
|
private async void CopyAsync(string id, string text = null)
|
||||||
{
|
{
|
||||||
if (_passwordRepromptService.ProtectedFields.Contains(id) && !await PromptPasswordAsync())
|
if (_passwordRepromptService.ProtectedFields.Contains(id) && !await PromptPasswordAsync())
|
||||||
{
|
{
|
||||||
@@ -684,6 +681,16 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CopyUri(LoginUriView uri)
|
||||||
|
{
|
||||||
|
CopyAsync("LoginUri", uri.Uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyField(FieldView field)
|
||||||
|
{
|
||||||
|
CopyAsync(field.Type == Core.Enums.FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value);
|
||||||
|
}
|
||||||
|
|
||||||
private void LaunchUri(LoginUriView uri)
|
private void LaunchUri(LoginUriView uri)
|
||||||
{
|
{
|
||||||
if (uri.CanLaunch && (Page as BaseContentPage).DoOnce())
|
if (uri.CanLaunch && (Page as BaseContentPage).DoOnce())
|
||||||
@@ -727,7 +734,6 @@ namespace Bit.App.Pages
|
|||||||
additionalPropertyNames: new string[]
|
additionalPropertyNames: new string[]
|
||||||
{
|
{
|
||||||
nameof(ValueText),
|
nameof(ValueText),
|
||||||
nameof(ValueAccessibilityText),
|
|
||||||
nameof(IsBooleanType),
|
nameof(IsBooleanType),
|
||||||
nameof(IsHiddenType),
|
nameof(IsHiddenType),
|
||||||
nameof(IsTextType),
|
nameof(IsTextType),
|
||||||
@@ -751,7 +757,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (IsBooleanType)
|
if (IsBooleanType)
|
||||||
{
|
{
|
||||||
return _field.BoolValue ? BitwardenIcons.CheckSquare : BitwardenIcons.Square;
|
return _field.Value == "true" ? BitwardenIcons.Square : BitwardenIcons.CheckSquare;
|
||||||
}
|
}
|
||||||
else if (IsLinkedType)
|
else if (IsLinkedType)
|
||||||
{
|
{
|
||||||
@@ -765,19 +771,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ValueAccessibilityText
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (IsBooleanType)
|
|
||||||
{
|
|
||||||
return _field.BoolValue ? AppResources.Enabled : AppResources.Disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ValueText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public FormattedString ColoredHiddenValue => PasswordFormatter.FormatPassword(_field.Value);
|
public FormattedString ColoredHiddenValue => PasswordFormatter.FormatPassword(_field.Value);
|
||||||
|
|
||||||
public Command ToggleHiddenValueCommand { get; set; }
|
public Command ToggleHiddenValueCommand { get; set; }
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models.Domain;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
|
||||||
{
|
|
||||||
public abstract class VaultFilterViewModel : BaseViewModel
|
|
||||||
{
|
|
||||||
protected abstract ICipherService cipherService { get; }
|
|
||||||
protected abstract IPolicyService policyService { get; }
|
|
||||||
protected abstract IOrganizationService organizationService { get; }
|
|
||||||
protected abstract ILogger logger { get; }
|
|
||||||
|
|
||||||
protected bool _showVaultFilter;
|
|
||||||
protected bool _personalOwnershipPolicyApplies;
|
|
||||||
protected string _vaultFilterSelection;
|
|
||||||
protected List<Organization> _organizations;
|
|
||||||
|
|
||||||
public VaultFilterViewModel()
|
|
||||||
{
|
|
||||||
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
|
|
||||||
onException: ex => logger.Exception(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICommand VaultFilterCommand { get; set; }
|
|
||||||
|
|
||||||
public bool ShowVaultFilter
|
|
||||||
{
|
|
||||||
get => _showVaultFilter;
|
|
||||||
set => SetProperty(ref _showVaultFilter, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string VaultFilterDescription
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_vaultFilterSelection == null || _vaultFilterSelection == AppResources.AllVaults)
|
|
||||||
{
|
|
||||||
return string.Format(AppResources.VaultFilterDescription, AppResources.All);
|
|
||||||
}
|
|
||||||
return string.Format(AppResources.VaultFilterDescription, _vaultFilterSelection);
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _vaultFilterSelection, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetVaultFilterOrgId()
|
|
||||||
{
|
|
||||||
return _organizations?.FirstOrDefault(o => o.Name == _vaultFilterSelection)?.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool IsVaultFilterMyVault => _vaultFilterSelection == AppResources.MyVault;
|
|
||||||
|
|
||||||
protected bool IsVaultFilterOrgVault => _vaultFilterSelection != AppResources.AllVaults &&
|
|
||||||
_vaultFilterSelection != AppResources.MyVault;
|
|
||||||
|
|
||||||
protected async Task InitVaultFilterAsync(bool shouldUpdateShowVaultFilter)
|
|
||||||
{
|
|
||||||
_organizations = await organizationService.GetAllAsync();
|
|
||||||
if (_organizations?.Any() ?? false)
|
|
||||||
{
|
|
||||||
_personalOwnershipPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
|
||||||
var singleOrgPolicyApplies = await policyService.PolicyAppliesToUser(PolicyType.OnlyOrg);
|
|
||||||
if (_vaultFilterSelection == null || (_personalOwnershipPolicyApplies && singleOrgPolicyApplies))
|
|
||||||
{
|
|
||||||
VaultFilterDescription = AppResources.AllVaults;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldUpdateShowVaultFilter)
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
ShowVaultFilter = await policyService.ShouldShowVaultFilterAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<List<CipherView>> GetAllCiphersAsync()
|
|
||||||
{
|
|
||||||
var decCiphers = await cipherService.GetAllDecryptedAsync();
|
|
||||||
if (IsVaultFilterMyVault)
|
|
||||||
{
|
|
||||||
return decCiphers.Where(c => c.OrganizationId == null).ToList();
|
|
||||||
}
|
|
||||||
if (IsVaultFilterOrgVault)
|
|
||||||
{
|
|
||||||
var orgId = GetVaultFilterOrgId();
|
|
||||||
return decCiphers.Where(c => c.OrganizationId == orgId).ToList();
|
|
||||||
}
|
|
||||||
return decCiphers;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task VaultFilterOptionsAsync()
|
|
||||||
{
|
|
||||||
var options = new List<string> { AppResources.AllVaults };
|
|
||||||
if (!_personalOwnershipPolicyApplies)
|
|
||||||
{
|
|
||||||
options.Add(AppResources.MyVault);
|
|
||||||
}
|
|
||||||
if (_organizations.Any())
|
|
||||||
{
|
|
||||||
options.AddRange(_organizations.OrderBy(o => o.Name).Select(o => o.Name));
|
|
||||||
}
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.FilterByVault, AppResources.Cancel, null,
|
|
||||||
options.ToArray());
|
|
||||||
if (selection == null || selection == AppResources.Cancel ||
|
|
||||||
(_vaultFilterSelection == null && selection == AppResources.AllVaults) ||
|
|
||||||
(_vaultFilterSelection != null && _vaultFilterSelection == selection))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VaultFilterDescription = selection;
|
|
||||||
await OnVaultFilterSelectedAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Task OnVaultFilterSelectedAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
100
src/App/Resources/AppResources.Designer.cs
generated
100
src/App/Resources/AppResources.Designer.cs
generated
@@ -1235,18 +1235,6 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Off {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("Off", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string On {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("On", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Status {
|
public static string Status {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Status", resourceCulture);
|
return ResourceManager.GetString("Status", resourceCulture);
|
||||||
@@ -1493,15 +1481,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string CopyTotpAutomaticallyDescription {
|
public static string DisableAutoTotpCopyDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("CopyTotpAutomaticallyDescription", resourceCulture);
|
return ResourceManager.GetString("DisableAutoTotpCopyDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string CopyTotpAutomatically {
|
public static string DisableAutoTotpCopy {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("CopyTotpAutomatically", resourceCulture);
|
return ResourceManager.GetString("DisableAutoTotpCopy", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1931,15 +1919,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ShowWebsiteIcons {
|
public static string DisableWebsiteIcons {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("ShowWebsiteIcons", resourceCulture);
|
return ResourceManager.GetString("DisableWebsiteIcons", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ShowWebsiteIconsDescription {
|
public static string DisableWebsiteIconsDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("ShowWebsiteIconsDescription", resourceCulture);
|
return ResourceManager.GetString("DisableWebsiteIconsDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2711,18 +2699,6 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string DefaultDarkTheme {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("DefaultDarkTheme", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string DefaultDarkThemeDescription {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("DefaultDarkThemeDescription", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string CopyNotes {
|
public static string CopyNotes {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("CopyNotes", resourceCulture);
|
return ResourceManager.GetString("CopyNotes", resourceCulture);
|
||||||
@@ -2753,33 +2729,27 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Nord {
|
public static string BlacklistedUris {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Nord", resourceCulture);
|
return ResourceManager.GetString("BlacklistedUris", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string AutofillBlockedUris {
|
public static string BlacklistedUrisDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("AutofillBlockedUris", resourceCulture);
|
return ResourceManager.GetString("BlacklistedUrisDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string AutofillBlockedUrisDescription {
|
public static string DisableSavePrompt {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("AutofillBlockedUrisDescription", resourceCulture);
|
return ResourceManager.GetString("DisableSavePrompt", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string AskToAddLogin {
|
public static string DisableSavePromptDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("AskToAddLogin", resourceCulture);
|
return ResourceManager.GetString("DisableSavePromptDescription", resourceCulture);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string AskToAddLoginDescription {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("AskToAddLoginDescription", resourceCulture);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4007,15 +3977,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string PasswordIsVisibleTapToHide {
|
public static string VisibilityTogglePasswordIsVisibleActivateToHide {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("PasswordIsVisibleTapToHide", resourceCulture);
|
return ResourceManager.GetString("VisibilityTogglePasswordIsVisibleActivateToHide", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string PasswordIsNotVisibleTapToShow {
|
public static string VisibilityTogglePasswordIsNotVisibleActivateToHide {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("PasswordIsNotVisibleTapToShow", resourceCulture);
|
return ResourceManager.GetString("VisibilityTogglePasswordIsNotVisibleActivateToHide", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4048,35 +4018,5 @@ namespace Bit.App.Resources {
|
|||||||
return ResourceManager.GetString("All", resourceCulture);
|
return ResourceManager.GetString("All", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string NeverLockWarning {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("NeverLockWarning", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string EnvironmentPageUrlsError {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("EnvironmentPageUrlsError", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GenericErrorMessage {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("GenericErrorMessage", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string AllowScreenCapture {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("AllowScreenCapture", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string AreYouSureYouWantToEnableScreenCapture {
|
|
||||||
get {
|
|
||||||
return ResourceManager.GetString("AreYouSureYouWantToEnableScreenCapture", resourceCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,7 +236,7 @@
|
|||||||
<comment>The button text that allows user to launch the website to their web browser.</comment>
|
<comment>The button text that allows user to launch the website to their web browser.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="HelpAndFeedback" xml:space="preserve">
|
<data name="HelpAndFeedback" xml:space="preserve">
|
||||||
<value>Help and feedback</value>
|
<value>Help and Feedback</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Hide" xml:space="preserve">
|
<data name="Hide" xml:space="preserve">
|
||||||
<value>Hide</value>
|
<value>Hide</value>
|
||||||
@@ -269,7 +269,7 @@
|
|||||||
<comment>Title for login page. (noun)</comment>
|
<comment>Title for login page. (noun)</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogOut" xml:space="preserve">
|
<data name="LogOut" xml:space="preserve">
|
||||||
<value>Log out</value>
|
<value>Log Out</value>
|
||||||
<comment>The log out button text (verb).</comment>
|
<comment>The log out button text (verb).</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogoutConfirmation" xml:space="preserve">
|
<data name="LogoutConfirmation" xml:space="preserve">
|
||||||
@@ -412,13 +412,13 @@
|
|||||||
<value>Add an Item</value>
|
<value>Add an Item</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AppExtension" xml:space="preserve">
|
<data name="AppExtension" xml:space="preserve">
|
||||||
<value>App extension</value>
|
<value>App Extension</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillAccessibilityDescription" xml:space="preserve">
|
<data name="AutofillAccessibilityDescription" xml:space="preserve">
|
||||||
<value>Use the Bitwarden accessibility service to auto-fill your logins across apps and the web.</value>
|
<value>Use the Bitwarden accessibility service to auto-fill your logins across apps and the web.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillService" xml:space="preserve">
|
<data name="AutofillService" xml:space="preserve">
|
||||||
<value>Auto-fill service</value>
|
<value>Auto-fill Service</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AvoidAmbiguousCharacters" xml:space="preserve">
|
<data name="AvoidAmbiguousCharacters" xml:space="preserve">
|
||||||
<value>Avoid Ambiguous Characters</value>
|
<value>Avoid Ambiguous Characters</value>
|
||||||
@@ -445,7 +445,7 @@
|
|||||||
<value>You can change your email address on the bitwarden.com web vault. Do you want to visit the website now?</value>
|
<value>You can change your email address on the bitwarden.com web vault. Do you want to visit the website now?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChangeMasterPassword" xml:space="preserve">
|
<data name="ChangeMasterPassword" xml:space="preserve">
|
||||||
<value>Change master password</value>
|
<value>Change Master Password</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ChangePasswordConfirmation" xml:space="preserve">
|
<data name="ChangePasswordConfirmation" xml:space="preserve">
|
||||||
<value>You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?</value>
|
<value>You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?</value>
|
||||||
@@ -473,13 +473,13 @@
|
|||||||
<value>Enter your account email address to receive your master password hint.</value>
|
<value>Enter your account email address to receive your master password hint.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExntesionReenable" xml:space="preserve">
|
<data name="ExntesionReenable" xml:space="preserve">
|
||||||
<value>Re-enable app extension</value>
|
<value>Re-enable App Extension</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExtensionAlmostDone" xml:space="preserve">
|
<data name="ExtensionAlmostDone" xml:space="preserve">
|
||||||
<value>Almost done!</value>
|
<value>Almost done!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExtensionEnable" xml:space="preserve">
|
<data name="ExtensionEnable" xml:space="preserve">
|
||||||
<value>Enable app extension</value>
|
<value>Enable App Extension</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExtensionInSafari" xml:space="preserve">
|
<data name="ExtensionInSafari" xml:space="preserve">
|
||||||
<value>In Safari, find Bitwarden using the share icon (hint: scroll to the right on the bottom row of the menu).</value>
|
<value>In Safari, find Bitwarden using the share icon (hint: scroll to the right on the bottom row of the menu).</value>
|
||||||
@@ -516,7 +516,7 @@
|
|||||||
<value>Get your master password hint</value>
|
<value>Get your master password hint</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImportItems" xml:space="preserve">
|
<data name="ImportItems" xml:space="preserve">
|
||||||
<value>Import items</value>
|
<value>Import Items</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImportItemsConfirmation" xml:space="preserve">
|
<data name="ImportItemsConfirmation" xml:space="preserve">
|
||||||
<value>You can bulk import items from the bitwarden.com web vault. Do you want to visit the website now?</value>
|
<value>You can bulk import items from the bitwarden.com web vault. Do you want to visit the website now?</value>
|
||||||
@@ -549,10 +549,10 @@
|
|||||||
<value>Immediately</value>
|
<value>Immediately</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VaultTimeout" xml:space="preserve">
|
<data name="VaultTimeout" xml:space="preserve">
|
||||||
<value>Vault timeout</value>
|
<value>Vault Timeout</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VaultTimeoutAction" xml:space="preserve">
|
<data name="VaultTimeoutAction" xml:space="preserve">
|
||||||
<value>Vault timeout action</value>
|
<value>Vault Timeout Action</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VaultTimeoutLogOutConfirmation" xml:space="preserve">
|
<data name="VaultTimeoutLogOutConfirmation" xml:space="preserve">
|
||||||
<value>Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?</value>
|
<value>Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?</value>
|
||||||
@@ -647,7 +647,7 @@
|
|||||||
<comment>Push notifications for apple products</comment>
|
<comment>Push notifications for apple products</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="RateTheApp" xml:space="preserve">
|
<data name="RateTheApp" xml:space="preserve">
|
||||||
<value>Rate the app</value>
|
<value>Rate the App</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RateTheAppDescription" xml:space="preserve">
|
<data name="RateTheAppDescription" xml:space="preserve">
|
||||||
<value>Please consider helping us out with a good review!</value>
|
<value>Please consider helping us out with a good review!</value>
|
||||||
@@ -694,14 +694,14 @@
|
|||||||
<value>Syncing failed.</value>
|
<value>Syncing failed.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SyncVaultNow" xml:space="preserve">
|
<data name="SyncVaultNow" xml:space="preserve">
|
||||||
<value>Sync vault now</value>
|
<value>Sync Vault Now</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TouchID" xml:space="preserve">
|
<data name="TouchID" xml:space="preserve">
|
||||||
<value>Touch ID</value>
|
<value>Touch ID</value>
|
||||||
<comment>What Apple calls their fingerprint reader.</comment>
|
<comment>What Apple calls their fingerprint reader.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="TwoStepLogin" xml:space="preserve">
|
<data name="TwoStepLogin" xml:space="preserve">
|
||||||
<value>Two-step login</value>
|
<value>Two-step Login</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TwoStepLoginConfirmation" xml:space="preserve">
|
<data name="TwoStepLoginConfirmation" xml:space="preserve">
|
||||||
<value>Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?</value>
|
<value>Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be enabled on the bitwarden.com web vault. Do you want to visit the website now?</value>
|
||||||
@@ -710,7 +710,7 @@
|
|||||||
<value>Unlock with {0}</value>
|
<value>Unlock with {0}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UnlockWithPIN" xml:space="preserve">
|
<data name="UnlockWithPIN" xml:space="preserve">
|
||||||
<value>Unlock with PIN code</value>
|
<value>Unlock with PIN Code</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Validating" xml:space="preserve">
|
<data name="Validating" xml:space="preserve">
|
||||||
<value>Validating</value>
|
<value>Validating</value>
|
||||||
@@ -723,7 +723,7 @@
|
|||||||
<value>View Item</value>
|
<value>View Item</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="WebVault" xml:space="preserve">
|
<data name="WebVault" xml:space="preserve">
|
||||||
<value>Bitwarden web vault</value>
|
<value>Bitwarden Web Vault</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Lost2FAApp" xml:space="preserve">
|
<data name="Lost2FAApp" xml:space="preserve">
|
||||||
<value>Lost authenticator app?</value>
|
<value>Lost authenticator app?</value>
|
||||||
@@ -733,7 +733,7 @@
|
|||||||
<comment>Screen title</comment>
|
<comment>Screen title</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExtensionActivated" xml:space="preserve">
|
<data name="ExtensionActivated" xml:space="preserve">
|
||||||
<value>Extension activated!</value>
|
<value>Extension Activated!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Icons" xml:space="preserve">
|
<data name="Icons" xml:space="preserve">
|
||||||
<value>Icons</value>
|
<value>Icons</value>
|
||||||
@@ -770,12 +770,6 @@
|
|||||||
<data name="Enabled" xml:space="preserve">
|
<data name="Enabled" xml:space="preserve">
|
||||||
<value>Enabled</value>
|
<value>Enabled</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Off" xml:space="preserve">
|
|
||||||
<value>Off</value>
|
|
||||||
</data>
|
|
||||||
<data name="On" xml:space="preserve">
|
|
||||||
<value>On</value>
|
|
||||||
</data>
|
|
||||||
<data name="Status" xml:space="preserve">
|
<data name="Status" xml:space="preserve">
|
||||||
<value>Status</value>
|
<value>Status</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -804,7 +798,7 @@
|
|||||||
<value>You are searching for an auto-fill item for "{0}".</value>
|
<value>You are searching for an auto-fill item for "{0}".</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LearnOrg" xml:space="preserve">
|
<data name="LearnOrg" xml:space="preserve">
|
||||||
<value>Learn about organizations</value>
|
<value>Learn About Organizations</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CannotOpenApp" xml:space="preserve">
|
<data name="CannotOpenApp" xml:space="preserve">
|
||||||
<value>Cannot open the app "{0}".</value>
|
<value>Cannot open the app "{0}".</value>
|
||||||
@@ -913,11 +907,11 @@
|
|||||||
<data name="CopyTotp" xml:space="preserve">
|
<data name="CopyTotp" xml:space="preserve">
|
||||||
<value>Copy TOTP</value>
|
<value>Copy TOTP</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CopyTotpAutomaticallyDescription" xml:space="preserve">
|
<data name="DisableAutoTotpCopyDescription" xml:space="preserve">
|
||||||
<value>If a login has an authenticator key, copy the TOTP verification code to your clip-board when you auto-fill the login.</value>
|
<value>If your login has an authenticator key attached to it, the TOTP verification code is automatically copied to your clipboard whenever you auto-fill the login.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CopyTotpAutomatically" xml:space="preserve">
|
<data name="DisableAutoTotpCopy" xml:space="preserve">
|
||||||
<value>Copy TOTP automatically</value>
|
<value>Disable Automatic TOTP Copy</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PremiumRequired" xml:space="preserve">
|
<data name="PremiumRequired" xml:space="preserve">
|
||||||
<value>A premium membership is required to use this feature.</value>
|
<value>A premium membership is required to use this feature.</value>
|
||||||
@@ -1134,11 +1128,11 @@
|
|||||||
<data name="Expiration" xml:space="preserve">
|
<data name="Expiration" xml:space="preserve">
|
||||||
<value>Expiration</value>
|
<value>Expiration</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ShowWebsiteIcons" xml:space="preserve">
|
<data name="DisableWebsiteIcons" xml:space="preserve">
|
||||||
<value>Show website icons</value>
|
<value>Disable Website Icons</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ShowWebsiteIconsDescription" xml:space="preserve">
|
<data name="DisableWebsiteIconsDescription" xml:space="preserve">
|
||||||
<value>Show a recognizable image next to each login.</value>
|
<value>Website Icons provide a recognizable image next to each login item in your vault.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="IconsUrl" xml:space="preserve">
|
<data name="IconsUrl" xml:space="preserve">
|
||||||
<value>Icons Server URL</value>
|
<value>Icons Server URL</value>
|
||||||
@@ -1317,7 +1311,7 @@
|
|||||||
<value>5. Select "Bitwarden"</value>
|
<value>5. Select "Bitwarden"</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PasswordAutofill" xml:space="preserve">
|
<data name="PasswordAutofill" xml:space="preserve">
|
||||||
<value>Password auto-fill</value>
|
<value>Password AutoFill</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="BitwardenAutofillAlert2" xml:space="preserve">
|
<data name="BitwardenAutofillAlert2" xml:space="preserve">
|
||||||
<value>The easiest way to add new logins to your vault is by using the Bitwarden Password AutoFill extension. Learn more about using the Bitwarden Password AutoFill extension by navigating to the "Settings" screen.</value>
|
<value>The easiest way to add new logins to your vault is by using the Bitwarden Password AutoFill extension. Learn more about using the Bitwarden Password AutoFill extension by navigating to the "Settings" screen.</value>
|
||||||
@@ -1455,7 +1449,7 @@
|
|||||||
<value>There are no folders to list.</value>
|
<value>There are no folders to list.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FingerprintPhrase" xml:space="preserve">
|
<data name="FingerprintPhrase" xml:space="preserve">
|
||||||
<value>Fingerprint phrase</value>
|
<value>Fingerprint Phrase</value>
|
||||||
<comment>A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing.</comment>
|
<comment>A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="YourAccountsFingerprint" xml:space="preserve">
|
<data name="YourAccountsFingerprint" xml:space="preserve">
|
||||||
@@ -1466,10 +1460,10 @@
|
|||||||
<value>Bitwarden allows you to share your vault items with others by using an organization account. Would you like to visit the bitwarden.com website to learn more?</value>
|
<value>Bitwarden allows you to share your vault items with others by using an organization account. Would you like to visit the bitwarden.com website to learn more?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExportVault" xml:space="preserve">
|
<data name="ExportVault" xml:space="preserve">
|
||||||
<value>Export vault</value>
|
<value>Export Vault</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LockNow" xml:space="preserve">
|
<data name="LockNow" xml:space="preserve">
|
||||||
<value>Lock now</value>
|
<value>Lock Now</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PIN" xml:space="preserve">
|
<data name="PIN" xml:space="preserve">
|
||||||
<value>PIN</value>
|
<value>PIN</value>
|
||||||
@@ -1523,7 +1517,7 @@
|
|||||||
<value>2 minutes</value>
|
<value>2 minutes</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearClipboard" xml:space="preserve">
|
<data name="ClearClipboard" xml:space="preserve">
|
||||||
<value>Clear clipboard</value>
|
<value>Clear Clipboard</value>
|
||||||
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClearClipboardDescription" xml:space="preserve">
|
<data name="ClearClipboardDescription" xml:space="preserve">
|
||||||
@@ -1531,7 +1525,7 @@
|
|||||||
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="DefaultUriMatchDetection" xml:space="preserve">
|
<data name="DefaultUriMatchDetection" xml:space="preserve">
|
||||||
<value>Default URI match detection</value>
|
<value>Default URI Match Detection</value>
|
||||||
<comment>Default URI match detection for auto-fill.</comment>
|
<comment>Default URI match detection for auto-fill.</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="DefaultUriMatchDetectionDescription" xml:space="preserve">
|
<data name="DefaultUriMatchDetectionDescription" xml:space="preserve">
|
||||||
@@ -1547,12 +1541,6 @@
|
|||||||
<data name="ThemeDefault" xml:space="preserve">
|
<data name="ThemeDefault" xml:space="preserve">
|
||||||
<value>Default (System)</value>
|
<value>Default (System)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DefaultDarkTheme" xml:space="preserve">
|
|
||||||
<value>Default dark theme</value>
|
|
||||||
</data>
|
|
||||||
<data name="DefaultDarkThemeDescription" xml:space="preserve">
|
|
||||||
<value>Choose the dark theme to use when using Default (System) theme while your device's dark mode is enabled.</value>
|
|
||||||
</data>
|
|
||||||
<data name="CopyNotes" xml:space="preserve">
|
<data name="CopyNotes" xml:space="preserve">
|
||||||
<value>Copy Note</value>
|
<value>Copy Note</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1569,24 +1557,20 @@
|
|||||||
<value>Black</value>
|
<value>Black</value>
|
||||||
<comment>The color black</comment>
|
<comment>The color black</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="Nord" xml:space="preserve">
|
<data name="BlacklistedUris" xml:space="preserve">
|
||||||
<value>Nord</value>
|
<value>Blacklisted URIs</value>
|
||||||
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillBlockedUris" xml:space="preserve">
|
<data name="BlacklistedUrisDescription" xml:space="preserve">
|
||||||
<value>Auto-fill blocked URIs</value>
|
<value>URIs that are blacklisted will not offer auto-fill. The list should be comma separated. Ex: "https://twitter.com, androidapp://com.twitter.android".</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
|
<data name="DisableSavePrompt" xml:space="preserve">
|
||||||
<value>Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".</value>
|
<value>Disable Save Prompt</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AskToAddLogin" xml:space="preserve">
|
<data name="DisableSavePromptDescription" xml:space="preserve">
|
||||||
<value>Ask to add login</value>
|
<value>The "Save Prompt" automatically prompts you to save new items to your vault whenever you enter them for the first time.</value>
|
||||||
</data>
|
|
||||||
<data name="AskToAddLoginDescription" xml:space="preserve">
|
|
||||||
<value>Ask to add an item if one isn't found in your vault.</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="OnRestart" xml:space="preserve">
|
<data name="OnRestart" xml:space="preserve">
|
||||||
<value>On app restart</value>
|
<value>On App Restart</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillServiceNotEnabled" xml:space="preserve">
|
<data name="AutofillServiceNotEnabled" xml:space="preserve">
|
||||||
<value>Auto-fill makes it easy to securely access your Bitwarden vault from other websites and apps. It looks like you have not enabled an auto-fill service for Bitwarden. Enable auto-fill for Bitwarden from the "Settings" screen.</value>
|
<value>Auto-fill makes it easy to securely access your Bitwarden vault from other websites and apps. It looks like you have not enabled an auto-fill service for Bitwarden. Enable auto-fill for Bitwarden from the "Settings" screen.</value>
|
||||||
@@ -1820,16 +1804,16 @@
|
|||||||
<value>Bitwarden needs attention - Enable "Draw-Over" in "Auto-fill Services" from Bitwarden Settings</value>
|
<value>Bitwarden needs attention - Enable "Draw-Over" in "Auto-fill Services" from Bitwarden Settings</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutofillServices" xml:space="preserve">
|
<data name="AutofillServices" xml:space="preserve">
|
||||||
<value>Auto-fill services</value>
|
<value>Auto-fill Services</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InlineAutofill" xml:space="preserve">
|
<data name="InlineAutofill" xml:space="preserve">
|
||||||
<value>Use inline autofill</value>
|
<value>Use Inline Autofill</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InlineAutofillDescription" xml:space="preserve">
|
<data name="InlineAutofillDescription" xml:space="preserve">
|
||||||
<value>Use inline autofill if your selected IME (keyboard) supports it. If your configuration is not supported (or this option is disabled), the default Autofill overlay will be used.</value>
|
<value>Use inline autofill if your selected IME (keyboard) supports it. If your configuration is not supported (or this option is disabled), the default Autofill overlay will be used.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Accessibility" xml:space="preserve">
|
<data name="Accessibility" xml:space="preserve">
|
||||||
<value>Use accessibility</value>
|
<value>Use Accessibility</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AccessibilityDescription" xml:space="preserve">
|
<data name="AccessibilityDescription" xml:space="preserve">
|
||||||
<value>Use the Bitwarden Accessibility Service to auto-fill your logins across apps and the web. When enabled, we'll display a popup when login fields are selected.</value>
|
<value>Use the Bitwarden Accessibility Service to auto-fill your logins across apps and the web. When enabled, we'll display a popup when login fields are selected.</value>
|
||||||
@@ -1844,7 +1828,7 @@
|
|||||||
<value>Required to use the Autofill Quick-Action Tile, or to augment the Autofill Service by using Draw-Over (if enabled).</value>
|
<value>Required to use the Autofill Quick-Action Tile, or to augment the Autofill Service by using Draw-Over (if enabled).</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DrawOver" xml:space="preserve">
|
<data name="DrawOver" xml:space="preserve">
|
||||||
<value>Use draw-over</value>
|
<value>Use Draw-Over</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DrawOverDescription" xml:space="preserve">
|
<data name="DrawOverDescription" xml:space="preserve">
|
||||||
<value>When enabled, allows the Bitwarden Accessibility Service to display a popup when login fields are selected.</value>
|
<value>When enabled, allows the Bitwarden Accessibility Service to display a popup when login fields are selected.</value>
|
||||||
@@ -2165,13 +2149,13 @@
|
|||||||
<value>Account removed successfully</value>
|
<value>Account removed successfully</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeleteAccount" xml:space="preserve">
|
<data name="DeleteAccount" xml:space="preserve">
|
||||||
<value>Delete account</value>
|
<value>Delete Account</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeletingYourAccountIsPermanent" xml:space="preserve">
|
<data name="DeletingYourAccountIsPermanent" xml:space="preserve">
|
||||||
<value>Deleting your account is permanent</value>
|
<value>Deleting your account is permanent</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeleteAccountExplanation" xml:space="preserve">
|
<data name="DeleteAccountExplanation" xml:space="preserve">
|
||||||
<value>Your account and all vault data will be erased and unrecoverable. Are you sure you want to continue?</value>
|
<value>Your account and all associated data will be erased and unrecoverable. Are you sure you want to continue?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="DeletingYourAccount" xml:space="preserve">
|
<data name="DeletingYourAccount" xml:space="preserve">
|
||||||
<value>Deleting your account</value>
|
<value>Deleting your account</value>
|
||||||
@@ -2239,11 +2223,11 @@
|
|||||||
<data name="TapToGoBack" xml:space="preserve">
|
<data name="TapToGoBack" xml:space="preserve">
|
||||||
<value>Tap to go back</value>
|
<value>Tap to go back</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PasswordIsVisibleTapToHide" xml:space="preserve">
|
<data name="VisibilityTogglePasswordIsVisibleActivateToHide" xml:space="preserve">
|
||||||
<value>Password is visible, tap to hide.</value>
|
<value>Visibility Toggle, Password is visible, activate to hide.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PasswordIsNotVisibleTapToShow" xml:space="preserve">
|
<data name="VisibilityTogglePasswordIsNotVisibleActivateToHide" xml:space="preserve">
|
||||||
<value>Password is not visible, tap to show.</value>
|
<value>Visibility Toggle, Password is not visible, activate to show.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FilterByVault" xml:space="preserve">
|
<data name="FilterByVault" xml:space="preserve">
|
||||||
<value>Filter items by vault</value>
|
<value>Filter items by vault</value>
|
||||||
@@ -2260,19 +2244,4 @@
|
|||||||
<data name="All" xml:space="preserve">
|
<data name="All" xml:space="preserve">
|
||||||
<value>All</value>
|
<value>All</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NeverLockWarning" xml:space="preserve">
|
|
||||||
<value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value>
|
|
||||||
</data>
|
|
||||||
<data name="EnvironmentPageUrlsError" xml:space="preserve">
|
|
||||||
<value>One or more of the URLs entered are invalid. Please revise it and try to save again.</value>
|
|
||||||
</data>
|
|
||||||
<data name="GenericErrorMessage" xml:space="preserve">
|
|
||||||
<value>We were unable to process your request. Please try again or contact us.</value>
|
|
||||||
</data>
|
|
||||||
<data name="AllowScreenCapture" xml:space="preserve">
|
|
||||||
<value>Allow screen capture</value>
|
|
||||||
</data>
|
|
||||||
<data name="AreYouSureYouWantToEnableScreenCapture" xml:space="preserve">
|
|
||||||
<value>Are you sure you want to enable Screen Capture?</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ namespace Bit.App.Services
|
|||||||
Constants.LastBuildKey,
|
Constants.LastBuildKey,
|
||||||
Constants.ClearCiphersCacheKey,
|
Constants.ClearCiphersCacheKey,
|
||||||
Constants.BiometricIntegrityKey,
|
Constants.BiometricIntegrityKey,
|
||||||
Constants.iOSExtensionActiveUserIdKey,
|
|
||||||
Constants.iOSAutoFillClearCiphersCacheKey,
|
Constants.iOSAutoFillClearCiphersCacheKey,
|
||||||
Constants.iOSAutoFillBiometricIntegrityKey,
|
Constants.iOSAutoFillBiometricIntegrityKey,
|
||||||
Constants.iOSExtensionClearCiphersCacheKey,
|
Constants.iOSExtensionClearCiphersCacheKey,
|
||||||
@@ -33,7 +32,7 @@ namespace Bit.App.Services
|
|||||||
Constants.iOSShareExtensionClearCiphersCacheKey,
|
Constants.iOSShareExtensionClearCiphersCacheKey,
|
||||||
Constants.iOSShareExtensionBiometricIntegrityKey,
|
Constants.iOSShareExtensionBiometricIntegrityKey,
|
||||||
Constants.RememberedEmailKey,
|
Constants.RememberedEmailKey,
|
||||||
Constants.RememberedOrgIdentifierKey
|
Constants.RememberedOrgIdentifierKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
public MobileStorageService(
|
public MobileStorageService(
|
||||||
|
|||||||
@@ -6,11 +6,21 @@ using Bit.App.Resources;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Utilities.AccountManagement
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
{
|
{
|
||||||
|
public static class AccountsManagerMessageCommands
|
||||||
|
{
|
||||||
|
public const string LOCKED = "locked";
|
||||||
|
public const string LOCK_VAULT = "lockVault";
|
||||||
|
public const string LOGOUT = "logout";
|
||||||
|
public const string LOGGED_OUT = "loggedOut";
|
||||||
|
public const string ADD_ACCOUNT = "addAccount";
|
||||||
|
public const string ACCOUNT_ADDED = "accountAdded";
|
||||||
|
public const string SWITCHED_ACCOUNT = "switchedAccount";
|
||||||
|
}
|
||||||
|
|
||||||
public class AccountsManager : IAccountsManager
|
public class AccountsManager : IAccountsManager
|
||||||
{
|
{
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
@@ -19,7 +29,6 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IAuthService _authService;
|
private readonly IAuthService _authService;
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
Func<AppOptions> _getOptionsFunc;
|
Func<AppOptions> _getOptionsFunc;
|
||||||
private IAccountsManagerHost _accountsManagerHost;
|
private IAccountsManagerHost _accountsManagerHost;
|
||||||
@@ -29,8 +38,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
IStorageService secureStorageService,
|
IStorageService secureStorageService,
|
||||||
IStateService stateService,
|
IStateService stateService,
|
||||||
IPlatformUtilsService platformUtilsService,
|
IPlatformUtilsService platformUtilsService,
|
||||||
IAuthService authService,
|
IAuthService authService)
|
||||||
ILogger logger)
|
|
||||||
{
|
{
|
||||||
_broadcasterService = broadcasterService;
|
_broadcasterService = broadcasterService;
|
||||||
_vaultTimeoutService = vaultTimeoutService;
|
_vaultTimeoutService = vaultTimeoutService;
|
||||||
@@ -38,7 +46,6 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
_platformUtilsService = platformUtilsService;
|
_platformUtilsService = platformUtilsService;
|
||||||
_authService = authService;
|
_authService = authService;
|
||||||
_logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };
|
||||||
@@ -112,49 +119,42 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
|
|
||||||
private async void OnMessage(Message message)
|
private async void OnMessage(Message message)
|
||||||
{
|
{
|
||||||
try
|
switch (message.Command)
|
||||||
{
|
{
|
||||||
switch (message.Command)
|
case AccountsManagerMessageCommands.LOCKED:
|
||||||
{
|
Locked(message.Data as Tuple<string, bool>);
|
||||||
case AccountsManagerMessageCommands.LOCKED:
|
break;
|
||||||
await Device.InvokeOnMainThreadAsync(() => LockedAsync(message.Data as Tuple<string, bool>));
|
case AccountsManagerMessageCommands.LOCK_VAULT:
|
||||||
break;
|
await _vaultTimeoutService.LockAsync(true);
|
||||||
case AccountsManagerMessageCommands.LOCK_VAULT:
|
break;
|
||||||
await _vaultTimeoutService.LockAsync(true);
|
case AccountsManagerMessageCommands.LOGOUT:
|
||||||
break;
|
LogOut(message.Data as Tuple<string, bool, bool>);
|
||||||
case AccountsManagerMessageCommands.LOGOUT:
|
break;
|
||||||
var extras = message.Data as Tuple<string, bool, bool>;
|
case AccountsManagerMessageCommands.LOGGED_OUT:
|
||||||
var userId = extras?.Item1;
|
// Clean up old migrated key if they ever log out.
|
||||||
var userInitiated = extras?.Item2 ?? true;
|
await _secureStorageService.RemoveAsync("oldKey");
|
||||||
var expired = extras?.Item3 ?? false;
|
break;
|
||||||
await Device.InvokeOnMainThreadAsync(() => LogOutAsync(userId, userInitiated, expired));
|
case AccountsManagerMessageCommands.ADD_ACCOUNT:
|
||||||
break;
|
AddAccount();
|
||||||
case AccountsManagerMessageCommands.LOGGED_OUT:
|
break;
|
||||||
// Clean up old migrated key if they ever log out.
|
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
|
||||||
await _secureStorageService.RemoveAsync("oldKey");
|
await _accountsManagerHost.UpdateThemeAsync();
|
||||||
break;
|
break;
|
||||||
case AccountsManagerMessageCommands.ADD_ACCOUNT:
|
case AccountsManagerMessageCommands.SWITCHED_ACCOUNT:
|
||||||
await AddAccountAsync();
|
await SwitchedAccountAsync();
|
||||||
break;
|
break;
|
||||||
case AccountsManagerMessageCommands.ACCOUNT_ADDED:
|
|
||||||
await _accountsManagerHost.UpdateThemeAsync();
|
|
||||||
break;
|
|
||||||
case AccountsManagerMessageCommands.SWITCHED_ACCOUNT:
|
|
||||||
await SwitchedAccountAsync();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Exception(ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LockedAsync(Tuple<string, bool> extras)
|
private void Locked(Tuple<string, bool> extras)
|
||||||
{
|
{
|
||||||
var userId = extras?.Item1;
|
var userId = extras?.Item1;
|
||||||
var userInitiated = extras?.Item2 ?? false;
|
var userInitiated = extras?.Item2 ?? false;
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LockedAsync(string userId, bool userInitiated)
|
||||||
|
{
|
||||||
if (!await _stateService.IsActiveAccountAsync(userId))
|
if (!await _stateService.IsActiveAccountAsync(userId))
|
||||||
{
|
{
|
||||||
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
|
||||||
@@ -173,19 +173,27 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
|
|
||||||
await _accountsManagerHost.SetPreviousPageInfoAsync();
|
await _accountsManagerHost.SetPreviousPageInfoAsync();
|
||||||
|
|
||||||
await Device.InvokeOnMainThreadAsync(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
|
Device.BeginInvokeOnMainThread(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddAccountAsync()
|
private void AddAccount()
|
||||||
{
|
{
|
||||||
await Device.InvokeOnMainThreadAsync(() =>
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
Options.HideAccountSwitcher = false;
|
Options.HideAccountSwitcher = false;
|
||||||
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogOutAsync(string userId, bool userInitiated, bool expired)
|
private void LogOut(Tuple<string, bool, bool> extras)
|
||||||
|
{
|
||||||
|
var userId = extras?.Item1;
|
||||||
|
var userInitiated = extras?.Item2 ?? true;
|
||||||
|
var expired = extras?.Item3 ?? false;
|
||||||
|
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
|
||||||
{
|
{
|
||||||
await AppHelpers.LogOutAsync(userId, userInitiated);
|
await AppHelpers.LogOutAsync(userId, userInitiated);
|
||||||
await NavigateOnAccountChangeAsync();
|
await NavigateOnAccountChangeAsync();
|
||||||
@@ -201,7 +209,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
private async Task SwitchedAccountAsync()
|
private async Task SwitchedAccountAsync()
|
||||||
{
|
{
|
||||||
await AppHelpers.OnAccountSwitchAsync();
|
await AppHelpers.OnAccountSwitchAsync();
|
||||||
await Device.InvokeOnMainThreadAsync(async () =>
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
if (await _vaultTimeoutService.ShouldTimeoutAsync())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,18 +17,13 @@ namespace Bit.App.Utilities
|
|||||||
|
|
||||||
public static bool IsThemeDirty = false;
|
public static bool IsThemeDirty = false;
|
||||||
|
|
||||||
public const string Light = "light";
|
public static void SetThemeStyle(string name, ResourceDictionary resources)
|
||||||
public const string Dark = "dark";
|
|
||||||
public const string Black = "black";
|
|
||||||
public const string Nord = "nord";
|
|
||||||
|
|
||||||
public static void SetThemeStyle(string name, string autoDarkName, ResourceDictionary resources)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Resources = () => resources;
|
Resources = () => resources;
|
||||||
|
|
||||||
var newTheme = NeedsThemeUpdate(name, autoDarkName, resources);
|
var newTheme = NeedsThemeUpdate(name, resources);
|
||||||
if (newTheme is null)
|
if (newTheme is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -90,30 +85,22 @@ namespace Bit.App.Utilities
|
|||||||
: Activator.CreateInstance(themeType) as ResourceDictionary;
|
: Activator.CreateInstance(themeType) as ResourceDictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ResourceDictionary NeedsThemeUpdate(string themeName, string autoDarkThemeName, ResourceDictionary resources)
|
static ResourceDictionary NeedsThemeUpdate(string themeName, ResourceDictionary resources)
|
||||||
{
|
{
|
||||||
switch (themeName)
|
switch (themeName)
|
||||||
{
|
{
|
||||||
case Dark:
|
case "dark":
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
||||||
case Black:
|
case "black":
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
|
||||||
case Nord:
|
case "nord":
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
|
||||||
case Light:
|
case "light":
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
||||||
default:
|
default:
|
||||||
if (OsDarkModeEnabled())
|
if (OsDarkModeEnabled())
|
||||||
{
|
{
|
||||||
switch (autoDarkThemeName)
|
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
||||||
{
|
|
||||||
case Black:
|
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Black), resources);
|
|
||||||
case Nord:
|
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Nord), resources);
|
|
||||||
default:
|
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Dark), resources);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
return CheckAndGetThemeForMergedDictionaries(typeof(Light), resources);
|
||||||
}
|
}
|
||||||
@@ -121,7 +108,7 @@ namespace Bit.App.Utilities
|
|||||||
|
|
||||||
public static void SetTheme(ResourceDictionary resources)
|
public static void SetTheme(ResourceDictionary resources)
|
||||||
{
|
{
|
||||||
SetThemeStyle(GetTheme(), GetAutoDarkTheme(), resources);
|
SetThemeStyle(GetTheme(), resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetTheme()
|
public static string GetTheme()
|
||||||
@@ -130,12 +117,6 @@ namespace Bit.App.Utilities
|
|||||||
return stateService.GetThemeAsync().GetAwaiter().GetResult();
|
return stateService.GetThemeAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetAutoDarkTheme()
|
|
||||||
{
|
|
||||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
|
||||||
return stateService.GetAutoDarkThemeAsync().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool OsDarkModeEnabled()
|
public static bool OsDarkModeEnabled()
|
||||||
{
|
{
|
||||||
if (Application.Current == null)
|
if (Application.Current == null)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task PutDeleteCipherAsync(string id);
|
Task PutDeleteCipherAsync(string id);
|
||||||
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
||||||
Task RefreshIdentityTokenAsync();
|
Task RefreshIdentityTokenAsync();
|
||||||
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
|
Task<object> PreValidateSso(string identifier);
|
||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrls urls);
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
public interface IBroadcasterService
|
public interface IBroadcasterService
|
||||||
{
|
{
|
||||||
void Send(Message message);
|
void Send(Message message, string id = null);
|
||||||
void Send(Message message, string id);
|
|
||||||
void Subscribe(string id, Action<Message> messageCallback);
|
void Subscribe(string id, Action<Message> messageCallback);
|
||||||
void Unsubscribe(string id);
|
void Unsubscribe(string id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<string> GetActiveUserIdAsync();
|
Task<string> GetActiveUserIdAsync();
|
||||||
Task<bool> IsActiveAccountAsync(string userId = null);
|
Task<bool> IsActiveAccountAsync(string userId = null);
|
||||||
Task SetActiveUserAsync(string userId);
|
Task SetActiveUserAsync(string userId);
|
||||||
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
|
||||||
Task<bool> IsAuthenticatedAsync(string userId = null);
|
Task<bool> IsAuthenticatedAsync(string userId = null);
|
||||||
Task<string> GetUserIdAsync(string email);
|
Task<string> GetUserIdAsync(string email);
|
||||||
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
Task RefreshAccountViewsAsync(bool allowAddAccountRow);
|
||||||
@@ -110,8 +109,6 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetRememberedOrgIdentifierAsync(string value);
|
Task SetRememberedOrgIdentifierAsync(string value);
|
||||||
Task<string> GetThemeAsync(string userId = null);
|
Task<string> GetThemeAsync(string userId = null);
|
||||||
Task SetThemeAsync(string value, string userId = null);
|
Task SetThemeAsync(string value, string userId = null);
|
||||||
Task<string> GetAutoDarkThemeAsync(string userId = null);
|
|
||||||
Task SetAutoDarkThemeAsync(string value, string userId = null);
|
|
||||||
Task<bool?> GetAddSitePromptShownAsync(string userId = null);
|
Task<bool?> GetAddSitePromptShownAsync(string userId = null);
|
||||||
Task SetAddSitePromptShownAsync(bool? value, string userId = null);
|
Task SetAddSitePromptShownAsync(bool? value, string userId = null);
|
||||||
Task<bool?> GetPushInitialPromptShownAsync();
|
Task<bool?> GetPushInitialPromptShownAsync();
|
||||||
@@ -148,8 +145,5 @@ namespace Bit.Core.Abstractions
|
|||||||
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
Task SetRefreshTokenAsync(string value, bool skipTokenStorage, string userId = null);
|
||||||
Task<string> GetTwoFactorTokenAsync(string email = null);
|
Task<string> GetTwoFactorTokenAsync(string email = null);
|
||||||
Task SetTwoFactorTokenAsync(string value, string email = null);
|
Task SetTwoFactorTokenAsync(string value, string email = null);
|
||||||
Task<bool> GetScreenCaptureAllowedAsync(string userId = null);
|
|
||||||
Task SetScreenCaptureAllowedAsync(bool value, string userId = null);
|
|
||||||
Task SaveExtensionActiveUserIdToStorageAsync(string userId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
Task<string> GetCodeAsync(string key);
|
Task<string> GetCodeAsync(string key);
|
||||||
int GetTimeInterval(string key);
|
int GetTimeInterval(string key);
|
||||||
|
Task<bool> IsAutoCopyEnabledAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
public static string iOSExtensionBiometricIntegrityKey = "iOSExtensionBiometricIntegrityState";
|
||||||
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
public static string iOSShareExtensionClearCiphersCacheKey = "iOSShareExtensionClearCiphersCache";
|
||||||
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
public static string iOSShareExtensionBiometricIntegrityKey = "iOSShareExtensionBiometricIntegrityState";
|
||||||
public static string iOSExtensionActiveUserIdKey = "iOSExtensionActiveUserId";
|
|
||||||
public static string EventCollectionKey = "eventCollection";
|
public static string EventCollectionKey = "eventCollection";
|
||||||
public static string RememberedEmailKey = "rememberedEmail";
|
public static string RememberedEmailKey = "rememberedEmail";
|
||||||
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
public static string RememberedOrgIdentifierKey = "rememberedOrgIdentifier";
|
||||||
@@ -73,7 +72,6 @@
|
|||||||
public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}";
|
public static string DisableFaviconKey(string userId) => $"disableFavicon_{userId}";
|
||||||
public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}";
|
public static string DefaultUriMatchKey(string userId) => $"defaultUriMatch_{userId}";
|
||||||
public static string ThemeKey(string userId) => $"theme_{userId}";
|
public static string ThemeKey(string userId) => $"theme_{userId}";
|
||||||
public static string AutoDarkThemeKey(string userId) => $"autoDarkTheme_{userId}";
|
|
||||||
public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}";
|
public static string DisableAutoTotpCopyKey(string userId) => $"disableAutoTotpCopy_{userId}";
|
||||||
public static string PreviousPageKey(string userId) => $"previousPage_{userId}";
|
public static string PreviousPageKey(string userId) => $"previousPage_{userId}";
|
||||||
public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}";
|
public static string PasswordRepromptAutofillKey(string userId) => $"passwordRepromptAutofillKey_{userId}";
|
||||||
|
|||||||
@@ -94,13 +94,11 @@ namespace Bit.Core.Models.Domain
|
|||||||
EnvironmentUrls = copy.EnvironmentUrls;
|
EnvironmentUrls = copy.EnvironmentUrls;
|
||||||
VaultTimeout = copy.VaultTimeout;
|
VaultTimeout = copy.VaultTimeout;
|
||||||
VaultTimeoutAction = copy.VaultTimeoutAction;
|
VaultTimeoutAction = copy.VaultTimeoutAction;
|
||||||
ScreenCaptureAllowed = copy.ScreenCaptureAllowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EnvironmentUrlData EnvironmentUrls;
|
public EnvironmentUrlData EnvironmentUrls;
|
||||||
public int? VaultTimeout;
|
public int? VaultTimeout;
|
||||||
public VaultTimeoutAction? VaultTimeoutAction;
|
public VaultTimeoutAction? VaultTimeoutAction;
|
||||||
public bool ScreenCaptureAllowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AccountVolatileData
|
public class AccountVolatileData
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
{
|
{
|
||||||
public class OrganizationUserResetPasswordEnrollmentRequest
|
public class OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
public string MasterPasswordHash { get; set; }
|
|
||||||
public string ResetPasswordKey { get; set; }
|
public string ResetPasswordKey { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Bit.Core.Models.Response
|
|
||||||
{
|
|
||||||
public class SsoPrevalidateResponse
|
|
||||||
{
|
|
||||||
public string Token { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,5 @@ namespace Bit.Core.Models.View
|
|||||||
public string MaskedValue => Value != null ? "••••••••" : null;
|
public string MaskedValue => Value != null ? "••••••••" : null;
|
||||||
public bool NewField { get; set; }
|
public bool NewField { get; set; }
|
||||||
public LinkedIdType? LinkedId { get; set; }
|
public LinkedIdType? LinkedId { get; set; }
|
||||||
public bool BoolValue => bool.TryParse(Value, out var b) && b;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -547,7 +547,7 @@ namespace Bit.Core.Services
|
|||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
|
public async Task<object> PreValidateSso(string identifier)
|
||||||
{
|
{
|
||||||
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
||||||
using (var requestMessage = new HttpRequestMessage())
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
@@ -571,8 +571,7 @@ namespace Bit.Core.Services
|
|||||||
var error = await HandleErrorAsync(response, false, true);
|
var error = await HandleErrorAsync(response, false, true);
|
||||||
throw new ApiException(error);
|
throw new ApiException(error);
|
||||||
}
|
}
|
||||||
var responseJsonString = await response.Content.ReadAsStringAsync();
|
return null;
|
||||||
return JsonConvert.DeserializeObject<SsoPrevalidateResponse>(responseJsonString);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,19 +588,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
requestMessage.Version = new Version(1, 0);
|
requestMessage.Version = new Version(1, 0);
|
||||||
requestMessage.Method = method;
|
requestMessage.Method = method;
|
||||||
|
|
||||||
if (!Uri.IsWellFormedUriString(ApiBaseUrl, UriKind.Absolute))
|
|
||||||
{
|
|
||||||
throw new ApiException(new ErrorResponse
|
|
||||||
{
|
|
||||||
StatusCode = HttpStatusCode.BadGateway,
|
|
||||||
//Note: This message is hardcoded until AppResources.resx gets moved into Core.csproj
|
|
||||||
Message = "One or more URLs saved in the Settings are incorrect. Please revise it and try to log in again."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
requestMessage.RequestUri = new Uri(string.Concat(ApiBaseUrl, path));
|
requestMessage.RequestUri = new Uri(string.Concat(ApiBaseUrl, path));
|
||||||
|
|
||||||
if (body != null)
|
if (body != null)
|
||||||
{
|
{
|
||||||
var bodyType = body.GetType();
|
var bodyType = body.GetType();
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@@ -174,7 +173,7 @@ namespace Bit.Core.Services
|
|||||||
public void LogOut(Action callback)
|
public void LogOut(Action callback)
|
||||||
{
|
{
|
||||||
callback.Invoke();
|
callback.Invoke();
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.LOGGED_OUT);
|
_messagingService.Send("loggedOut");
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TwoFactorProvider> GetSupportedTwoFactorProviders()
|
public List<TwoFactorProvider> GetSupportedTwoFactorProviders()
|
||||||
|
|||||||
@@ -8,58 +8,24 @@ namespace Bit.App.Services
|
|||||||
{
|
{
|
||||||
public class BroadcasterService : IBroadcasterService
|
public class BroadcasterService : IBroadcasterService
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly Dictionary<string, Action<Message>> _subscribers = new Dictionary<string, Action<Message>>();
|
private readonly Dictionary<string, Action<Message>> _subscribers = new Dictionary<string, Action<Message>>();
|
||||||
private object _myLock = new object();
|
private object _myLock = new object();
|
||||||
|
|
||||||
public BroadcasterService(ILogger logger)
|
public void Send(Message message, string id = null)
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Send(Message message)
|
|
||||||
{
|
{
|
||||||
lock (_myLock)
|
lock (_myLock)
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
if (_subscribers.ContainsKey(id))
|
||||||
|
{
|
||||||
|
Task.Run(() => _subscribers[id].Invoke(message));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach (var sub in _subscribers)
|
foreach (var sub in _subscribers)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() => sub.Value.Invoke(message));
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sub.Value(message);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Exception(ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Send(Message message, string id)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(id))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_myLock)
|
|
||||||
{
|
|
||||||
if (_subscribers.TryGetValue(id, out var action))
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
action(message);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Exception(ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +34,14 @@ namespace Bit.App.Services
|
|||||||
{
|
{
|
||||||
lock (_myLock)
|
lock (_myLock)
|
||||||
{
|
{
|
||||||
_subscribers[id] = messageCallback;
|
if (_subscribers.ContainsKey(id))
|
||||||
|
{
|
||||||
|
_subscribers[id] = messageCallback;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_subscribers.Add(id, messageCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,12 +249,6 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<bool> ShouldShowVaultFilterAsync()
|
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||||
{
|
{
|
||||||
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
|
||||||
var singleOrgPolicyApplies = await PolicyAppliesToUser(PolicyType.OnlyOrg);
|
|
||||||
if (personalOwnershipPolicyApplies && singleOrgPolicyApplies)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var organizations = await _organizationService.GetAllAsync();
|
var organizations = await _organizationService.GetAllAsync();
|
||||||
return organizations?.Any() ?? false;
|
return organizations?.Any() ?? false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ namespace Bit.Core.Services
|
|||||||
EnvironmentUrls = environmentUrls,
|
EnvironmentUrls = environmentUrls,
|
||||||
VaultTimeout = vaultTimeout,
|
VaultTimeout = vaultTimeout,
|
||||||
VaultTimeoutAction =
|
VaultTimeoutAction =
|
||||||
vaultTimeoutAction == "logout" ? VaultTimeoutAction.Logout : VaultTimeoutAction.Lock
|
vaultTimeoutAction == "logout" ? VaultTimeoutAction.Logout : VaultTimeoutAction.Lock,
|
||||||
};
|
};
|
||||||
var state = new State { Accounts = new Dictionary<string, Account> { [userId] = account } };
|
var state = new State { Accounts = new Dictionary<string, Account> { [userId] = account } };
|
||||||
state.ActiveUserId = userId;
|
state.ActiveUserId = userId;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
@@ -16,20 +15,16 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly IStorageService _secureStorageService;
|
private readonly IStorageService _secureStorageService;
|
||||||
private readonly IMessagingService _messagingService;
|
|
||||||
|
|
||||||
private State _state;
|
private State _state;
|
||||||
private bool _migrationChecked;
|
private bool _migrationChecked;
|
||||||
|
|
||||||
public List<AccountView> AccountViews { get; set; }
|
public List<AccountView> AccountViews { get; set; }
|
||||||
|
|
||||||
public StateService(IStorageService storageService,
|
public StateService(IStorageService storageService, IStorageService secureStorageService)
|
||||||
IStorageService secureStorageService,
|
|
||||||
IMessagingService messagingService)
|
|
||||||
{
|
{
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
_secureStorageService = secureStorageService;
|
_secureStorageService = secureStorageService;
|
||||||
_messagingService = messagingService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetActiveUserIdAsync()
|
public async Task<string> GetActiveUserIdAsync()
|
||||||
@@ -72,28 +67,6 @@ namespace Bit.Core.Services
|
|||||||
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
|
await SetPreAuthEnvironmentUrlsAsync(await GetEnvironmentUrlsAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CheckExtensionActiveUserAndSwitchIfNeededAsync()
|
|
||||||
{
|
|
||||||
var extensionUserId = await GetExtensionActiveUserIdFromStorageAsync();
|
|
||||||
if (string.IsNullOrEmpty(extensionUserId))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_state?.ActiveUserId == extensionUserId)
|
|
||||||
{
|
|
||||||
// Clear the value in case the user changes the active user from the app
|
|
||||||
// so if that happens and the user sends the app to background and comes back
|
|
||||||
// the user is not changed again.
|
|
||||||
await SaveExtensionActiveUserIdToStorageAsync(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await SetActiveUserAsync(extensionUserId);
|
|
||||||
await SaveExtensionActiveUserIdToStorageAsync(null);
|
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> IsAuthenticatedAsync(string userId = null)
|
public async Task<bool> IsAuthenticatedAsync(string userId = null)
|
||||||
{
|
{
|
||||||
return await GetAccessTokenAsync(userId) != null;
|
return await GetAccessTokenAsync(userId) != null;
|
||||||
@@ -559,27 +532,6 @@ namespace Bit.Core.Services
|
|||||||
await SaveAccountAsync(account, reconciledOptions);
|
await SaveAccountAsync(account, reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> GetScreenCaptureAllowedAsync(string userId = null)
|
|
||||||
{
|
|
||||||
if (CoreHelpers.ForceScreenCaptureEnabled())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await GetAccountAsync(
|
|
||||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
|
|
||||||
))?.Settings?.ScreenCaptureAllowed ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetScreenCaptureAllowedAsync(bool value, string userId = null)
|
|
||||||
{
|
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
|
||||||
await GetDefaultStorageOptionsAsync());
|
|
||||||
var account = await GetAccountAsync(reconciledOptions);
|
|
||||||
account.Settings.ScreenCaptureAllowed = value;
|
|
||||||
await SaveAccountAsync(account, reconciledOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DateTime?> GetLastFileCacheClearAsync()
|
public async Task<DateTime?> GetLastFileCacheClearAsync()
|
||||||
{
|
{
|
||||||
var options = await GetDefaultStorageOptionsAsync();
|
var options = await GetDefaultStorageOptionsAsync();
|
||||||
@@ -946,25 +898,6 @@ namespace Bit.Core.Services
|
|||||||
SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget();
|
SetValueGloballyAsync(Constants.ThemeKey, value, reconciledOptions).FireAndForget();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetAutoDarkThemeAsync(string userId = null)
|
|
||||||
{
|
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
|
||||||
await GetDefaultStorageOptionsAsync());
|
|
||||||
var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId);
|
|
||||||
return await GetValueAsync<string>(key, reconciledOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetAutoDarkThemeAsync(string value, string userId = null)
|
|
||||||
{
|
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
|
||||||
await GetDefaultStorageOptionsAsync());
|
|
||||||
var key = Constants.AutoDarkThemeKey(reconciledOptions.UserId);
|
|
||||||
await SetValueAsync(key, value, reconciledOptions);
|
|
||||||
|
|
||||||
// TODO remove this to restore per-account Theme support
|
|
||||||
SetValueGloballyAsync(Constants.AutoDarkThemeKey, value, reconciledOptions).FireAndForget();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool?> GetAddSitePromptShownAsync(string userId = null)
|
public async Task<bool?> GetAddSitePromptShownAsync(string userId = null)
|
||||||
{
|
{
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
@@ -1455,7 +1388,6 @@ namespace Bit.Core.Services
|
|||||||
await SetPasswordVerifiedAutofillAsync(null, userId);
|
await SetPasswordVerifiedAutofillAsync(null, userId);
|
||||||
await SetSyncOnRefreshAsync(null, userId);
|
await SetSyncOnRefreshAsync(null, userId);
|
||||||
await SetThemeAsync(null, userId);
|
await SetThemeAsync(null, userId);
|
||||||
await SetAutoDarkThemeAsync(null, userId);
|
|
||||||
await SetAddSitePromptShownAsync(null, userId);
|
await SetAddSitePromptShownAsync(null, userId);
|
||||||
await SetPasswordGenerationOptionsAsync(null, userId);
|
await SetPasswordGenerationOptionsAsync(null, userId);
|
||||||
}
|
}
|
||||||
@@ -1465,7 +1397,6 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
await CheckStateAsync();
|
await CheckStateAsync();
|
||||||
var currentTheme = await GetThemeAsync();
|
var currentTheme = await GetThemeAsync();
|
||||||
var currentAutoDarkTheme = await GetAutoDarkThemeAsync();
|
|
||||||
var currentDisableFavicons = await GetDisableFaviconAsync();
|
var currentDisableFavicons = await GetDisableFaviconAsync();
|
||||||
|
|
||||||
account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync();
|
account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync();
|
||||||
@@ -1483,7 +1414,6 @@ namespace Bit.Core.Services
|
|||||||
var existingAccount = state.Accounts[account.Profile.UserId];
|
var existingAccount = state.Accounts[account.Profile.UserId];
|
||||||
account.Settings.VaultTimeout = existingAccount.Settings.VaultTimeout;
|
account.Settings.VaultTimeout = existingAccount.Settings.VaultTimeout;
|
||||||
account.Settings.VaultTimeoutAction = existingAccount.Settings.VaultTimeoutAction;
|
account.Settings.VaultTimeoutAction = existingAccount.Settings.VaultTimeoutAction;
|
||||||
account.Settings.ScreenCaptureAllowed = existingAccount.Settings.ScreenCaptureAllowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New account defaults
|
// New account defaults
|
||||||
@@ -1496,7 +1426,6 @@ namespace Bit.Core.Services
|
|||||||
account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock;
|
account.Settings.VaultTimeoutAction = VaultTimeoutAction.Lock;
|
||||||
}
|
}
|
||||||
await SetThemeAsync(currentTheme, account.Profile.UserId);
|
await SetThemeAsync(currentTheme, account.Profile.UserId);
|
||||||
await SetAutoDarkThemeAsync(currentAutoDarkTheme, account.Profile.UserId);
|
|
||||||
await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId);
|
await SetDisableFaviconAsync(currentDisableFavicons, account.Profile.UserId);
|
||||||
|
|
||||||
state.Accounts[account.Profile.UserId] = account;
|
state.Accounts[account.Profile.UserId] = account;
|
||||||
@@ -1581,16 +1510,6 @@ namespace Bit.Core.Services
|
|||||||
await _storageService.SaveAsync(Constants.StateKey, state);
|
await _storageService.SaveAsync(Constants.StateKey, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> GetExtensionActiveUserIdFromStorageAsync()
|
|
||||||
{
|
|
||||||
return await _storageService.GetAsync<string>(Constants.iOSExtensionActiveUserIdKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SaveExtensionActiveUserIdToStorageAsync(string userId)
|
|
||||||
{
|
|
||||||
await _storageService.SaveAsync(Constants.iOSExtensionActiveUserIdKey, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CheckStateAsync()
|
private async Task CheckStateAsync()
|
||||||
{
|
{
|
||||||
if (!_migrationChecked)
|
if (!_migrationChecked)
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
private const string SteamChars = "23456789BCDFGHJKMNPQRTVWXY";
|
private const string SteamChars = "23456789BCDFGHJKMNPQRTVWXY";
|
||||||
|
|
||||||
|
private readonly IStateService _stateService;
|
||||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
|
|
||||||
public TotpService(
|
public TotpService(
|
||||||
|
IStateService stateService,
|
||||||
ICryptoFunctionService cryptoFunctionService)
|
ICryptoFunctionService cryptoFunctionService)
|
||||||
{
|
{
|
||||||
|
_stateService = stateService;
|
||||||
_cryptoFunctionService = cryptoFunctionService;
|
_cryptoFunctionService = cryptoFunctionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,5 +132,11 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
return period;
|
return period;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsAutoCopyEnabledAsync()
|
||||||
|
{
|
||||||
|
var disabled = await _stateService.GetDisableAutoTotpCopyAsync();
|
||||||
|
return !disabled.GetValueOrDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace Bit.Core.Utilities
|
|
||||||
{
|
|
||||||
public static class AccountsManagerMessageCommands
|
|
||||||
{
|
|
||||||
public const string LOCKED = "locked";
|
|
||||||
public const string LOCK_VAULT = "lockVault";
|
|
||||||
public const string LOGOUT = "logout";
|
|
||||||
public const string LOGGED_OUT = "loggedOut";
|
|
||||||
public const string ADD_ACCOUNT = "addAccount";
|
|
||||||
public const string ACCOUNT_ADDED = "accountAdded";
|
|
||||||
public const string SWITCHED_ACCOUNT = "switchedAccount";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,25 +34,6 @@ namespace Bit.Core.Utilities
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns whether to force enabling the screen capture.
|
|
||||||
/// On Debug it will allow screen capture by default but this method
|
|
||||||
/// makes it easier to test the change on enabling/disabling the feature
|
|
||||||
/// on debug.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// To test enabling/disabling in DEBUG, just return <c>false</c> in the #if condition
|
|
||||||
/// and that's it.
|
|
||||||
/// </remarks>
|
|
||||||
public static bool ForceScreenCaptureEnabled()
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetHostname(string uriString)
|
public static string GetHostname(string uriString)
|
||||||
{
|
{
|
||||||
var uri = GetUri(uriString);
|
var uri = GetUri(uriString);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@@ -10,7 +8,7 @@ namespace Bit.Core.Utilities
|
|||||||
{
|
{
|
||||||
public static class ServiceContainer
|
public static class ServiceContainer
|
||||||
{
|
{
|
||||||
public static ConcurrentDictionary<string, object> RegisteredServices { get; set; } = new ConcurrentDictionary<string, object>();
|
public static Dictionary<string, object> RegisteredServices { get; set; } = new Dictionary<string, object>();
|
||||||
public static bool Inited { get; set; }
|
public static bool Inited { get; set; }
|
||||||
|
|
||||||
public static void Init(string customUserAgent = null, string clearCipherCacheKey = null,
|
public static void Init(string customUserAgent = null, string clearCipherCacheKey = null,
|
||||||
@@ -74,7 +72,7 @@ namespace Bit.Core.Utilities
|
|||||||
});
|
});
|
||||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService,
|
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService,
|
||||||
cryptoFunctionService, policyService);
|
cryptoFunctionService, policyService);
|
||||||
var totpService = new TotpService(cryptoFunctionService);
|
var totpService = new TotpService(stateService, cryptoFunctionService);
|
||||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
||||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||||
keyConnectorService);
|
keyConnectorService);
|
||||||
@@ -111,17 +109,18 @@ namespace Bit.Core.Utilities
|
|||||||
|
|
||||||
public static void Register<T>(string serviceName, T obj)
|
public static void Register<T>(string serviceName, T obj)
|
||||||
{
|
{
|
||||||
if (!RegisteredServices.TryAdd(serviceName, obj))
|
if (RegisteredServices.ContainsKey(serviceName))
|
||||||
{
|
{
|
||||||
throw new Exception($"Service {serviceName} has already been registered.");
|
throw new Exception($"Service {serviceName} has already been registered.");
|
||||||
}
|
}
|
||||||
|
RegisteredServices.Add(serviceName, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T Resolve<T>(string serviceName, bool dontThrow = false)
|
public static T Resolve<T>(string serviceName, bool dontThrow = false)
|
||||||
{
|
{
|
||||||
if (RegisteredServices.TryGetValue(serviceName, out var service))
|
if (RegisteredServices.ContainsKey(serviceName))
|
||||||
{
|
{
|
||||||
return (T)service;
|
return (T)RegisteredServices[serviceName];
|
||||||
}
|
}
|
||||||
if (dontThrow)
|
if (dontThrow)
|
||||||
{
|
{
|
||||||
@@ -130,59 +129,6 @@ namespace Bit.Core.Utilities
|
|||||||
throw new Exception($"Service {serviceName} is not registered.");
|
throw new Exception($"Service {serviceName} is not registered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Register<T>(T obj)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
Register(typeof(T), obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Register(Type type, object obj)
|
|
||||||
{
|
|
||||||
var serviceName = GetServiceRegistrationName(type);
|
|
||||||
if (!RegisteredServices.TryAdd(serviceName, obj))
|
|
||||||
{
|
|
||||||
throw new Exception($"Service {serviceName} has already been registered.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T Resolve<T>()
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
return (T)Resolve(typeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static object Resolve(Type type)
|
|
||||||
{
|
|
||||||
var serviceName = GetServiceRegistrationName(type);
|
|
||||||
if (RegisteredServices.TryGetValue(serviceName, out var service))
|
|
||||||
{
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
throw new Exception($"Service {serviceName} is not registered.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryResolve<T>(out T service)
|
|
||||||
where T : class
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var toReturn = TryResolve(typeof(T), out var serviceObj);
|
|
||||||
service = (T)serviceObj;
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
service = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryResolve(Type type, out object service)
|
|
||||||
{
|
|
||||||
var serviceName = GetServiceRegistrationName(type);
|
|
||||||
return RegisteredServices.TryGetValue(serviceName, out service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Reset()
|
public static void Reset()
|
||||||
{
|
{
|
||||||
foreach (var service in RegisteredServices)
|
foreach (var service in RegisteredServices)
|
||||||
@@ -194,33 +140,7 @@ namespace Bit.Core.Utilities
|
|||||||
}
|
}
|
||||||
Inited = false;
|
Inited = false;
|
||||||
RegisteredServices.Clear();
|
RegisteredServices.Clear();
|
||||||
RegisteredServices = new ConcurrentDictionary<string, object>();
|
RegisteredServices = new Dictionary<string, object>();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the service registration name
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">Type of the service</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// In order to work with already register/resolve we need to maintain the naming convention
|
|
||||||
/// of camelCase without the first "I" on the services interfaces
|
|
||||||
/// e.g. "ITokenService" -> "tokenService"
|
|
||||||
/// </remarks>
|
|
||||||
static string GetServiceRegistrationName(Type type)
|
|
||||||
{
|
|
||||||
var typeName = type.Name;
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
|
|
||||||
var indexToLowerCase = 0;
|
|
||||||
if (typeName[0] == 'I' && char.IsUpper(typeName[1]))
|
|
||||||
{
|
|
||||||
// if it's an interface then we ignore the first char
|
|
||||||
// and lower case the 2nd one (index 1)
|
|
||||||
indexToLowerCase = 1;
|
|
||||||
}
|
|
||||||
sb.Append(char.ToLower(typeName[indexToLowerCase]));
|
|
||||||
sb.Append(typeName.Substring(++indexToLowerCase));
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,10 @@ using Bit.App.Utilities;
|
|||||||
using Bit.App.Utilities.AccountManagement;
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.iOS.Autofill.Models;
|
using Bit.iOS.Autofill.Models;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using Bit.iOS.Core.Views;
|
using Bit.iOS.Core.Views;
|
||||||
using CoreFoundation;
|
|
||||||
using CoreNFC;
|
using CoreNFC;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
@@ -38,128 +36,88 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
public override void ViewDidLoad()
|
public override void ViewDidLoad()
|
||||||
{
|
{
|
||||||
try
|
InitApp();
|
||||||
|
base.ViewDidLoad();
|
||||||
|
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
||||||
|
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
||||||
|
_context = new Context
|
||||||
{
|
{
|
||||||
InitApp();
|
ExtContext = ExtensionContext
|
||||||
base.ViewDidLoad();
|
};
|
||||||
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
|
||||||
View.BackgroundColor = ThemeHelpers.SplashBackgroundColor;
|
|
||||||
_context = new Context
|
|
||||||
{
|
|
||||||
ExtContext = ExtensionContext
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
|
public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
|
||||||
{
|
{
|
||||||
try
|
InitAppIfNeeded();
|
||||||
|
_context.ServiceIdentifiers = serviceIdentifiers;
|
||||||
|
if (serviceIdentifiers.Length > 0)
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
var uri = serviceIdentifiers[0].Identifier;
|
||||||
_context.ServiceIdentifiers = serviceIdentifiers;
|
if (serviceIdentifiers[0].Type == ASCredentialServiceIdentifierType.Domain)
|
||||||
if (serviceIdentifiers.Length > 0)
|
|
||||||
{
|
{
|
||||||
var uri = serviceIdentifiers[0].Identifier;
|
uri = string.Concat("https://", uri);
|
||||||
if (serviceIdentifiers[0].Type == ASCredentialServiceIdentifierType.Domain)
|
|
||||||
{
|
|
||||||
uri = string.Concat("https://", uri);
|
|
||||||
}
|
|
||||||
_context.UrlString = uri;
|
|
||||||
}
|
}
|
||||||
if (!await IsAuthed())
|
_context.UrlString = uri;
|
||||||
|
}
|
||||||
|
if (!await IsAuthed())
|
||||||
|
{
|
||||||
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
|
}
|
||||||
|
else if (await IsLocked())
|
||||||
|
{
|
||||||
|
PerformSegue("lockPasswordSegue", this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
||||||
{
|
{
|
||||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
PerformSegue("loginSearchSegue", this);
|
||||||
}
|
|
||||||
else if (await IsLocked())
|
|
||||||
{
|
|
||||||
PerformSegue("lockPasswordSegue", this);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
PerformSegue("loginListSegue", this);
|
||||||
{
|
|
||||||
PerformSegue("loginSearchSegue", this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PerformSegue("loginListSegue", this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
||||||
{
|
{
|
||||||
try
|
InitAppIfNeeded();
|
||||||
|
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
|
||||||
|
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
|
||||||
|
if (!await IsAuthed() || await IsLocked())
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
|
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
||||||
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
|
ExtensionContext.CancelRequest(err);
|
||||||
if (!await IsAuthed() || await IsLocked())
|
return;
|
||||||
{
|
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
|
||||||
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
|
||||||
ExtensionContext.CancelRequest(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_context.CredentialIdentity = credentialIdentity;
|
|
||||||
await ProvideCredentialAsync(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
_context.CredentialIdentity = credentialIdentity;
|
||||||
|
await ProvideCredentialAsync(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
|
public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
|
||||||
{
|
{
|
||||||
try
|
InitAppIfNeeded();
|
||||||
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
if (!await IsAuthed())
|
return;
|
||||||
{
|
|
||||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_context.CredentialIdentity = credentialIdentity;
|
|
||||||
await CheckLockAsync(async () => await ProvideCredentialAsync());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
_context.CredentialIdentity = credentialIdentity;
|
||||||
|
CheckLock(async () => await ProvideCredentialAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void PrepareInterfaceForExtensionConfiguration()
|
public override async void PrepareInterfaceForExtensionConfiguration()
|
||||||
{
|
{
|
||||||
try
|
InitAppIfNeeded();
|
||||||
|
_context.Configuring = true;
|
||||||
|
if (!await IsAuthed())
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||||
_context.Configuring = true;
|
return;
|
||||||
if (!await IsAuthed())
|
|
||||||
{
|
|
||||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await CheckLockAsync(() => PerformSegue("setupSegue", this));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
CheckLock(() => PerformSegue("setupSegue", this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CompleteRequest(string id = null, string username = null,
|
public void CompleteRequest(string id = null, string username = null,
|
||||||
@@ -201,43 +159,34 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
|
||||||
{
|
{
|
||||||
try
|
if (segue.DestinationViewController is UINavigationController navController)
|
||||||
{
|
{
|
||||||
if (segue.DestinationViewController is UINavigationController navController)
|
if (navController.TopViewController is LoginListViewController listLoginController)
|
||||||
{
|
{
|
||||||
if (navController.TopViewController is LoginListViewController listLoginController)
|
listLoginController.Context = _context;
|
||||||
{
|
listLoginController.CPViewController = this;
|
||||||
listLoginController.Context = _context;
|
segue.DestinationViewController.PresentationController.Delegate =
|
||||||
listLoginController.CPViewController = this;
|
new CustomPresentationControllerDelegate(listLoginController.DismissModalAction);
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
}
|
||||||
new CustomPresentationControllerDelegate(listLoginController.DismissModalAction);
|
else if (navController.TopViewController is LoginSearchViewController listSearchController)
|
||||||
}
|
{
|
||||||
else if (navController.TopViewController is LoginSearchViewController listSearchController)
|
listSearchController.Context = _context;
|
||||||
{
|
listSearchController.CPViewController = this;
|
||||||
listSearchController.Context = _context;
|
segue.DestinationViewController.PresentationController.Delegate =
|
||||||
listSearchController.CPViewController = this;
|
new CustomPresentationControllerDelegate(listSearchController.DismissModalAction);
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
}
|
||||||
new CustomPresentationControllerDelegate(listSearchController.DismissModalAction);
|
else if (navController.TopViewController is LockPasswordViewController passwordViewController)
|
||||||
}
|
{
|
||||||
else if (navController.TopViewController is LockPasswordViewController passwordViewController)
|
passwordViewController.CPViewController = this;
|
||||||
{
|
segue.DestinationViewController.PresentationController.Delegate =
|
||||||
passwordViewController.CPViewController = this;
|
new CustomPresentationControllerDelegate(passwordViewController.DismissModalAction);
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
}
|
||||||
new CustomPresentationControllerDelegate(passwordViewController.DismissModalAction);
|
else if (navController.TopViewController is SetupViewController setupViewController)
|
||||||
}
|
{
|
||||||
else if (navController.TopViewController is SetupViewController setupViewController)
|
setupViewController.CPViewController = this;
|
||||||
{
|
segue.DestinationViewController.PresentationController.Delegate =
|
||||||
setupViewController.CPViewController = this;
|
new CustomPresentationControllerDelegate(setupViewController.DismissModalAction);
|
||||||
segue.DestinationViewController.PresentationController.Delegate =
|
|
||||||
new CustomPresentationControllerDelegate(setupViewController.DismissModalAction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,109 +194,93 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
DismissViewController(false, async () =>
|
DismissViewController(false, async () =>
|
||||||
{
|
{
|
||||||
try
|
if (_context.CredentialIdentity != null)
|
||||||
{
|
{
|
||||||
if (_context.CredentialIdentity != null)
|
await ProvideCredentialAsync();
|
||||||
{
|
return;
|
||||||
await ProvideCredentialAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_context.Configuring)
|
|
||||||
{
|
|
||||||
PerformSegue("setupSegue", this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
|
||||||
{
|
|
||||||
PerformSegue("loginSearchSegue", this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PerformSegue("loginListSegue", this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
if (_context.Configuring)
|
||||||
{
|
{
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
PerformSegue("setupSegue", this);
|
||||||
throw;
|
return;
|
||||||
|
}
|
||||||
|
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
||||||
|
{
|
||||||
|
PerformSegue("loginSearchSegue", this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PerformSegue("loginListSegue", this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProvideCredentialAsync(bool userInteraction = true)
|
private async Task ProvideCredentialAsync(bool userInteraction = true)
|
||||||
{
|
{
|
||||||
try
|
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService", true);
|
||||||
|
Bit.Core.Models.Domain.Cipher cipher = null;
|
||||||
|
var cancel = cipherService == null || _context.CredentialIdentity?.RecordIdentifier == null;
|
||||||
|
if (!cancel)
|
||||||
{
|
{
|
||||||
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService", true);
|
cipher = await cipherService.GetAsync(_context.CredentialIdentity.RecordIdentifier);
|
||||||
Bit.Core.Models.Domain.Cipher cipher = null;
|
cancel = cipher == null || cipher.Type != Bit.Core.Enums.CipherType.Login || cipher.Login == null;
|
||||||
var cancel = cipherService == null || _context.CredentialIdentity?.RecordIdentifier == null;
|
}
|
||||||
if (!cancel)
|
if (cancel)
|
||||||
{
|
{
|
||||||
cipher = await cipherService.GetAsync(_context.CredentialIdentity.RecordIdentifier);
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
cancel = cipher == null || cipher.Type != Bit.Core.Enums.CipherType.Login || cipher.Login == null;
|
Convert.ToInt32(ASExtensionErrorCode.CredentialIdentityNotFound), null);
|
||||||
}
|
ExtensionContext?.CancelRequest(err);
|
||||||
if (cancel)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var decCipher = await cipher.DecryptAsync();
|
||||||
|
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
|
||||||
|
{
|
||||||
|
// Prompt for password using either the lock screen or dialog unless
|
||||||
|
// already verified the password.
|
||||||
|
if (!userInteraction)
|
||||||
{
|
{
|
||||||
|
await _stateService.Value.SetPasswordRepromptAutofillAsync(true);
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
Convert.ToInt32(ASExtensionErrorCode.CredentialIdentityNotFound), null);
|
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
||||||
ExtensionContext?.CancelRequest(err);
|
ExtensionContext?.CancelRequest(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (!await _stateService.Value.GetPasswordVerifiedAutofillAsync())
|
||||||
var decCipher = await cipher.DecryptAsync();
|
|
||||||
if (decCipher.Reprompt != Bit.Core.Enums.CipherRepromptType.None)
|
|
||||||
{
|
{
|
||||||
// Prompt for password using either the lock screen or dialog unless
|
// Add a timeout to resolve keyboard not always showing up.
|
||||||
// already verified the password.
|
await Task.Delay(250);
|
||||||
if (!userInteraction)
|
var passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
|
if (!await passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
{
|
{
|
||||||
await _stateService.Value.SetPasswordRepromptAutofillAsync(true);
|
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||||
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
|
||||||
ExtensionContext?.CancelRequest(err);
|
ExtensionContext?.CancelRequest(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (!await _stateService.Value.GetPasswordVerifiedAutofillAsync())
|
|
||||||
{
|
|
||||||
// Add a timeout to resolve keyboard not always showing up.
|
|
||||||
await Task.Delay(250);
|
|
||||||
var passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
|
||||||
if (!await passwordRepromptService.ShowPasswordPromptAsync())
|
|
||||||
{
|
|
||||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
|
||||||
Convert.ToInt32(ASExtensionErrorCode.UserCanceled), null);
|
|
||||||
ExtensionContext?.CancelRequest(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
string totpCode = null;
|
|
||||||
var disableTotpCopy = await _stateService.Value.GetDisableAutoTotpCopyAsync();
|
|
||||||
if (!disableTotpCopy.GetValueOrDefault(false))
|
|
||||||
{
|
|
||||||
var canAccessPremiumAsync = await _stateService.Value.CanAccessPremiumAsync();
|
|
||||||
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
|
|
||||||
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
|
|
||||||
{
|
|
||||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
|
||||||
totpCode = await totpService.GetCodeAsync(decCipher.Login.Totp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompleteRequest(decCipher.Id, decCipher.Login.Username, decCipher.Login.Password, totpCode);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
string totpCode = null;
|
||||||
|
var disableTotpCopy = await _stateService.Value.GetDisableAutoTotpCopyAsync();
|
||||||
|
if (!disableTotpCopy.GetValueOrDefault(false))
|
||||||
{
|
{
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
var canAccessPremiumAsync = await _stateService.Value.CanAccessPremiumAsync();
|
||||||
throw;
|
if (!string.IsNullOrWhiteSpace(decCipher.Login.Totp) &&
|
||||||
|
(canAccessPremiumAsync || cipher.OrganizationUseTotp))
|
||||||
|
{
|
||||||
|
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||||
|
totpCode = await totpService.GetCodeAsync(decCipher.Login.Totp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CompleteRequest(decCipher.Id, decCipher.Login.Username, decCipher.Login.Password, totpCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckLockAsync(Action notLockedAction)
|
private async void CheckLock(Action notLockedAction)
|
||||||
{
|
{
|
||||||
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
||||||
{
|
{
|
||||||
DispatchQueue.MainQueue.DispatchAsync(() => PerformSegue("lockPasswordSegue", this));
|
PerformSegue("lockPasswordSegue", this);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -370,21 +303,15 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
try
|
if (await IsAuthed())
|
||||||
{
|
{
|
||||||
if (await IsAuthed())
|
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
||||||
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
if (deviceActionService.SystemMajorVersion() >= 12)
|
||||||
{
|
{
|
||||||
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
|
||||||
{
|
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.8.0</string>
|
<string>2022.05.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Models;
|
|
||||||
using Bit.App.Pages;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models.Domain;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.iOS.Core.Utilities;
|
|
||||||
using Bit.iOS.Core.Views;
|
|
||||||
using Foundation;
|
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
using Foundation;
|
||||||
|
using Bit.iOS.Core.Views;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Models;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
using Bit.Core;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Controllers
|
namespace Bit.iOS.Core.Controllers
|
||||||
{
|
{
|
||||||
@@ -28,7 +28,6 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
private IPlatformUtilsService _platformUtilsService;
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
private IBiometricService _biometricService;
|
private IBiometricService _biometricService;
|
||||||
private IKeyConnectorService _keyConnectorService;
|
private IKeyConnectorService _keyConnectorService;
|
||||||
private IAccountsManager _accountManager;
|
|
||||||
private bool _isPinProtected;
|
private bool _isPinProtected;
|
||||||
private bool _isPinProtectedWithKey;
|
private bool _isPinProtectedWithKey;
|
||||||
private bool _pinLock;
|
private bool _pinLock;
|
||||||
@@ -40,10 +39,6 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
protected bool autofillExtension = false;
|
protected bool autofillExtension = false;
|
||||||
|
|
||||||
public BaseLockPasswordViewController()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseLockPasswordViewController(IntPtr handle)
|
public BaseLockPasswordViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{ }
|
{ }
|
||||||
@@ -85,7 +80,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract UITableView TableView { get; }
|
public abstract UITableView TableView { get; }
|
||||||
|
|
||||||
public override async void ViewDidLoad()
|
public override async void ViewDidLoad()
|
||||||
{
|
{
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
@@ -96,7 +91,6 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
_accountManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
|
||||||
|
|
||||||
// We re-use the lock screen for autofill extension to verify master password
|
// We re-use the lock screen for autofill extension to verify master password
|
||||||
// when trying to access protected items.
|
// when trying to access protected items.
|
||||||
@@ -174,12 +168,13 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
TableView.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
TableView.SeparatorColor = ThemeHelpers.SeparatorColor;
|
||||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
|
||||||
TableView.EstimatedRowHeight = 70;
|
|
||||||
TableView.Source = new TableSource(this);
|
|
||||||
TableView.AllowsSelection = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||||
|
TableView.EstimatedRowHeight = 70;
|
||||||
|
TableView.Source = new TableSource(this);
|
||||||
|
TableView.AllowsSelection = true;
|
||||||
|
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
if (_biometricLock)
|
if (_biometricLock)
|
||||||
@@ -196,7 +191,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ViewDidAppear(bool animated)
|
public override async void ViewDidAppear(bool animated)
|
||||||
{
|
{
|
||||||
base.ViewDidAppear(animated);
|
base.ViewDidAppear(animated);
|
||||||
|
|
||||||
@@ -267,7 +262,13 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
if (failed)
|
if (failed)
|
||||||
{
|
{
|
||||||
await HandleFailedCredentialsAsync();
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
|
if (invalidUnlockAttempts >= 5)
|
||||||
|
{
|
||||||
|
await LogOutAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InvalidValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -302,22 +303,17 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await HandleFailedCredentialsAsync();
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
|
if (invalidUnlockAttempts >= 5)
|
||||||
|
{
|
||||||
|
await LogOutAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InvalidValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleFailedCredentialsAsync()
|
|
||||||
{
|
|
||||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
|
||||||
if (invalidUnlockAttempts >= 5)
|
|
||||||
{
|
|
||||||
await _accountManager.LogOutAsync(await _stateService.GetActiveUserIdAsync(), false, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
InvalidValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
{
|
{
|
||||||
if (!_biometricLock || !_biometricIntegrityValid)
|
if (!_biometricLock || !_biometricIntegrityValid)
|
||||||
@@ -396,43 +392,38 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
PresentViewController(alert, true, null);
|
PresentViewController(alert, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
private async Task LogOutAsync()
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync());
|
||||||
|
var authService = ServiceContainer.Resolve<IAuthService>("authService");
|
||||||
MasterPasswordCell?.Dispose();
|
authService.LogOut(() =>
|
||||||
MasterPasswordCell = null;
|
{
|
||||||
|
Cancel?.Invoke();
|
||||||
TableView?.Dispose();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TableSource : ExtendedUITableViewSource
|
public class TableSource : ExtendedUITableViewSource
|
||||||
{
|
{
|
||||||
private readonly WeakReference<BaseLockPasswordViewController> _controller;
|
private readonly BaseLockPasswordViewController _controller;
|
||||||
|
|
||||||
public TableSource(BaseLockPasswordViewController controller)
|
public TableSource(BaseLockPasswordViewController controller)
|
||||||
{
|
{
|
||||||
_controller = new WeakReference<BaseLockPasswordViewController>(controller);
|
_controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
if (!_controller.TryGetTarget(out var controller))
|
|
||||||
{
|
|
||||||
return new ExtendedUITableViewCell();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexPath.Section == 0)
|
if (indexPath.Section == 0)
|
||||||
{
|
{
|
||||||
if (indexPath.Row == 0)
|
if (indexPath.Row == 0)
|
||||||
{
|
{
|
||||||
if (controller._biometricUnlockOnly)
|
if (_controller._biometricUnlockOnly)
|
||||||
{
|
{
|
||||||
return controller.BiometricCell;
|
return _controller.BiometricCell;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return controller.MasterPasswordCell;
|
return _controller.MasterPasswordCell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,7 +431,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
if (indexPath.Row == 0)
|
if (indexPath.Row == 0)
|
||||||
{
|
{
|
||||||
if (controller._passwordReprompt)
|
if (_controller._passwordReprompt)
|
||||||
{
|
{
|
||||||
var cell = new ExtendedUITableViewCell();
|
var cell = new ExtendedUITableViewCell();
|
||||||
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
cell.TextLabel.TextColor = ThemeHelpers.DangerColor;
|
||||||
@@ -450,9 +441,9 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
|
cell.TextLabel.Text = AppResources.PasswordConfirmationDesc;
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
else if (!controller._biometricUnlockOnly)
|
else if (!_controller._biometricUnlockOnly)
|
||||||
{
|
{
|
||||||
return controller.BiometricCell;
|
return _controller.BiometricCell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,13 +457,8 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public override nint NumberOfSections(UITableView tableView)
|
public override nint NumberOfSections(UITableView tableView)
|
||||||
{
|
{
|
||||||
if (!_controller.TryGetTarget(out var controller))
|
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
|
||||||
{
|
_controller._passwordReprompt
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (!controller._biometricUnlockOnly && controller._biometricLock) ||
|
|
||||||
controller._passwordReprompt
|
|
||||||
? 2
|
? 2
|
||||||
: 1;
|
: 1;
|
||||||
}
|
}
|
||||||
@@ -498,18 +484,13 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
if (!_controller.TryGetTarget(out var controller))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tableView.DeselectRow(indexPath, true);
|
tableView.DeselectRow(indexPath, true);
|
||||||
tableView.EndEditing(true);
|
tableView.EndEditing(true);
|
||||||
if (indexPath.Row == 0 &&
|
if (indexPath.Row == 0 &&
|
||||||
((controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
((_controller._biometricUnlockOnly && indexPath.Section == 0) ||
|
||||||
indexPath.Section == 1))
|
indexPath.Section == 1))
|
||||||
{
|
{
|
||||||
var task = controller.PromptBiometricAsync();
|
var task = _controller.PromptBiometricAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var cell = tableView.CellAt(indexPath);
|
var cell = tableView.CellAt(indexPath);
|
||||||
|
|||||||
@@ -7,11 +7,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
public class ExtendedUIViewController : UIViewController
|
public class ExtendedUIViewController : UIViewController
|
||||||
{
|
{
|
||||||
public Action DismissModalAction { get; set; }
|
public Action DismissModalAction { get; set; }
|
||||||
|
|
||||||
public ExtendedUIViewController()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExtendedUIViewController(IntPtr handle)
|
public ExtendedUIViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
{
|
{
|
||||||
@@ -32,28 +28,16 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
View.BackgroundColor = ThemeHelpers.BackgroundColor;
|
View.BackgroundColor = ThemeHelpers.BackgroundColor;
|
||||||
}
|
}
|
||||||
UpdateNavigationBarTheme();
|
if (NavigationController?.NavigationBar != null)
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void UpdateNavigationBarTheme()
|
|
||||||
{
|
|
||||||
UpdateNavigationBarTheme(NavigationController?.NavigationBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void UpdateNavigationBarTheme(UINavigationBar navBar)
|
|
||||||
{
|
|
||||||
if (navBar is null)
|
|
||||||
{
|
{
|
||||||
return;
|
NavigationController.NavigationBar.BarTintColor = ThemeHelpers.NavBarBackgroundColor;
|
||||||
|
NavigationController.NavigationBar.BackgroundColor = ThemeHelpers.NavBarBackgroundColor;
|
||||||
|
NavigationController.NavigationBar.TintColor = ThemeHelpers.NavBarTextColor;
|
||||||
|
NavigationController.NavigationBar.TitleTextAttributes = new UIStringAttributes
|
||||||
|
{
|
||||||
|
ForegroundColor = ThemeHelpers.NavBarTextColor
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
navBar.BarTintColor = ThemeHelpers.NavBarBackgroundColor;
|
|
||||||
navBar.BackgroundColor = ThemeHelpers.NavBarBackgroundColor;
|
|
||||||
navBar.TintColor = ThemeHelpers.NavBarTextColor;
|
|
||||||
navBar.TitleTextAttributes = new UIStringAttributes
|
|
||||||
{
|
|
||||||
ForegroundColor = ThemeHelpers.NavBarTextColor
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ViewDidAppear(bool animated)
|
public override async void ViewDidAppear(bool animated)
|
||||||
{
|
{
|
||||||
base.ViewDidAppear(animated);
|
base.ViewDidAppear(animated);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
namespace Bit.iOS.Core.Renderers.CollectionView
|
|
||||||
{
|
|
||||||
public class CollectionException : Exception
|
|
||||||
{
|
|
||||||
public CollectionException(string message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public CollectionException(string message, Exception innerEx)
|
|
||||||
: base(message, innerEx)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using UIKit;
|
|
||||||
using Xamarin.Forms.Platform.iOS;
|
using Xamarin.Forms.Platform.iOS;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Renderers.CollectionView
|
namespace Bit.iOS.Core.Renderers.CollectionView
|
||||||
@@ -15,11 +13,6 @@ namespace Bit.iOS.Core.Renderers.CollectionView
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UICollectionViewDelegateFlowLayout CreateDelegator()
|
|
||||||
{
|
|
||||||
return new ExtendedGroupableItemsViewDelegator<TItemsView, ExtendedGroupableItemsViewController<TItemsView>>(ItemsViewLayout, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateTemplatedCell(TemplatedCell cell, NSIndexPath indexPath)
|
protected override void UpdateTemplatedCell(TemplatedCell cell, NSIndexPath indexPath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -28,17 +21,7 @@ namespace Bit.iOS.Core.Renderers.CollectionView
|
|||||||
}
|
}
|
||||||
catch (Exception ex) when (ItemsView?.ExtraDataForLogging != null)
|
catch (Exception ex) when (ItemsView?.ExtraDataForLogging != null)
|
||||||
{
|
{
|
||||||
var colEx = new CollectionException("Error in ExtendedCollectionView -> ExtendedGroupableItemsViewController, extra data: " + ItemsView.ExtraDataForLogging, ex);
|
throw new Exception("Error in ExtendedCollectionView, extra data: " + ItemsView.ExtraDataForLogging, ex);
|
||||||
try
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(colEx);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Do nothing in here, this is temporary to get more info about the crash, if the logger fails, we want to get the info
|
|
||||||
// by crashing with the original exception and not the logger one
|
|
||||||
}
|
|
||||||
throw colEx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using CoreGraphics;
|
|
||||||
using Foundation;
|
|
||||||
using UIKit;
|
|
||||||
using Xamarin.Forms.Platform.iOS;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Renderers.CollectionView
|
|
||||||
{
|
|
||||||
public class ExtendedGroupableItemsViewDelegator<TItemsView, TViewController> : GroupableItemsViewDelegator<TItemsView, TViewController>
|
|
||||||
where TItemsView : ExtendedCollectionView
|
|
||||||
where TViewController : GroupableItemsViewController<TItemsView>
|
|
||||||
{
|
|
||||||
public ExtendedGroupableItemsViewDelegator(ItemsViewLayout itemsViewLayout, TViewController itemsViewController)
|
|
||||||
: base(itemsViewLayout, itemsViewController)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override CGSize GetSizeForItem(UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
|
|
||||||
{
|
|
||||||
// Added this to get extra information on a crash when getting the size for an item.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return base.GetSizeForItem(collectionView, layout, indexPath);
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (ViewController?.ItemsView?.ExtraDataForLogging != null)
|
|
||||||
{
|
|
||||||
var colEx = new CollectionException("Error in ExtendedCollectionView -> ExtendedGroupableItemsViewDelegator, extra data: " + ViewController.ItemsView.ExtraDataForLogging, ex);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(colEx);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Do nothing in here, this is temporary to get more info about the crash, if the logger fails, we want to get the info
|
|
||||||
// by crashing with the original exception and not the logger one
|
|
||||||
}
|
|
||||||
throw colEx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ namespace Bit.iOS.Core.Renderers
|
|||||||
public CustomTabbedRenderer()
|
public CustomTabbedRenderer()
|
||||||
{
|
{
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_broadcasterService.Subscribe(nameof(CustomTabbedRenderer), (message) =>
|
_broadcasterService.Subscribe(nameof(CustomTabbedRenderer), async (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "updatedTheme")
|
if (message.Command == "updatedTheme")
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user