1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-16 00:03:22 +00:00

Compare commits

..

53 Commits

Author SHA1 Message Date
github-actions[bot]
ececcb7bd7 Bump version to 2.17.0 (#1858)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit a3a508eb83)
2022-03-22 10:50:41 -06:00
Matt Portune
81cccf22db Remove verbose state & value storage debug logging (#1857) 2022-03-21 16:46:18 -04:00
Federico Maccaroni
4e36bc83ae Added null checks for iOS crash OnActivated on KeyWindow 2022-03-21 12:36:21 -03:00
github-actions[bot]
9db70119b9 Bumped version to 2.16.5 (#1854)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit fdcb2d76c9)
2022-03-18 13:22:15 -07:00
Federico Maccaroni
3d485f6fb5 Fixed flickering on iOS while loading collections for the collection crash hotfix 2022-03-18 15:49:48 -03:00
Matt Portune
372c875cb2 Misc fixes for account switching (#1849)
* Misc fixes for account switching

* use unique bio integrity key in ShareExtension
2022-03-17 17:57:31 -04:00
Federico Maccaroni
2e60787128 Fix iOS 15.4 crash from empty list to adding an item by awaiting after every header add; also added that on Settings just in case there is another crash scenario 2022-03-17 17:35:42 -03:00
github-actions[bot]
d283586d96 Bumped version to 2.16.4 (#1846)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit c47aad0412)
2022-03-15 09:29:41 -07:00
Federico Maccaroni
3a2a1b527f Removed grouping from Settings to fix a crash on iOS 15.4 2022-03-15 11:31:33 -03:00
Federico Maccaroni
d908d5e64d Fix #1745 crash on scroll of grouped collection view on iOS 15.4 beta 2022-03-14 13:54:37 -03:00
Matt Portune
8c59552d41 fixes for font cutoff on samsung devices (#1838) 2022-03-10 13:56:33 -05:00
Matt Portune
77c4c423e0 fixed issues with logging out inactive accounts (#1836) 2022-03-10 09:03:06 -05:00
Joseph Flinn
47241fd5a9 Update hotfix release branch name to hotfix-rc (#1834)
(cherry picked from commit bdd0ea007b)
2022-03-09 12:54:20 -08:00
Matt Portune
51cfd70398 Fix for short profile Name value crashing app (#1833) 2022-03-09 09:01:16 -05:00
Matt Portune
de566be994 fix for lock & logout message parsing issue (#1832) 2022-03-08 14:32:13 -05:00
Federico Maccaroni
819d1b616a Remove Microsoft.AppCenter.Crashes from Core.csproj on FDroid on the build.yml (#1831) 2022-03-08 14:31:57 -05:00
Matt Portune
17cdc96352 fix for logging out active account from switcher and cleanup (#1830) 2022-03-07 15:15:21 -05:00
Vincent Salucci
fcc94d85af [Captcha] Implement for 2fa (#1827) 2022-03-07 12:39:38 -06:00
Matt Portune
79a76c4638 Support for lock/logout/remove accounts from account list (#1826)
* Support for lock/logout/remove accounts via long-press

* establish and set listview height before showing

* undo modification
2022-03-07 12:28:06 -05:00
stevenlele
efd83d07dd [SupportedBrowsers] add Alook (#1814) 2022-03-07 09:16:58 -05:00
github-actions[bot]
0f14aa242c Autosync the updated translations (#1825)
Co-authored-by: github-actions <>
2022-03-04 01:26:35 +01:00
Micaiah Martin
a33232dec0 Renewed certificates and profiles (#1823) 2022-03-03 11:20:34 -07:00
Matt Gibson
084072e485 Add url encoding to data parameter (#1822) 2022-03-02 11:32:43 -06:00
Federico Maccaroni
db7ca3b93e BEEEP: Abstract and Centralize Logging (#1663)
* Abstracted App Center Logging into its own component, so that we can have it centralized in one place and we avoid checking for FDroid on all the places we want to use it

* Implemented the new logger where Crashes.TrackError was being used except on some specific cases

* Improved logging, added a debug logger and removed AppCenter to be used on DEBUG
2022-03-02 14:15:16 -03:00
Matt Portune
34d0ecf64b support for per-user biometric state tracking (#1820) 2022-03-01 14:04:17 -05:00
Daniel James Smith
2076c11cbd Bump target framework to netcoreapp3.1 (#1817)
Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
2022-02-28 12:04:09 -07:00
Micaiah Martin
4a508ea7a2 Added manual trigger for builds (#1819) 2022-02-28 11:30:27 -07:00
Matt Portune
9384b3e538 fixed migration and account removal issues (#1818) 2022-02-28 13:02:33 -05:00
Daniel James Smith
317e7dad9a BEEEP: Colorize hidden custom field when value visible (#1813) 2022-02-25 21:47:21 +01:00
github-actions[bot]
fac295c97b Autosync the updated translations (#1812)
Co-authored-by: github-actions <>
2022-02-25 12:29:47 +01:00
Matt Portune
f94812719d Apply Disable Favicon setting globally to match desktop (#1811)
* Apply Disable Favicon setting globally to match desktop

* streamline the approach to applying global settings
2022-02-24 17:13:00 -05:00
Matt Portune
be993bcd02 Fix for missing bio unlock on app restart (#1810) 2022-02-24 15:33:55 -05:00
Federico Maccaroni
c74ed668b5 Changed link on Settings "Change Master Password" and "Two Step Login" to go to the web vault settings. Also refactored a bit to reuse the urls (#1809) 2022-02-24 10:27:08 -03:00
Matt Portune
9201da8515 take environment into account when checking for existing account (#1808) 2022-02-23 15:30:49 -05:00
Matt Portune
2e8824ce05 Account Switching (#1807)
* Account Switching (#1720)

* Account switching

* WIP

* wip

* wip

* updates to send test logic

* fixed Send tests

* fixes for theme handling on account switching and re-adding existing account

* switch fixes

* fixes

* fixes

* cleanup

* vault timeout fixes

* account list status enhancements

* logout fixes and token handling improvements

* merge latest (#1727)

* remove duplicate dependency

* fix for initial login token storage paradox (#1730)

* Fix avatar color update toolbar item issue on iOS for account switching (#1735)

* Updated account switching menu UI (#1733)

* updated account switching menu UI

* additional changes

* add key suffix to constant

* GetFirstLetters method tweaks

* Fix crash on account switching when logging out when having more than user at a time (#1740)

* single account migration to multi-account on app update (#1741)

* Account Switching Tap to dismiss (#1743)

* Added tap to dismiss on the Account switching overlay and improved a bit the code

* Fix account switching overlay background transparent on the proper place

* Fixed transparent background and the shadow on the account switching overlay

* Fix iOS top space on Account switching list overlay after modal (#1746)

* Fix top space added to Account switching list overlay after closing modal

* Fix top space added to Account switching list overlay after closing modal on lock, login and home views just in case we add modals in the future there as well

* Usability: dismiss account list on certain events (#1748)

* dismiss account list on certain events

* use new FireAndForget method for back button logic

* Create and use Account Switching overlay control (#1753)

* Added Account switching overlay control and its own ViewModel and refactored accordingly

* Fix account switching Accounts list binding update

* Implemented dismiss account switching overlay when changing tabs and when selecting the same tab. Also updated the deprecated listener on CustomTabbedRenderer on Android (#1755)

* Overriden Equals on AvatarImageSource so it doesn't get set multiple times when it's the same image thus producing blinking on tab chaged (#1756)

* Usability improvements for logout on vault timeout (#1781)

* accountswitching fixes (#1784)

* Fix for invalid PIN lock state when switching accounts (#1792)

* fix for pin lock flow

* named tuple values and updated async

* clear send service cache on account switch (#1796)

* Global theme and account removal (#1793)

* Global theme and account removal

* remove redundant call to hide account list overlay

* cleanup and additional tweaks

* add try/catch to remove account dialog flow

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2022-02-23 12:40:17 -05:00
Micaiah Martin
ded3f07fa6 Fixes incorrect path in workflow (#1806) 2022-02-23 10:29:40 -06:00
Micaiah Martin
31a3ec963b [BEEEP] - Added workflows to ignored paths (#1802)
Makes sure that edits to workflow files don't trigger a build.
2022-02-23 09:34:26 -06:00
Micaiah Martin
4722d2f632 Add dry run option to release workflow (#1801)
* Add dry-run to release workflow.
2022-02-23 08:48:07 -06:00
Federico Maccaroni
fa1bc3fa14 Changed Input keyboard on phone to be the telephone keyboard and also capitalized the keyboard on some fields of add/edit identity (#1800) 2022-02-23 11:07:54 -03:00
Federico Maccaroni
fa8d59075b Fix Options being seen in two lines on Add/edit Send (#1798) 2022-02-22 12:28:43 -05:00
Federico Maccaroni
23ca0f4b93 Fix icon image size to be adaptive on Large Font Size Accessibility which fixes row height on large vault (#1795) 2022-02-22 10:33:38 -05:00
Chad Scharf
04f4ad48f0 We're Hiring (#1797)
Added link to README.md for Bitwarden Careers page.
2022-02-22 10:28:46 -03:00
Micaiah Martin
a9be659e27 Moved to new Google Service Account (#1789) 2022-02-18 15:05:13 -07:00
Micaiah Martin
39596d7533 Moved to new Google Service Account (#1788) 2022-02-18 15:21:59 -06:00
Micaiah Martin
dd2c24dcc7 Move to using shared workflow (#1787) 2022-02-18 13:29:14 -06:00
github-actions[bot]
ad6cf9318b Autosync the updated translations (#1786)
Co-authored-by: github-actions <>
2022-02-18 10:20:24 +01:00
Federico Maccaroni
ea471b0749 Fixed some Large Font Accessibility issues on Vault and Send screens for Icons Display #1774 (#1785) 2022-02-17 19:34:22 -03:00
Micaiah Martin
dbaa32b02c Created initial workflow for workflow linting (#1783) 2022-02-16 15:26:11 -06:00
Matt Gibson
46128bcfe6 Enforce Hold label (#1779)
* Enforce Hold label

* Linting

Co-authored-by: Micaiah Martin <77340197+mimartin12@users.noreply.github.com>
2022-02-16 08:43:46 -06:00
Federico Maccaroni
02562be8c7 Fix truncated bottom on Password generator when large font size is set on Android (#1782) 2022-02-15 19:10:43 -03:00
Joseph Flinn
95581bd4d9 Patch/release new build artifact name (#1778)
* Switching the iOS build artifact and release asset names

* disabling jobs/steps to test the new release asset name

* switching to download artifacts from rc

* testing the upload of the 'Bitwarden iOS' directory

* Build zip asset of the Bitwarden iOS asset

* trying a couple of different zip paths

* Final package test

* Re-enabling all of the jobs after testing
2022-02-15 07:57:21 -08:00
Jake Fink
aba34c38e9 remove erroneous autofill description (#1780) 2022-02-15 10:46:33 -05:00
github-actions[bot]
1af447c47f Bumped version to 2.16.3 (#1777)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-14 12:37:14 -08:00
245 changed files with 7742 additions and 2415 deletions

View File

@@ -6,6 +6,10 @@ on:
branches-ignore:
- 'l10n_master'
- 'gh-pages'
paths-ignore:
- '.github/workflows/**'
workflow_dispatch:
inputs: {}
jobs:
cloc:
@@ -237,6 +241,7 @@ jobs:
run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
$corePath = $($env:GITHUB_WORKSPACE + "/src/Core/Core.csproj");
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
@@ -302,6 +307,18 @@ jobs:
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);
Write-Output "########################################"
Write-Output "##### Uninstall from Core.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($corePath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($corePath);
shell: pwsh
- name: Restore packages
@@ -529,8 +546,7 @@ jobs:
|| github.ref == 'refs/heads/hotfix-rc'
env:
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
run: |
appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
run: appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
shell: bash
- name: Deploy to App Store

16
.github/workflows/enforce-labels.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
---
name: Enforce PR labels
on:
pull_request:
types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
enforce-label:
name: EnforceLabel
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
with:
BANNED_LABELS: "hold"
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"

View File

@@ -12,6 +12,7 @@ on:
options:
- Initial Release
- Redeploy
- dry-run
jobs:
release:
@@ -21,6 +22,7 @@ jobs:
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
- name: Branch check
if: github.event.inputs.release_type != 'dry-run'
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "==================================="
@@ -30,12 +32,13 @@ jobs:
fi
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Retrieve Mobile release version
id: retrieve-mobile-version
run: |
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' ./src/Android/Properties/AndroidManifest.xml | tr -d '"')
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' \
./src/Android/Properties/AndroidManifest.xml | tr -d '"')
echo "::set-output name=mobile_version::${ver}"
shell: bash
@@ -62,7 +65,7 @@ jobs:
echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Download all artifacts
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
workflow: build.yml
workflow_conclusion: success
@@ -72,7 +75,8 @@ jobs:
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
- name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
if: github.event.inputs.release_type != 'dry-run'
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
@@ -92,10 +96,10 @@ jobs:
needs: release
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Download F-Droid .apk artifact
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
workflow: build.yml
workflow_conclusion: success
@@ -103,7 +107,7 @@ jobs:
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.3.0
uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
with:
node-version: '10.x'
@@ -167,4 +171,5 @@ jobs:
cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages
if: github.event.inputs.release_type != 'dry-run'
run: npm run deploy

11
.github/workflows/workflow-linter.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
---
name: Workflow Linter
on:
pull_request:
paths:
- .github/workflows/**
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master

View File

@@ -23,6 +23,10 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
- Restore the nuget packages.
- Build and run the app.
# We're Hiring!
Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.
# Contribute
Code contributions are welcome! Visual Studio with Xamarin is required to work on this project. Please commit any pull requests against the `master` branch.

View File

@@ -31,6 +31,8 @@ namespace Bit.Droid.Accessibility
// So keep them in sync with:
// - AutofillHelpers.{TrustedBrowsers,CompatBrowsers}
// - Resources/xml/autofillservice.xml
new Browser("alook.browser", "search_fragment_input_view"),
new Browser("alook.browser.google", "search_fragment_input_view"),
new Browser("com.amazon.cloud9", "url"),
new Browser("com.android.browser", "url"),
new Browser("com.android.chrome", "url_bar"),

View File

@@ -10,7 +10,6 @@ using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
@@ -25,7 +24,7 @@ namespace Bit.Droid.Accessibility
private const string BitwardenPackage = "com.x8bit.bitwarden";
private const string BitwardenWebsite = "vault.bitwarden.com";
private IStorageService _storageService;
private IStateService _stateService;
private IBroadcasterService _broadcasterService;
private DateTime? _lastSettingsReload = null;
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
@@ -444,9 +443,9 @@ namespace Bit.Droid.Accessibility
private void LoadServices()
{
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
if (_broadcasterService == null)
{
@@ -460,12 +459,12 @@ namespace Bit.Droid.Accessibility
if (_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
{
_lastSettingsReload = now;
var uris = await _storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
var uris = await _stateService.GetAutofillBlacklistedUrisAsync();
if (uris != null)
{
_blacklistedUris = new HashSet<string>(uris);
}
var isAutoFillTileAdded = await _storageService.GetAsync<bool?>(Constants.AutofillTileAdded);
var isAutoFillTileAdded = await _stateService.GetAutofillTileAddedAsync();
AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault();
}
}

View File

@@ -84,7 +84,7 @@
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version>
<Version>1.7.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>122.0.0</Version>
@@ -171,7 +171,8 @@
<AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable\card.xml" />
<AndroidResource Include="Resources\drawable\cog.xml" />
<AndroidResource Include="Resources\drawable\cog_environment.xml" />
<AndroidResource Include="Resources\drawable\cog_settings.xml" />
<AndroidResource Include="Resources\drawable\icon.xml" />
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
<AndroidResource Include="Resources\drawable\ic_warning.xml" />

View File

@@ -51,6 +51,8 @@ namespace Bit.Droid.Autofill
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
public static HashSet<string> CompatBrowsers = new HashSet<string>
{
"alook.browser",
"alook.browser.google",
"com.amazon.cloud9",
"com.android.browser",
"com.android.chrome",

View File

@@ -1,4 +1,7 @@
using Android;
using System;
using System.Collections.Generic;
using System.Linq;
using Android;
using Android.App;
using Android.Content;
using Android.OS;
@@ -9,12 +12,6 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
namespace Bit.Droid.Autofill
{
@@ -26,9 +23,9 @@ namespace Bit.Droid.Autofill
{
private ICipherService _cipherService;
private IVaultTimeoutService _vaultTimeoutService;
private IStorageService _storageService;
private IPolicyService _policyService;
private IUserService _userService;
private IStateService _stateService;
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback)
@@ -44,18 +41,18 @@ namespace Bit.Droid.Autofill
var parser = new Parser(structure, ApplicationContext);
parser.Parse();
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
var shouldAutofill = await parser.ShouldAutofillAsync(_stateService);
if (!shouldAutofill)
{
return;
}
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
var inlineAutofillEnabled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
if (_vaultTimeoutService == null)
{
@@ -76,7 +73,7 @@ namespace Bit.Droid.Autofill
// build response
var response = AutofillHelpers.CreateFillResponse(parser, items, locked, inlineAutofillEnabled, request);
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync();
if (!disableSavePrompt.GetValueOrDefault())
{
AutofillHelpers.AddSaveInfo(parser, request, response, parser.FieldCollection);
@@ -85,9 +82,7 @@ namespace Bit.Droid.Autofill
}
catch (Exception e)
{
#if !FDROID
Crashes.TrackError(e);
#endif
_logger.Value.Exception(e);
}
}
@@ -101,12 +96,12 @@ namespace Bit.Droid.Autofill
return;
}
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync();
if (disableSavePrompt.GetValueOrDefault())
{
return;
@@ -161,9 +156,7 @@ namespace Bit.Droid.Autofill
}
catch (Exception e)
{
#if !FDROID
Crashes.TrackError(e);
#endif
_logger.Value.Exception(e);
}
}
}

View File

@@ -80,13 +80,13 @@ namespace Bit.Droid.Autofill
}
}
public async Task<bool> ShouldAutofillAsync(IStorageService storageService)
public async Task<bool> ShouldAutofillAsync(IStateService stateService)
{
var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
FieldCollection != null && FieldCollection.Fillable;
if (fillable)
{
var blacklistedUris = await storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
var blacklistedUris = await stateService.GetAutofillBlacklistedUrisAsync();
if (blacklistedUris != null && blacklistedUris.Count > 0)
{
fillable = !new HashSet<string>(blacklistedUris).Contains(Uri);

View File

@@ -32,7 +32,7 @@ namespace Bit.Droid
private IDeviceActionService _deviceActionService;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
private IUserService _userService;
private IStateService _stateService;
private IAppIdService _appIdService;
private IEventService _eventService;
private PendingIntent _eventUploadPendingIntent;
@@ -53,7 +53,7 @@ namespace Bit.Droid
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
@@ -69,8 +69,8 @@ namespace Bit.Droid
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
}
#if !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
#if !DEBUG && !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
var appCenterTask = appCenterHelper.InitAsync();
#endif
@@ -375,7 +375,7 @@ namespace Bit.Droid
{
Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor);
Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor);
ThemeHelpers.SetAppearance(ThemeManager.GetTheme(true), ThemeManager.OsDarkModeEnabled());
ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled());
}
private void ExitApp()

View File

@@ -53,7 +53,8 @@ namespace Bit.Droid
ServiceContainer.Resolve<IApiService>("apiService"),
ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"));
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"),
ServiceContainer.Resolve<ILogger>("logger"));
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
@@ -87,7 +88,14 @@ namespace Bit.Droid
private void RegisterLocalServices()
{
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
#if FDROID
ServiceContainer.Register<ILogger>("logger", new StubLogger());
#elif DEBUG
ServiceContainer.Register<ILogger>("logger", DebugLogger.Instance);
#else
ServiceContainer.Register<ILogger>("logger", Logger.Instance);
#endif
// Note: This might cause a race condition. Investigate more.
Task.Run(() =>
@@ -113,13 +121,16 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService,
var stateService = new StateService(mobileStorageService, secureStorageService);
var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var deviceActionService = new DeviceActionService(stateService, messagingService,
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
@@ -129,7 +140,9 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(mobileStorageService));
ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
@@ -148,7 +161,7 @@ namespace Bit.Droid
ServiceContainer.Register<IPushNotificationListenerService>(
"pushNotificationListenerService", notificationListenerService);
var androidPushNotificationService = new AndroidPushNotificationService(
mobileStorageService, notificationListenerService);
stateService, notificationListenerService);
ServiceContainer.Register<IPushNotificationService>(
"pushNotificationService", androidPushNotificationService);
#endif
@@ -164,10 +177,6 @@ namespace Bit.Droid
private async Task BootstrapAsync()
{
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService")
.GetAsync<bool?>(Constants.DisableFaviconKey);
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
Constants.DisableFaviconKey, disableFavicon);
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
}
}

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.16.4" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.17.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>

View File

@@ -16,10 +16,10 @@ namespace Bit.Droid.Push
{
public async override void OnNewToken(string token)
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token);
await stateService.SetPushRegisteredTokenAsync(token);
await pushNotificationService.RegisterAsync();
}

View File

@@ -1,5 +1,4 @@
using System;
using Android.App;
using Android.App;
using Android.Content;
using Bit.App.Abstractions;
using Bit.App.Utilities;
@@ -14,9 +13,10 @@ namespace Bit.Droid.Receivers
{
public override async void OnReceive(Context context, Intent intent)
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
await AppHelpers.PerformUpdateTasksAsync(ServiceContainer.Resolve<ISyncService>("syncService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"), storageService);
await AppHelpers.PerformUpdateTasksAsync(
ServiceContainer.Resolve<ISyncService>("syncService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"),
ServiceContainer.Resolve<IStateService>("stateService"));
}
}
}

View File

@@ -1,7 +1,9 @@
using Android.Content;
using Android.Views;
using Bit.App.Pages;
using Bit.Droid.Renderers;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Navigation;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
@@ -9,7 +11,7 @@ using Xamarin.Forms.Platform.Android.AppCompat;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomTabbedRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener
public class CustomTabbedRenderer : TabbedPageRenderer, NavigationBarView.IOnItemReselectedListener
{
private TabbedPage _page;
@@ -21,7 +23,7 @@ namespace Bit.Droid.Renderers
if (e.NewElement != null)
{
_page = e.NewElement;
GetBottomNavigationView()?.SetOnNavigationItemReselectedListener(this);
GetBottomNavigationView()?.SetOnItemReselectedListener(this);
}
else
{
@@ -53,6 +55,10 @@ namespace Bit.Droid.Renderers
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
if (_page is TabsPage tabsPage)
{
tabsPage.OnPageReselected();
}
Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync());
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="640dp"
android:height="512dp"
android:viewportWidth="640"
android:viewportHeight="512">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M532.989 289.887l-3.872-2.528c-3.197-1.866-5.744-4.667-7.299-8.026-1.558-3.358-2.048-7.113-1.405-10.759v-24.64c-0.682-3.57-0.202-7.266 1.37-10.542s4.154-5.964 7.366-7.666l4.768-2.4c12.013-7.054 20.768-18.547 24.384-32 3.421-13.333 1.661-27.466-4.928-39.552l-25.056-43.872c-7.011-11.727-18.259-20.313-31.418-23.987-13.161-3.674-27.229-2.155-39.303 4.243l-4.384 2.208c-3.286 1.769-6.983 2.63-10.711 2.496-3.731-0.135-7.357-1.261-10.505-3.263-7.082-4.796-14.579-8.951-22.4-12.416-3.28-1.636-6.038-4.157-7.962-7.278s-2.935-6.719-2.918-10.386v-6.528c0.099-6.965-1.197-13.879-3.808-20.335s-6.49-12.326-11.401-17.264c-4.915-4.937-10.765-8.842-17.209-11.486s-13.351-3.972-20.317-3.907h-51.2c-6.952-0.043-13.842 1.301-20.267 3.954s-12.257 6.561-17.154 11.496c-4.896 4.935-8.758 10.797-11.361 17.243s-3.892 13.347-3.794 20.298v5.472c0.032 3.614-0.938 7.165-2.802 10.261s-4.55 5.614-7.758 7.275c-5.691 2.572-11.197 5.533-16.48 8.864l-6.080 3.584c-3.102 2.221-6.788 3.481-10.6 3.623s-7.582-0.839-10.84-2.823l-3.968-1.952c-5.856-3.516-12.377-5.778-19.153-6.642s-13.656-0.314-20.208 1.618c-13.446 3.716-24.885 12.58-31.84 24.672l-24.96 43.68c-3.566 6.048-5.867 12.757-6.763 19.721s-0.37 14.037 1.547 20.791c1.743 6.495 4.779 12.571 8.925 17.866s9.317 9.699 15.203 12.95l2.88 2.848 1.312 0.928c3.197 1.867 5.744 4.667 7.3 8.026s2.046 7.113 1.403 10.758v24.704c0.326 3.533-0.314 7.087-1.853 10.283s-3.918 5.913-6.883 7.861l-4.768 2.4c-11.724 7.217-20.258 18.63-23.866 31.917s-2.020 27.447 4.442 39.603l25.088 43.872c6.806 11.955 18.058 20.739 31.308 24.445 13.25 3.702 27.425 2.026 39.445-4.669l4.352-2.176c3.287-1.792 6.994-2.669 10.736-2.547 3.742 0.125 7.382 1.248 10.544 3.251 7.082 4.797 14.578 8.954 22.4 12.416 3.281 1.635 6.038 4.157 7.962 7.28 1.923 3.12 2.934 6.717 2.918 10.384v5.472c-0.102 6.954 1.185 13.859 3.788 20.31s6.468 12.317 11.368 17.251c4.901 4.938 10.738 8.845 17.169 11.495s13.327 3.987 20.282 3.936h51.2c6.957 0.051 13.856-1.286 20.288-3.936s12.272-6.557 17.175-11.491c4.902-4.938 8.771-10.8 11.379-17.251 2.605-6.451 3.897-13.357 3.798-20.313v-5.472c-0.032-3.613 0.938-7.165 2.803-10.259 1.863-3.098 4.547-5.616 7.757-7.277 5.683-2.567 11.181-5.526 16.448-8.864l1.376-0.8 4.704-2.784c3.111-2.211 6.803-3.466 10.618-3.606 3.815-0.144 7.587 0.832 10.854 2.807l3.968 1.952c5.993 3.568 12.653 5.878 19.565 6.791 6.915 0.912 13.945 0.409 20.659-1.478 6.599-1.805 12.755-4.95 18.080-9.248 5.325-4.295 9.706-9.645 12.864-15.712l24.96-43.68c3.504-5.907 5.757-12.474 6.615-19.289 0.861-6.816 0.307-13.735-1.622-20.327-3.584-13.397-12.298-24.846-24.256-31.873zM319.997 346.752c-17.949 0-35.495-5.322-50.419-15.296-14.924-9.971-26.556-24.144-33.424-40.727s-8.666-34.83-5.165-52.434c3.502-17.604 12.145-33.775 24.837-46.466s28.862-21.335 46.466-24.837c17.604-3.502 35.852-1.704 52.434 5.164s30.755 18.501 40.73 33.425c9.971 14.924 15.293 32.47 15.293 50.419 0 24.069-9.562 47.153-26.579 64.17-17.021 17.021-40.103 26.582-64.173 26.582z" />
</vector>

View File

@@ -6,4 +6,4 @@
<path
android:fillColor="#FF000000"
android:pathData="M532.989 289.887l-3.872-2.528c-3.197-1.866-5.744-4.667-7.299-8.026-1.558-3.358-2.048-7.113-1.405-10.759v-24.64c-0.682-3.57-0.202-7.266 1.37-10.542s4.154-5.964 7.366-7.666l4.768-2.4c12.013-7.054 20.768-18.547 24.384-32 3.421-13.333 1.661-27.466-4.928-39.552l-25.056-43.872c-7.011-11.727-18.259-20.313-31.418-23.987-13.161-3.674-27.229-2.155-39.303 4.243l-4.384 2.208c-3.286 1.769-6.983 2.63-10.711 2.496-3.731-0.135-7.357-1.261-10.505-3.263-7.082-4.796-14.579-8.951-22.4-12.416-3.28-1.636-6.038-4.157-7.962-7.278s-2.935-6.719-2.918-10.386v-6.528c0.099-6.965-1.197-13.879-3.808-20.335s-6.49-12.326-11.401-17.264c-4.915-4.937-10.765-8.842-17.209-11.486s-13.351-3.972-20.317-3.907h-51.2c-6.952-0.043-13.842 1.301-20.267 3.954s-12.257 6.561-17.154 11.496c-4.896 4.935-8.758 10.797-11.361 17.243s-3.892 13.347-3.794 20.298v5.472c0.032 3.614-0.938 7.165-2.802 10.261s-4.55 5.614-7.758 7.275c-5.691 2.572-11.197 5.533-16.48 8.864l-6.080 3.584c-3.102 2.221-6.788 3.481-10.6 3.623s-7.582-0.839-10.84-2.823l-3.968-1.952c-5.856-3.516-12.377-5.778-19.153-6.642s-13.656-0.314-20.208 1.618c-13.446 3.716-24.885 12.58-31.84 24.672l-24.96 43.68c-3.566 6.048-5.867 12.757-6.763 19.721s-0.37 14.037 1.547 20.791c1.743 6.495 4.779 12.571 8.925 17.866s9.317 9.699 15.203 12.95l2.88 2.848 1.312 0.928c3.197 1.867 5.744 4.667 7.3 8.026s2.046 7.113 1.403 10.758v24.704c0.326 3.533-0.314 7.087-1.853 10.283s-3.918 5.913-6.883 7.861l-4.768 2.4c-11.724 7.217-20.258 18.63-23.866 31.917s-2.020 27.447 4.442 39.603l25.088 43.872c6.806 11.955 18.058 20.739 31.308 24.445 13.25 3.702 27.425 2.026 39.445-4.669l4.352-2.176c3.287-1.792 6.994-2.669 10.736-2.547 3.742 0.125 7.382 1.248 10.544 3.251 7.082 4.797 14.578 8.954 22.4 12.416 3.281 1.635 6.038 4.157 7.962 7.28 1.923 3.12 2.934 6.717 2.918 10.384v5.472c-0.102 6.954 1.185 13.859 3.788 20.31s6.468 12.317 11.368 17.251c4.901 4.938 10.738 8.845 17.169 11.495s13.327 3.987 20.282 3.936h51.2c6.957 0.051 13.856-1.286 20.288-3.936s12.272-6.557 17.175-11.491c4.902-4.938 8.771-10.8 11.379-17.251 2.605-6.451 3.897-13.357 3.798-20.313v-5.472c-0.032-3.613 0.938-7.165 2.803-10.259 1.863-3.098 4.547-5.616 7.757-7.277 5.683-2.567 11.181-5.526 16.448-8.864l1.376-0.8 4.704-2.784c3.111-2.211 6.803-3.466 10.618-3.606 3.815-0.144 7.587 0.832 10.854 2.807l3.968 1.952c5.993 3.568 12.653 5.878 19.565 6.791 6.915 0.912 13.945 0.409 20.659-1.478 6.599-1.805 12.755-4.95 18.080-9.248 5.325-4.295 9.706-9.645 12.864-15.712l24.96-43.68c3.504-5.907 5.757-12.474 6.615-19.289 0.861-6.816 0.307-13.735-1.622-20.327-3.584-13.397-12.298-24.846-24.256-31.873zM319.997 346.752c-17.949 0-35.495-5.322-50.419-15.296-14.924-9.971-26.556-24.144-33.424-40.727s-8.666-34.83-5.165-52.434c3.502-17.604 12.145-33.775 24.837-46.466s28.862-21.335 46.466-24.837c17.604-3.502 35.852-1.704 52.434 5.164s30.755 18.501 40.73 33.425c9.971 14.924 15.293 32.47 15.293 50.419 0 24.069-9.562 47.153-26.579 64.17-17.021 17.021-40.103 26.582-64.173 26.582z" />
</vector>
</vector>

View File

@@ -11,6 +11,12 @@
-->
<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsInlineSuggestions="true">
<compatibility-package
android:name="alook.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="alook.browser.google"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.amazon.cloud9"
android:maxLongVersionCode="10000000000"/>

View File

@@ -3,7 +3,7 @@ using System;
namespace Bit.Core.Services
{
public class AndroidLogService : ILogService
public class AndroidLogService : INativeLogService
{
private static readonly string _tag = "BITWARDEN";

View File

@@ -3,7 +3,6 @@ using System;
using System.Threading.Tasks;
using AndroidX.Core.App;
using Bit.App.Abstractions;
using Bit.Core;
using Bit.Core.Abstractions;
using Xamarin.Forms;
@@ -11,14 +10,14 @@ namespace Bit.Droid.Services
{
public class AndroidPushNotificationService : IPushNotificationService
{
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly IPushNotificationListenerService _pushNotificationListenerService;
public AndroidPushNotificationService(
IStorageService storageService,
IStateService stateService,
IPushNotificationListenerService pushNotificationListenerService)
{
_storageService = storageService;
_stateService = stateService;
_pushNotificationListenerService = pushNotificationListenerService;
}
@@ -26,12 +25,12 @@ namespace Bit.Droid.Services
public async Task<string> GetTokenAsync()
{
return await _storageService.GetAsync<string>(Constants.PushCurrentTokenKey);
return await _stateService.GetPushCurrentTokenAsync();
}
public async Task RegisterAsync()
{
var registeredToken = await _storageService.GetAsync<string>(Constants.PushRegisteredTokenKey);
var registeredToken = await _stateService.GetPushRegisteredTokenAsync();
var currentToken = await GetTokenAsync();
if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken)
{
@@ -39,7 +38,7 @@ namespace Bit.Droid.Services
}
else
{
await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow);
await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow);
}
}

View File

@@ -12,12 +12,12 @@ namespace Bit.Droid.Services
{
public class ClipboardService : IClipboardService
{
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly Lazy<PendingIntent> _clearClipboardPendingIntent;
public ClipboardService(IStorageService storageService)
public ClipboardService(IStateService stateService)
{
_storageService = storageService;
_stateService = stateService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
@@ -39,7 +39,7 @@ namespace Bit.Droid.Services
if (clearMs < 0)
{
// if not set then we need to check if the user set this config
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
var clearSeconds = await _stateService.GetClearClipboardAsync();
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;

View File

@@ -35,7 +35,7 @@ namespace Bit.Droid.Services
{
public class DeviceActionService : IDeviceActionService
{
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly Func<IEventService> _eventServiceFunc;
@@ -47,12 +47,12 @@ namespace Bit.Droid.Services
private string _userAgent;
public DeviceActionService(
IStorageService storageService,
IStateService stateService,
IMessagingService messagingService,
IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc)
{
_storageService = storageService;
_stateService = stateService;
_messagingService = messagingService;
_broadcasterService = broadcasterService;
_eventServiceFunc = eventServiceFunc;
@@ -333,7 +333,7 @@ namespace Bit.Droid.Services
try
{
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
await _storageService.SaveAsync(Constants.LastFileCacheClearKey, DateTime.UtcNow);
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
}
catch (Exception) { }
}
@@ -916,9 +916,8 @@ namespace Bit.Droid.Services
{
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
{
var userService = ServiceContainer.Resolve<IUserService>("userService");
var autoCopyDisabled = await _storageService.GetAsync<bool?>(Constants.DisableAutoTotpCopyKey);
var canAccessPremium = await userService.CanAccessPremiumAsync();
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
{
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
@@ -938,5 +937,16 @@ namespace Bit.Droid.Services
Context.ClipboardService) as Android.Content.ClipboardManager;
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text);
}
public float GetSystemFontSizeScale()
{
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
return activity?.Resources?.Configuration?.FontScale ?? 1;
}
public async Task OnAccountSwitchCompleteAsync()
{
// for any Android-specific cleanup required after switching accounts
}
}
}

View File

@@ -4,7 +4,6 @@ using Android.Content;
using Android.Runtime;
using Android.Service.QuickSettings;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Accessibility;
@@ -18,7 +17,7 @@ namespace Bit.Droid.Tile
[Register("com.x8bit.bitwarden.AutofillTileService")]
public class AutofillTileService : TileService
{
private IStorageService _storageService;
private IStateService _stateService;
public override void OnTileAdded()
{
@@ -59,11 +58,11 @@ namespace Bit.Droid.Tile
private void SetTileAdded(bool isAdded)
{
AccessibilityHelpers.IsAutofillTileAdded = isAdded;
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
_storageService.SaveAsync(Constants.AutofillTileAdded, isAdded);
_stateService.SetAutofillTileAddedAsync(isAdded);
}
private void ScanAndFill()

View File

@@ -12,22 +12,22 @@ namespace Bit.Droid.Utilities
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
private readonly IAppIdService _appIdService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private string _userId;
private string _appId;
public AppCenterHelper(
IAppIdService appIdService,
IUserService userService)
IStateService stateService)
{
_appIdService = appIdService;
_userService = userService;
_stateService = stateService;
}
public async Task InitAsync()
{
_userId = await _userService.GetUserIdAsync();
_userId = await _stateService.GetActiveUserIdAsync();
_appId = await _appIdService.GetAppIdAsync();
AppCenter.Start(AppSecret, typeof(Crashes));

View File

@@ -45,5 +45,7 @@ namespace Bit.App.Abstractions
long GetActiveTime();
void CloseMainApp();
bool SupportsFido2();
float GetSystemFontSizeScale();
Task OnAccountSwitchCompleteAsync();
}
}

View File

@@ -15,12 +15,13 @@
<ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.1" />
</ItemGroup>
<ItemGroup>
@@ -121,17 +122,20 @@
<DependentUpon>SendGroupingsPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Remove="Pages\Accounts\AccountsPopupPage.xaml.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Behaviors\" />
<Folder Include="Controls\AccountSwitchingOverlay\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
</ItemGroup>
<ItemGroup>
@@ -416,5 +420,6 @@
<ItemGroup>
<None Remove="Behaviors\" />
<None Remove="Xamarin.CommunityToolkit" />
<None Remove="Controls\AccountSwitchingOverlay\" />
</ItemGroup>
</Project>

View File

@@ -4,12 +4,12 @@ using Bit.App.Pages;
using Bit.App.Resources;
using Bit.App.Services;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using System;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
@@ -18,7 +18,6 @@ namespace Bit.App
{
public partial class App : Application
{
private readonly IUserService _userService;
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IStateService _stateService;
@@ -26,7 +25,6 @@ namespace Bit.App
private readonly ISyncService _syncService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService;
private readonly IStorageService _storageService;
private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService;
@@ -40,7 +38,6 @@ namespace Bit.App
Current = this;
return;
}
_userService = ServiceContainer.Resolve<IUserService>("userService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
@@ -48,7 +45,6 @@ namespace Bit.App
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@@ -77,7 +73,10 @@ namespace Bit.App
}
else if (message.Command == "locked")
{
await LockedAsync(!(message.Data as bool?).GetValueOrDefault());
var extras = message.Data as Tuple<string, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? false;
Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
}
else if (message.Command == "lockVault")
{
@@ -85,8 +84,11 @@ namespace Bit.App
}
else if (message.Command == "logout")
{
Device.BeginInvokeOnMainThread(async () =>
await LogOutAsync((message.Data as bool?).GetValueOrDefault()));
var extras = message.Data as Tuple<string, bool, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? true;
var expired = extras?.Item3 ?? false;
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
}
else if (message.Command == "loggedOut")
{
@@ -107,6 +109,18 @@ namespace Bit.App
await SleptAsync();
}
}
else if (message.Command == "addAccount")
{
await AddAccount();
}
else if (message.Command == "accountAdded")
{
await UpdateThemeAsync();
}
else if (message.Command == "switchedAccount")
{
await SwitchedAccountAsync();
}
else if (message.Command == "migrated")
{
await Task.Delay(1000);
@@ -168,7 +182,7 @@ namespace Bit.App
if (string.IsNullOrWhiteSpace(Options.Uri))
{
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_storageService);
_stateService);
if (!updated)
{
SyncIfNeeded();
@@ -192,7 +206,7 @@ namespace Bit.App
var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked)
{
await _storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime());
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
SetTabsPageFromAutofill(isLocked);
await SleptAsync();
@@ -233,7 +247,7 @@ namespace Bit.App
{
await Device.InvokeOnMainThreadAsync(() =>
{
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
ThemeManager.SetTheme(Current.Resources);
_messagingService.Send("updatedTheme");
});
}
@@ -246,12 +260,12 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar();
}
private async Task LogOutAsync(bool expired)
private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
{
await AppHelpers.LogOutAsync();
await AppHelpers.LogOutAsync(userId, userInitiated);
await SetMainPageAsync();
_authService.LogOut(() =>
{
Current.MainPage = new HomePage();
if (expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
@@ -259,12 +273,50 @@ namespace Bit.App
});
}
private async Task AddAccount()
{
Device.BeginInvokeOnMainThread(async () =>
{
Options.HideAccountSwitcher = false;
Current.MainPage = new NavigationPage(new HomePage(Options));
});
}
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
Device.BeginInvokeOnMainThread(async () =>
{
if (await _vaultTimeoutService.ShouldTimeoutAsync())
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await SetMainPageAsync();
}
await Task.Delay(50);
await UpdateThemeAsync();
});
}
private async Task SetMainPageAsync()
{
var authed = await _userService.IsAuthenticatedAsync();
var authed = await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLockedAsync())
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else if (await _vaultTimeoutService.IsLockedAsync() ||
await _vaultTimeoutService.ShouldLockAsync())
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
@@ -287,13 +339,26 @@ namespace Bit.App
}
else
{
Current.MainPage = new HomePage(Options);
Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
// TODO implement orgIdentifier flow to SSO Login page, same as email flow below
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
var email = await _stateService.GetEmailAsync();
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else
{
Current.MainPage = new NavigationPage(new HomePage(Options));
}
}
}
private async Task ClearCacheIfNeededAsync()
{
var lastClear = await _storageService.GetAsync<DateTime?>(Constants.LastFileCacheClearKey);
var lastClear = await _stateService.GetLastFileCacheClearAsync();
if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
{
var task = Task.Run(() => _deviceActionService.ClearCacheAsync());
@@ -336,12 +401,12 @@ namespace Bit.App
{
InitializeComponent();
SetCulture();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
ThemeManager.SetTheme(Current.Resources);
Current.RequestedThemeChanged += (s, a) =>
{
UpdateThemeAsync();
};
Current.MainPage = new HomePage();
Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
}
@@ -363,12 +428,18 @@ namespace Bit.App
});
}
private async Task LockedAsync(bool autoPromptBiometric)
private async Task LockedAsync(string userId, bool userInitiated)
{
await _stateService.PurgeAsync();
if (!await _stateService.IsActiveAccountAsync(userId))
{
_platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
return;
}
var autoPromptBiometric = !userInitiated;
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
@@ -398,7 +469,7 @@ namespace Bit.App
}
}
}
await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock);
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
var lockPage = new LockPage(Options, autoPromptBiometric);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
}

View File

@@ -0,0 +1,57 @@
<?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:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:view="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
x:Name="_mainOverlay"
x:DataType="controls:AccountSwitchingOverlayViewModel"
x:Class="Bit.App.Controls.AccountSwitchingOverlayView"
BackgroundColor="#22000000"
Padding="0"
IsVisible="False">
<StackLayout
x:Name="_accountListContainer"
VerticalOptions="Fill"
HorizontalOptions="FillAndExpand"
BackgroundColor="Transparent">
<Frame
Padding="0"
HorizontalOptions="Fill"
VerticalOptions="Start"
xct:ShadowEffect.Color="Black"
xct:ShadowEffect.Radius="10"
xct:ShadowEffect.OffsetY="3">
<ListView
x:Name="_accountListView"
ItemsSource="{Binding BindingContext.AccountViews, Source={x:Reference _mainOverlay}}"
BackgroundColor="{DynamicResource BackgroundColor}"
VerticalOptions="Start"
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
<ListView.ItemTemplate>
<DataTemplate x:DataType="view:AccountView">
<controls:AccountViewCell
Account="{Binding .}"
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Effects>
<effects:ScrollViewContentInsetAdjustmentBehaviorEffect />
</ListView.Effects>
</ListView>
</Frame>
<BoxView
BackgroundColor="Transparent"
HorizontalOptions="Fill"
VerticalOptions="FillAndExpand">
<BoxView.GestureRecognizers>
<TapGestureRecognizer Tapped="FreeSpaceOverlay_Tapped" />
</BoxView.GestureRecognizers>
</BoxView>
</StackLayout>
</ContentView>

View File

@@ -0,0 +1,188 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class AccountSwitchingOverlayView : ContentView
{
public static readonly BindableProperty MainPageProperty = BindableProperty.Create(
nameof(MainPage),
typeof(ContentPage),
typeof(AccountSwitchingOverlayView),
defaultBindingMode: BindingMode.OneWay);
public static readonly BindableProperty MainFabProperty = BindableProperty.Create(
nameof(MainFab),
typeof(View),
typeof(AccountSwitchingOverlayView),
defaultBindingMode: BindingMode.OneWay);
public ContentPage MainPage
{
get => (ContentPage)GetValue(MainPageProperty);
set => SetValue(MainPageProperty, value);
}
public View MainFab
{
get => (View)GetValue(MainFabProperty);
set => SetValue(MainFabProperty, value);
}
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public AccountSwitchingOverlayView()
{
InitializeComponent();
ToggleVisibililtyCommand = new AsyncCommand(ToggleVisibilityAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
LongPressAccountCommand = new AsyncCommand<AccountViewCellViewModel>(LongPressAccountAsync,
onException: ex => _logger.Value.Exception(ex),
allowsMultipleExecutions: false);
}
public AccountSwitchingOverlayViewModel ViewModel => BindingContext as AccountSwitchingOverlayViewModel;
public ICommand ToggleVisibililtyCommand { get; }
public ICommand SelectAccountCommand { get; }
public ICommand LongPressAccountCommand { get; }
public int AccountListRowHeight => Device.RuntimePlatform == Device.Android ? 74 : 70;
public async Task ToggleVisibilityAsync()
{
if (IsVisible)
{
await HideAsync();
}
else
{
await ShowAsync();
}
}
public async Task ShowAsync()
{
if (ViewModel == null)
{
return;
}
await ViewModel.RefreshAccountViewsAsync();
await Device.InvokeOnMainThreadAsync(async () =>
{
// start listView in default (off-screen) position
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 0);
// re-measure in case accounts have been removed without changing screens
if (ViewModel.AccountViews != null)
{
_accountListView.HeightRequest = AccountListRowHeight * ViewModel.AccountViews.Count;
}
// set overlay opacity to zero before making visible and start fade-in
Opacity = 0;
IsVisible = true;
this.FadeTo(1, 100);
if (Device.RuntimePlatform == Device.Android && MainFab != null)
{
// start fab fade-out
MainFab.FadeTo(0, 200);
}
// slide account list into view
await _accountListContainer.TranslateTo(0, 0, 200, Easing.SinOut);
});
}
public async Task HideAsync()
{
if (!IsVisible)
{
// already hidden, don't animate again
return;
}
// Not all animations are awaited. This is intentional to allow multiple simultaneous animations.
await Device.InvokeOnMainThreadAsync(async () =>
{
// start overlay fade-out
this.FadeTo(0, 200);
if (Device.RuntimePlatform == Device.Android && MainFab != null)
{
// start fab fade-in
MainFab.FadeTo(1, 200);
}
// slide account list out of view
await _accountListContainer.TranslateTo(0, _accountListContainer.Height * -1, 200, Easing.SinIn);
// remove overlay
IsVisible = false;
});
}
private async void FreeSpaceOverlay_Tapped(object sender, EventArgs e)
{
try
{
await HideAsync();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
private async Task SelectAccountAsync(AccountViewCellViewModel item)
{
try
{
await Task.Delay(100);
await HideAsync();
ViewModel?.SelectAccountCommand?.Execute(item);
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
private async Task LongPressAccountAsync(AccountViewCellViewModel item)
{
if (!item.IsAccount)
{
return;
}
try
{
await Task.Delay(100);
await HideAsync();
ViewModel?.LongPressAccountCommand?.Execute(
new Tuple<ContentPage, AccountViewCellViewModel>(MainPage, item));
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class AccountSwitchingOverlayViewModel : ExtendedViewModel
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
public AccountSwitchingOverlayViewModel(IStateService stateService,
IMessagingService messagingService,
ILogger logger)
{
_stateService = stateService;
_messagingService = messagingService;
SelectAccountCommand = new AsyncCommand<AccountViewCellViewModel>(SelectAccountAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
LongPressAccountCommand = new AsyncCommand<Tuple<ContentPage, AccountViewCellViewModel>>(LongPressAccountAsync,
onException: ex => logger.Exception(ex),
allowsMultipleExecutions: false);
}
// this needs to be a new list every time for the binding to get updated,
// XF doesn't currentlyl provide a direct way to update on same instance
// https://github.com/xamarin/Xamarin.Forms/issues/1950
public List<AccountView> AccountViews => _stateService?.AccountViews is null ? null : new List<AccountView>(_stateService.AccountViews);
public bool AllowActiveAccountSelection { get; set; }
public bool AllowAddAccountRow { get; set; }
public ICommand SelectAccountCommand { get; }
public ICommand LongPressAccountCommand { get; }
private async Task SelectAccountAsync(AccountViewCellViewModel item)
{
if (item.AccountView.IsAccount)
{
if (!item.AccountView.IsActive)
{
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send("switchedAccount");
}
else if (AllowActiveAccountSelection)
{
_messagingService.Send("switchedAccount");
}
}
else
{
_messagingService.Send("addAccount");
}
}
private async Task LongPressAccountAsync(Tuple<ContentPage, AccountViewCellViewModel> item)
{
var (page, account) = item;
if (account.AccountView.IsAccount)
{
await AppHelpers.AccountListOptions(page, account);
}
}
public async Task RefreshAccountViewsAsync()
{
await _stateService.RefreshAccountViewsAsync(AllowAddAccountRow);
Device.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(AccountViews)));
}
}
}

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="Bit.App.Controls.AccountViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:Name="_accountView"
x:DataType="controls:AccountViewCellViewModel">
<Grid RowSpacing="0"
ColumnSpacing="0"
xct:TouchEffect.NativeAnimation="True"
xct:TouchEffect.Command="{Binding SelectAccountCommand, Source={x:Reference _accountView}}"
xct:TouchEffect.CommandParameter="{Binding .}"
xct:TouchEffect.LongPressCommand="{Binding LongPressAccountCommand, Source={x:Reference _accountView}}"
xct:TouchEffect.LongPressCommandParameter="{Binding .}">
<Grid.Resources>
<u:InverseBoolConverter x:Key="inverseBool" />
</Grid.Resources>
<Grid
IsVisible="{Binding IsAccount}"
VerticalOptions="CenterAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Source="{Binding AvatarImageSource}"
HorizontalOptions="Center"
Margin="10,0"
VerticalOptions="Center" />
<Grid
Grid.Column="1"
RowSpacing="1"
VerticalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Text="{Binding AccountView.Email}"
IsVisible="{Binding IsActive}"
StyleClass="accountlist-title, accountlist-title-platform"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="0"
Text="{Binding AccountView.Email}"
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
StyleClass="accountlist-title, accountlist-title-platform"
TextColor="{DynamicResource MutedColor}"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="1"
IsVisible="{Binding ShowHostname}"
Text="{Binding AccountView.Hostname}"
StyleClass="accountlist-sub, accountlist-sub-platform"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="2"
Text="{u:I18n AccountUnlocked}"
IsVisible="{Binding IsUnlockedAndNotActive}"
StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="2"
Text="{u:I18n AccountLocked}"
IsVisible="{Binding IsLockedAndNotActive}"
StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
<Label
Grid.Row="2"
Text="{u:I18n AccountLoggedOut}"
IsVisible="{Binding IsLoggedOutAndNotActive}"
StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
</Grid>
<controls:IconLabel
Grid.Column="2"
Text="{Binding AuthStatusIconNotActive}"
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
Margin="12,0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform" />
<controls:IconLabel
Grid.Column="2"
Text="{Binding AuthStatusIconActive}"
IsVisible="{Binding IsActive}"
Margin="12,0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
TextColor="{DynamicResource TextColor}"/>
</Grid>
<Grid
IsVisible="{Binding IsAccount, Converter={StaticResource inverseBool}}"
VerticalOptions="CenterAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
VerticalOptions="Center"
HorizontalOptions="Center"
Margin="14,0"
WidthRequest="{OnPlatform 24, iOS=24, Android=26}"
HeightRequest="{OnPlatform 24, iOS=24, Android=26}"
Source="plus.png"
xct:IconTintColorEffect.TintColor="{DynamicResource TextColor}"
AutomationProperties.IsInAccessibleTree="False" />
<Label
Text="{u:I18n AddAccount}"
StyleClass="accountlist-title, accountlist-title-platform"
LineBreakMode="TailTruncation"
VerticalOptions="Center"
Grid.Column="1" />
</Grid>
</Grid>
</ViewCell>

View File

@@ -0,0 +1,54 @@
using Bit.Core.Models.View;
using System.Windows.Input;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class AccountViewCell : ViewCell
{
public static readonly BindableProperty AccountProperty = BindableProperty.Create(
nameof(Account), typeof(AccountView), typeof(AccountViewCell));
public static readonly BindableProperty SelectAccountCommandProperty = BindableProperty.Create(
nameof(SelectAccountCommand), typeof(ICommand), typeof(AccountViewCell));
public static readonly BindableProperty LongPressAccountCommandProperty = BindableProperty.Create(
nameof(LongPressAccountCommand), typeof(ICommand), typeof(AccountViewCell));
public AccountViewCell()
{
InitializeComponent();
}
public AccountView Account
{
get => GetValue(AccountProperty) as AccountView;
set => SetValue(AccountProperty, value);
}
public ICommand SelectAccountCommand
{
get => GetValue(SelectAccountCommandProperty) as ICommand;
set => SetValue(SelectAccountCommandProperty, value);
}
public ICommand LongPressAccountCommand
{
get => GetValue(LongPressAccountCommandProperty) as ICommand;
set => SetValue(LongPressAccountCommandProperty, value);
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == AccountProperty.PropertyName)
{
if (Account == null)
{
return;
}
BindingContext = new AccountViewCellViewModel(Account);
}
}
}
}

View File

@@ -0,0 +1,93 @@
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Controls
{
public class AccountViewCellViewModel : ExtendedViewModel
{
private AccountView _accountView;
private AvatarImageSource _avatar;
public AccountViewCellViewModel(AccountView accountView)
{
AccountView = accountView;
AvatarImageSource = new AvatarImageSource(AccountView.Name, AccountView.Email);
}
public AccountView AccountView
{
get => _accountView;
set => SetProperty(ref _accountView, value);
}
public AvatarImageSource AvatarImageSource
{
get => _avatar;
set => SetProperty(ref _avatar, value);
}
public bool IsAccount
{
get => AccountView.IsAccount;
}
public bool ShowHostname
{
get => !string.IsNullOrWhiteSpace(AccountView.Hostname) && AccountView.Hostname != "vault.bitwarden.com";
}
public bool IsActive
{
get => AccountView.IsActive;
}
public bool IsUnlocked
{
get => AccountView.AuthStatus == AuthenticationStatus.Unlocked;
}
public bool IsUnlockedAndNotActive
{
get => IsUnlocked && !IsActive;
}
public bool IsLocked
{
get => AccountView.AuthStatus == AuthenticationStatus.Locked;
}
public bool IsLockedAndNotActive
{
get => IsLocked && !IsActive;
}
public bool IsLoggedOut
{
get => AccountView.AuthStatus == AuthenticationStatus.LoggedOut;
}
public bool IsLoggedOutAndNotActive
{
get => IsLoggedOut && !IsActive;
}
public string AuthStatusIconActive
{
get => BitwardenIcons.CheckCircle;
}
public string AuthStatusIconNotActive
{
get
{
if (IsUnlocked)
{
return BitwardenIcons.Unlock;
}
return BitwardenIcons.Lock;
}
}
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SkiaSharp;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class AvatarImageSource : StreamImageSource
{
private string _data;
public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}
if (obj is AvatarImageSource avatar)
{
return avatar._data == _data;
}
return base.Equals(obj);
}
public override int GetHashCode() => _data?.GetHashCode() ?? -1;
public AvatarImageSource(string name = null, string email = null)
{
_data = name;
if (string.IsNullOrWhiteSpace(_data))
{
_data = email;
}
}
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
private Task<Stream> GetStreamAsync(CancellationToken userToken = new CancellationToken())
{
OnLoadingStarted();
userToken.Register(CancellationTokenSource.Cancel);
var result = Draw();
OnLoadingCompleted(CancellationTokenSource.IsCancellationRequested);
return Task.FromResult(result);
}
private Stream Draw()
{
string chars = null;
string upperData = null;
if (string.IsNullOrEmpty(_data))
{
chars = "..";
}
else if (_data?.Length > 1)
{
upperData = _data.ToUpper();
chars = GetFirstLetters(upperData, 2);
}
else
{
chars = upperData = _data.ToUpper();
}
var bgColor = StringToColor(upperData);
var textColor = Color.White;
var size = 50;
var bitmap = new SKBitmap(
size * 2,
size * 2,
SKImageInfo.PlatformColorType,
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
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor.ToHex())
};
canvas.DrawCircle(midX, midY, radius, circlePaint);
var typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Normal);
var textSize = midX / 1.3f;
var textPaint = new SKPaint
{
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColor.Parse(textColor.ToHex()),
TextSize = textSize,
TextAlign = SKTextAlign.Center,
Typeface = typeface
};
var rect = new SKRect();
textPaint.MeasureText(chars, ref rect);
canvas.DrawText(chars, midX, midY + rect.Height / 2, textPaint);
return SKImage.FromBitmap(bitmap).Encode(SKEncodedImageFormat.Png, 100).AsStream();
}
private string GetFirstLetters(string data, int charCount)
{
var parts = data.Split();
if (parts.Length > 1 && charCount <= 2)
{
var text = "";
for (int i = 0; i < charCount; i++)
{
text += parts[i].Substring(0,1);
}
return text;
}
if (data.Length > 2)
{
return data.Substring(0, 2);
}
return data;
}
private Color StringToColor(string str)
{
if (str == null)
{
return Color.FromHex("#33ffffff");
}
var hash = 0;
for (var i = 0; i < str.Length; i++)
{
hash = str[i] + ((hash << 5) - hash);
}
var color = "#FF";
for (var i = 0; i < 3; i++)
{
var value = (hash >> (i * 8)) & 0xff;
var base16 = "00" + Convert.ToString(value, 16);
color += base16.Substring(base16.Length - 2);
}
return Color.FromHex(color);
}
}
}

View File

@@ -12,10 +12,10 @@
x:DataType="controls:CipherViewCellViewModel">
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
<u:IconImageConverter x:Key="iconImageConverter"/>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
<u:IconImageConverter x:Key="iconImageConverter"/>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValueConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
@@ -23,7 +23,7 @@
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="40" x:Name="_iconColumn" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
@@ -35,17 +35,21 @@
StyleClass="list-icon, list-icon-platform"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage
x:Name="_iconImage"
Grid.Column="0"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="Center"
VerticalOptions="Center"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
Margin="9"
WidthRequest="22"
HeightRequest="22"
Aspect="AspectFit"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" />
@@ -112,4 +116,4 @@
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</controls:ExtendedGrid>
</controls:ExtendedGrid>

View File

@@ -1,11 +1,16 @@
using System;
using Bit.App.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class CipherViewCell : ExtendedGrid
{
private const int ICON_COLUMN_DEFAULT_WIDTH = 40;
private const int ICON_IMAGE_DEFAULT_WIDTH = 22;
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
nameof(Cipher), typeof(CipherView), typeof(CipherViewCell), default(CipherView), BindingMode.OneWay);
@@ -18,6 +23,11 @@ namespace Bit.App.Controls
public CipherViewCell()
{
InitializeComponent();
var fontScale = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService").GetSystemFontSizeScale();
_iconColumn.Width = new GridLength(ICON_COLUMN_DEFAULT_WIDTH * fontScale, GridUnitType.Absolute);
_iconImage.WidthRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
_iconImage.HeightRequest = ICON_IMAGE_DEFAULT_WIDTH * fontScale;
}
public bool? WebsiteIconsEnabled

View File

@@ -0,0 +1,29 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedToolbarItem : ToolbarItem
{
public bool UseOriginalImage { get; set; }
// HACK: For the issue of correctly updating the avatar toolbar item color on iOS
// we need to subscribe to the PropertyChanged event of the ToolbarItem on the CustomNavigationRenderer
// The problem is that there are a lot of private places where the navigation renderer disposes objects
// that we don't have access to, and that we should in order to properly prevent memory leaks
// So as a hack solution we have this OnAppearing/OnDisappearing actions and methods to be called on page lifecycle
// to subscribe/unsubscribe indirectly on the CustomNavigationRenderer
public Action OnAppearingAction { get; set; }
public Action OnDisappearingAction { get; set; }
public void OnAppearing()
{
OnAppearingAction?.Invoke();
}
public void OnDisappearing()
{
OnDisappearingAction?.Invoke();
}
}
}

View File

@@ -4,6 +4,8 @@ namespace Bit.App.Controls
{
public class IconLabel : Label
{
public bool ShouldUpdateFontSizeDynamicallyForAccesibility { get; set; }
public IconLabel()
{
switch (Device.RuntimePlatform)

View File

@@ -19,7 +19,7 @@
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="40" x:Name="_iconColumn" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
@@ -31,6 +31,7 @@
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding Send, Converter={StaticResource sendIconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">

View File

@@ -1,5 +1,7 @@
using System;
using Bit.App.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
@@ -18,6 +20,9 @@ namespace Bit.App.Controls
public SendViewCell()
{
InitializeComponent();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_iconColumn.Width = new GridLength(40 * deviceActionService.GetSystemFontSizeScale(), GridUnitType.Absolute);
}
public SendView Send

View File

@@ -0,0 +1,33 @@
using Xamarin.Forms;
namespace Bit.App.Effects
{
public enum ScrollContentInsetAdjustmentBehavior
{
Automatic,
ScrollableAxes,
Never,
Always
}
public class ScrollViewContentInsetAdjustmentBehaviorEffect : RoutingEffect
{
public static readonly BindableProperty ContentInsetAdjustmentBehaviorProperty =
BindableProperty.CreateAttached("ContentInsetAdjustmentBehavior", typeof(ScrollContentInsetAdjustmentBehavior), typeof(ScrollViewContentInsetAdjustmentBehaviorEffect), ScrollContentInsetAdjustmentBehavior.Automatic);
public static ScrollContentInsetAdjustmentBehavior GetContentInsetAdjustmentBehavior(BindableObject view)
{
return (ScrollContentInsetAdjustmentBehavior)view.GetValue(ContentInsetAdjustmentBehaviorProperty);
}
public static void SetContentInsetAdjustmentBehavior(BindableObject view, ScrollContentInsetAdjustmentBehavior value)
{
view.SetValue(ContentInsetAdjustmentBehaviorProperty, value);
}
public ScrollViewContentInsetAdjustmentBehaviorEffect()
: base($"Bitwarden.{nameof(ScrollViewContentInsetAdjustmentBehaviorEffect)}")
{
}
}
}

View File

@@ -22,6 +22,7 @@ namespace Bit.App.Models
public bool IosExtension { get; set; }
public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
public bool CopyInsteadOfShareAfterSaving { get; set; }
public bool HideAccountSwitcher { get; set; }
public void SetAllFrom(AppOptions o)
{
@@ -46,6 +47,7 @@ namespace Bit.App.Models
IosExtension = o.IosExtension;
CreateSend = o.CreateSend;
CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving;
HideAccountSwitcher = o.HideAccountSwitcher;
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Bit.App.Pages
public class BaseChangePasswordViewModel : BaseViewModel
{
protected readonly IPlatformUtilsService _platformUtilsService;
protected readonly IUserService _userService;
protected readonly IStateService _stateService;
protected readonly IPolicyService _policyService;
protected readonly IPasswordGenerationService _passwordGenerationService;
protected readonly II18nService _i18nService;
@@ -31,7 +31,7 @@ namespace Bit.App.Pages
protected BaseChangePasswordViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
@@ -172,7 +172,7 @@ namespace Bit.App.Pages
private async Task<List<string>> GetPasswordStrengthUserInput()
{
var email = await _userService.GetEmailAsync();
var email = await _stateService.GetEmailAsync();
List<string> userInput = null;
var atPosition = email.IndexOf('@');
if (atPosition > -1)

View File

@@ -5,9 +5,6 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
namespace Bit.App.Pages
{
@@ -15,11 +12,13 @@ namespace Bit.App.Pages
{
readonly IPlatformUtilsService _platformUtilsService;
readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
readonly ILogger _logger;
public DeleteAccountViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_verificationActionsFlowHelper = ServiceContainer.Resolve<IVerificationActionsFlowHelper>("verificationActionsFlowHelper");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.DeleteAccount;
}
@@ -44,9 +43,7 @@ namespace Bit.App.Pages
}
catch (System.Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Exception(ex);
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
}
}
@@ -60,16 +57,19 @@ namespace Bit.App.Pages
readonly IMessagingService _messagingService;
readonly IPlatformUtilsService _platformUtilsService;
readonly IDeviceActionService _deviceActionService;
readonly ILogger _logger;
public DeleteAccountActionFlowExecutioner(IApiService apiService,
IMessagingService messagingService,
IPlatformUtilsService platformUtilsService,
IDeviceActionService deviceActionService)
IDeviceActionService deviceActionService,
ILogger logger)
{
_apiService = apiService;
_messagingService = messagingService;
_platformUtilsService = platformUtilsService;
_deviceActionService = deviceActionService;
_logger = logger;
}
public async Task Execute(IActionFlowParmeters parameters)
@@ -102,9 +102,7 @@ namespace Bit.App.Pages
catch (System.Exception ex)
{
await _deviceActionService.HideLoadingAsync();
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Exception(ex);
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
}
}

View File

@@ -10,14 +10,11 @@ namespace Bit.App.Pages
public partial class EnvironmentPage : BaseContentPage
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
private readonly EnvironmentPageViewModel _vm;
public EnvironmentPage()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
InitializeComponent();
_vm = BindingContext as EnvironmentPageViewModel;
_vm.Page = this;
@@ -35,7 +32,6 @@ namespace Bit.App.Pages
_vm.SubmitSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SubmitSuccessAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
}

View File

@@ -6,49 +6,76 @@
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:HomeViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:HomeViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem
Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</ContentPage.ToolbarItems>
<StackLayout Spacing="0" Padding="10, 5">
<controls:IconButton Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
StyleClass="btn-muted, btn-icon, btn-icon-platform"
HorizontalOptions="Start"
Clicked="Environment_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}">
<controls:IconButton.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 10, 0, 0" />
<On Platform="Android" Value="0" />
</OnPlatform>
</controls:IconButton.Margin>
</controls:IconButton>
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
<Image
x:Name="_logo"
Source="logo.png"
VerticalOptions="Center" />
<Label Text="{u:I18n LoginOrCreateNewAccount}"
StyleClass="text-lg"
HorizontalTextAlignment="Center"></Label>
<StackLayout Spacing="5">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked" />
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked" />
<ContentPage.Resources>
<ResourceDictionary>
<StackLayout x:Name="_mainLayout" x:Key="mainLayout" Spacing="0" Padding="10, 5">
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
<Image
x:Name="_logo"
Source="logo.png"
VerticalOptions="Center" />
<Label Text="{u:I18n LoginOrCreateNewAccount}"
StyleClass="text-lg"
HorizontalTextAlignment="Center">
</Label>
<StackLayout Spacing="5">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked" />
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked" />
<Button Text="{u:I18n Cancel}"
IsVisible="{Binding ShowCancelButton}"
Margin="0,10,0,0"
Clicked="Cancel_Clicked" />
</StackLayout>
</StackLayout>
</StackLayout>
</StackLayout>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
MainPage="{Binding Source={x:Reference _page}}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -1,9 +1,9 @@
using Bit.App.Models;
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -12,13 +12,10 @@ namespace Bit.App.Pages
{
private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", false);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent();
@@ -29,6 +26,16 @@ namespace Bit.App.Pages
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
UpdateLogo();
if (_appOptions?.IosExtension ?? false)
{
_vm.ShowCancelButton = true;
}
if (_appOptions?.HideAccountSwitcher ?? false)
{
ToolbarItems.Remove(_accountAvatar);
}
}
public async Task DismissRegisterPageAndLogInAsync(string email)
@@ -37,10 +44,16 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(new LoginPage(email, _appOptions)));
}
protected override void OnAppearing()
protected override async void OnAppearing()
{
base.OnAppearing();
_messagingService.Send("showStatusBar", false);
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
if (!_appOptions?.HideAccountSwitcher ?? false)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
{
if (message.Command == "updatedTheme")
@@ -53,10 +66,21 @@ namespace Bit.App.Pages
});
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(HomePage));
_accountAvatar?.OnDisappearing();
}
private void UpdateLogo()
@@ -64,7 +88,7 @@ namespace Bit.App.Pages
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
}
private void Close_Clicked(object sender, EventArgs e)
private void Cancel_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
@@ -124,6 +148,7 @@ namespace Bit.App.Pages
private async Task StartEnvironmentAsync()
{
await _accountListOverlay.HideAsync();
var page = new EnvironmentPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@@ -1,15 +1,39 @@
using System;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{
public class HomeViewModel : BaseViewModel
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private bool _showCancelButton;
public HomeViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
var logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.Bitwarden;
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, logger)
{
AllowActiveAccountSelection = true
};
}
public bool ShowCancelButton
{
get => _showCancelButton;
set => SetProperty(ref _showCancelButton, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Action StartLoginAction { get; set; }
public Action StartRegisterAction { get; set; }
public Action StartSsoLoginAction { get; set; }

View File

@@ -7,12 +7,26 @@
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LockPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:LockPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
@@ -25,119 +39,136 @@
x:Name="_logOut"
Clicked="LogOut_Clicked"
Order="Secondary"/>
<ScrollView x:Name="_mainLayout" x:Key="mainLayout">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Grid
StyleClass="box-row"
IsVisible="{Binding PinLock}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n PIN}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_pin"
Text="{Binding Pin}"
StyleClass="box-value"
Keyboard="Numeric"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Grid
x:Name="_passwordGrid"
StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout
StyleClass="box-row"
Padding="0, 10, 0, 0">
<Label
Text="{Binding LockedVerifyText}"
StyleClass="box-footer-label" />
<Label
Text="{Binding LoggedInAsText}"
StyleClass="box-footer-label"
Margin="0, 10, 0, 0" />
</StackLayout>
</StackLayout>
<StackLayout Padding="10, 0">
<Label
Text="{u:I18n BiometricInvalidated}"
StyleClass="box-footer-label,text-danger,text-bold"
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
IsVisible="{Binding BiometricButtonVisible}">
</Button>
<Button
x:Name="_unlockButton"
Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
</ContentPage.ToolbarItems>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Grid
StyleClass="box-row"
IsVisible="{Binding PinLock}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n PIN}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_pin"
Text="{Binding Pin}"
StyleClass="box-value"
Keyboard="Numeric"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Grid
x:Name="_passwordGrid"
StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout
StyleClass="box-row"
Padding="0, 10, 0, 0">
<Label
Text="{Binding LockedVerifyText}"
StyleClass="box-footer-label" />
<Label
Text="{Binding LoggedInAsText}"
StyleClass="box-footer-label"
Margin="0, 10, 0, 0" />
</StackLayout>
</StackLayout>
<StackLayout Padding="10, 0">
<Label
Text="{u:I18n BiometricInvalidated}"
StyleClass="box-footer-label,text-danger,text-bold"
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
IsVisible="{Binding BiometricButtonVisible}"></Button>
<Button
x:Name="_unlockButton"
Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
MainPage="{Binding Source={x:Reference _page}}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -1,10 +1,9 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -62,7 +61,14 @@ namespace Bit.App.Pages
{
return;
}
_appeared = true;
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
await _vm.InitAsync();
if (!_vm.BiometricLock)
{
@@ -93,6 +99,23 @@ namespace Bit.App.Pages
}
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_accountAvatar?.OnDisappearing();
}
private void Unlock_Clicked(object sender, EventArgs e)
{
if (DoOnce())
@@ -107,6 +130,7 @@ namespace Bit.App.Pages
private async void LogOut_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
if (DoOnce())
{
await _vm.LogOutAsync();
@@ -123,6 +147,8 @@ namespace Bit.App.Pages
private async void More_Clicked(object sender, System.EventArgs e)
{
await _accountListOverlay.HideAsync();
if (!DoOnce())
{
return;

View File

@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
@@ -10,9 +11,6 @@ using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Xamarin.Forms;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
namespace Bit.App.Pages
{
@@ -23,14 +21,12 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICryptoService _cryptoService;
private readonly IStorageService _storageService;
private readonly IUserService _userService;
private readonly IMessagingService _messagingService;
private readonly IStorageService _secureStorageService;
private readonly IEnvironmentService _environmentService;
private readonly IStateService _stateService;
private readonly IBiometricService _biometricService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly ILogger _logger;
private string _email;
private bool _showPassword;
@@ -42,7 +38,8 @@ namespace Bit.App.Pages
private string _biometricButtonText;
private string _loggedInAsText;
private string _lockedVerifyText;
private Tuple<bool, bool> _pinSet;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
public LockPageViewModel()
{
@@ -51,18 +48,22 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
SubmitCommand = new Command(async () => await SubmitAsync());
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = true,
AllowActiveAccountSelection = true
};
}
public bool ShowPassword
@@ -122,6 +123,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _lockedVerifyText, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
@@ -131,8 +134,9 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
_pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey;
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
// Users with key connector and without biometric or pin has no MP to unlock with
@@ -142,16 +146,14 @@ namespace Bit.App.Pages
await _vaultTimeoutService.LogOutAsync();
return;
}
_email = await _userService.GetEmailAsync();
_email = await _stateService.GetEmailAsync();
if (string.IsNullOrWhiteSpace(_email))
{
await _vaultTimeoutService.LogOutAsync();
#if !FDROID
Crashes.TrackError(new NullReferenceException("Email not found in storage"));
#endif
_logger.Exception(new NullReferenceException("Email not found in storage"));
return;
}
var webVault = _environmentService.GetWebVaultUrl();
var webVault = _environmentService.GetWebVaultUrl(true);
if (string.IsNullOrWhiteSpace(webVault))
{
webVault = "https://bitwarden.com";
@@ -215,21 +217,21 @@ namespace Bit.App.Pages
}
ShowPassword = false;
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
if (PinLock)
{
var failed = true;
try
{
if (_pinSet.Item1)
if (_isPinProtected)
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
_vaultTimeoutService.PinProtectedKey);
await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin;
if (!failed)
@@ -296,14 +298,14 @@ namespace Bit.App.Pages
}
if (passwordValid)
{
if (_pinSet.Item1)
if (_isPinProtected)
{
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
}
MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
@@ -369,7 +371,7 @@ namespace Bit.App.Pages
page.MasterPasswordEntry.Focus();
}
});
_vaultTimeoutService.BiometricLocked = !success;
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{
await DoContinueAsync();
@@ -388,9 +390,7 @@ namespace Bit.App.Pages
private async Task DoContinueAsync()
{
_vaultTimeoutService.BiometricLocked = false;
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
await _stateService.SetBiometricLockedAsync(false);
_messagingService.Send("unlocked");
UnlockedAction?.Invoke();
}

View File

@@ -7,12 +7,26 @@
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LoginPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:LoginPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
@@ -24,69 +38,101 @@
x:Key="getPasswordHint"
x:Name="_getPasswordHint"
Clicked="Hint_Clicked"
Order="Secondary"/>
Order="Secondary" />
<ToolbarItem Text="{u:I18n RemoveAccount}"
x:Key="removeAccount"
x:Name="_removeAccount"
Clicked="RemoveAccount_Clicked"
Order="Secondary" />
<ScrollView x:Name="_mainLayout" x:Key="mainLayout">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n EmailAddress}"
StyleClass="box-label" />
<Entry
x:Name="_email"
Text="{Binding Email}"
Keyboard="Email"
StyleClass="box-value">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{DynamicResource MutedColor}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding LogInCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n Cancel}"
IsVisible="{Binding ShowCancelButton}"
Clicked="Cancel_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n EmailAddress}"
StyleClass="box-label" />
<Entry
x:Name="_email"
Text="{Binding Email}"
Keyboard="Email"
StyleClass="box-value" />
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding LogInCommand}" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
MainPage="{Binding Source={x:Reference _page}}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -1,18 +1,15 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class LoginPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly LoginPageViewModel _vm;
private readonly AppOptions _appOptions;
@@ -20,9 +17,6 @@ namespace Bit.App.Pages
public LoginPage(string email = null, AppOptions appOptions = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as LoginPageViewModel;
@@ -33,15 +27,19 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await _accountListOverlay.HideAsync();
await Navigation.PopModalAsync();
};
if (!string.IsNullOrWhiteSpace(email))
{
_email.IsEnabled = false;
}
else
{
_vm.ShowCancelButton = true;
}
_vm.Email = email;
MasterPasswordEntry = _masterPassword;
if (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
}
_email.ReturnType = ReturnType.Next;
_email.ReturnCommand = new Command(() => _masterPassword.Focus());
@@ -54,6 +52,21 @@ namespace Bit.App.Pages
{
ToolbarItems.Add(_getPasswordHint);
}
if (Device.RuntimePlatform == Device.Android && !_email.IsEnabled)
{
ToolbarItems.Add(_removeAccount);
}
if (_appOptions?.IosExtension ?? false)
{
_vm.ShowCancelButton = true;
}
if (_appOptions?.HideAccountSwitcher ?? false)
{
ToolbarItems.Remove(_accountAvatar);
}
}
public Entry MasterPasswordEntry { get; set; }
@@ -61,6 +74,13 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
if (!_appOptions?.HideAccountSwitcher ?? false)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
await _vm.InitAsync();
if (!_inputFocused)
{
@@ -69,11 +89,28 @@ namespace Bit.App.Pages
}
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_accountAvatar?.OnDisappearing();
}
private async void LogIn_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.LogInAsync();
await _vm.LogInAsync(true, _email.IsEnabled);
}
}
@@ -85,7 +122,16 @@ namespace Bit.App.Pages
}
}
private void Close_Clicked(object sender, EventArgs e)
private async void RemoveAccount_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
if (DoOnce())
{
await _vm.RemoveAccountAsync();
}
}
private void Cancel_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
@@ -95,18 +141,25 @@ namespace Bit.App.Pages
private async void More_Clicked(object sender, System.EventArgs e)
{
await _accountListOverlay.HideAsync();
if (!DoOnce())
{
return;
}
var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, AppResources.GetPasswordHint);
var buttons = _email.IsEnabled ? new[] { AppResources.GetPasswordHint }
: new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount };
var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, buttons);
if (selection == AppResources.GetPasswordHint)
{
await Navigation.PushModalAsync(new NavigationPage(new HintPage()));
}
else if (selection == AppResources.RemoveAccount)
{
await _vm.RemoveAccountAsync();
}
}
private async Task StartTwoFactorAsync()

View File

@@ -1,31 +1,31 @@
using Bit.App.Abstractions;
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LoginPageViewModel : CaptchaProtectedViewModel
{
private const string Keys_RememberedEmail = "rememberedEmail";
private const string Keys_RememberEmail = "rememberEmail";
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private readonly IEnvironmentService _environmentService;
private readonly II18nService _i18nService;
private readonly IMessagingService _messagingService;
private readonly ILogger _logger;
private bool _showPassword;
private bool _showCancelButton;
private string _email;
private string _masterPassword;
@@ -34,15 +34,22 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword);
LogInCommand = new Command(async () => await LogInAsync());
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = true,
AllowActiveAccountSelection = true
};
}
public bool ShowPassword
@@ -55,6 +62,12 @@ namespace Bit.App.Pages
});
}
public bool ShowCancelButton
{
get => _showCancelButton;
set => SetProperty(ref _showCancelButton, value);
}
public string Email
{
get => _email;
@@ -67,10 +80,11 @@ namespace Bit.App.Pages
set => SetProperty(ref _masterPassword, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public bool RememberEmail { get; set; }
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
@@ -85,13 +99,11 @@ namespace Bit.App.Pages
{
if (string.IsNullOrWhiteSpace(Email))
{
Email = await _storageService.GetAsync<string>(Keys_RememberedEmail);
Email = await _stateService.GetRememberedEmailAsync();
}
var rememberEmail = await _storageService.GetAsync<bool?>(Keys_RememberEmail);
RememberEmail = rememberEmail.GetValueOrDefault(true);
}
public async Task LogInAsync(bool showLoading = true)
public async Task LogInAsync(bool showLoading = true, bool checkForExistingAccount = false)
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
@@ -123,20 +135,27 @@ namespace Bit.App.Pages
ShowPassword = false;
try
{
if (checkForExistingAccount)
{
var userId = await _stateService.GetUserIdAsync(Email);
if (!string.IsNullOrWhiteSpace(userId))
{
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
if (userEnvUrls?.Base == _environmentService.BaseUrl)
{
await PromptToSwitchToExistingAccountAsync(userId);
return;
}
}
}
if (showLoading)
{
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
}
var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken);
if (RememberEmail)
{
await _storageService.SaveAsync(Keys_RememberedEmail, Email);
}
else
{
await _storageService.RemoveAsync(Keys_RememberedEmail);
}
await _stateService.SetRememberedEmailAsync(Email);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (response.CaptchaNeeded)
@@ -163,8 +182,6 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
LogInSuccessAction?.Invoke();
}
@@ -189,5 +206,34 @@ namespace Bit.App.Pages
entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
}
public async Task RemoveAccountAsync()
{
try
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.RemoveAccountConfirmation,
AppResources.RemoveAccount, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
_messagingService.Send("logout");
}
}
catch (Exception e)
{
_logger.Exception(e);
}
}
private async Task PromptToSwitchToExistingAccountAsync(string userId)
{
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
AppResources.SwitchToAlreadyAddedAccountConfirmation,
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
if (switchToAccount)
{
await _stateService.SetActiveUserAsync(userId);
_messagingService.Send("switchedAccount");
}
}
}
}

View File

@@ -10,8 +10,6 @@ namespace Bit.App.Pages
{
public partial class LoginSsoPage : BaseContentPage
{
private readonly IStorageService _storageService;
private readonly IMessagingService _messagingService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly LoginSsoPageViewModel _vm;
private readonly AppOptions _appOptions;
@@ -20,10 +18,7 @@ namespace Bit.App.Pages
public LoginSsoPage(AppOptions appOptions = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as LoginSsoPageViewModel;
@@ -36,7 +31,6 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
if (Device.RuntimePlatform == Device.Android)

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
@@ -16,16 +15,12 @@ namespace Bit.App.Pages
{
public class LoginSsoPageViewModel : BaseViewModel
{
private const string Keys_RememberedOrgIdentifier = "rememberedOrgIdentifier";
private const string Keys_RememberOrgIdentifier = "rememberOrgIdentifier";
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IApiService _apiService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
@@ -40,7 +35,6 @@ namespace Bit.App.Pages
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>("cryptoFunctionService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
@@ -55,7 +49,6 @@ namespace Bit.App.Pages
}
public Command LogInCommand { get; }
public bool RememberOrgIdentifier { get; set; }
public Action StartTwoFactorAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; }
@@ -66,10 +59,8 @@ namespace Bit.App.Pages
{
if (string.IsNullOrWhiteSpace(OrgIdentifier))
{
OrgIdentifier = await _storageService.GetAsync<string>(Keys_RememberedOrgIdentifier);
OrgIdentifier = await _stateService.GetRememberedOrgIdentifierAsync();
}
var rememberOrgIdentifier = await _storageService.GetAsync<bool?>(Keys_RememberOrgIdentifier);
RememberOrgIdentifier = rememberOrgIdentifier.GetValueOrDefault(true);
}
public async Task LogInAsync()
@@ -170,14 +161,7 @@ namespace Bit.App.Pages
{
var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri, orgId);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (RememberOrgIdentifier)
{
await _storageService.SaveAsync(Keys_RememberedOrgIdentifier, OrgIdentifier);
}
else
{
await _storageService.RemoveAsync(Keys_RememberedOrgIdentifier);
}
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
await _deviceActionService.HideLoadingAsync();
if (response.TwoFactor)
{
@@ -193,8 +177,6 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
SsoAuthSuccessAction?.Invoke();
}

View File

@@ -1,6 +1,4 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
@@ -8,22 +6,18 @@ namespace Bit.App.Pages
{
public partial class RegisterPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly RegisterPageViewModel _vm;
private bool _inputFocused;
public RegisterPage(HomePage homePage)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
InitializeComponent();
_vm = BindingContext as RegisterPageViewModel;
_vm.Page = this;
_vm.RegistrationSuccess = () => Device.BeginInvokeOnMainThread(async () => await RegistrationSuccessAsync(homePage));
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
MasterPasswordEntry = _masterPassword;

View File

@@ -1,6 +1,4 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
@@ -10,14 +8,11 @@ namespace Bit.App.Pages
{
public partial class SetPasswordPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly SetPasswordPageViewModel _vm;
private readonly AppOptions _appOptions;
public SetPasswordPage(AppOptions appOptions = null, string orgIdentifier = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as SetPasswordPageViewModel;
@@ -26,7 +21,6 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await SetPasswordSuccessAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
_vm.OrgIdentifier = orgIdentifier;

View File

@@ -7,7 +7,6 @@ using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -24,7 +23,7 @@ namespace Bit.App.Pages
private readonly IApiService _apiService;
private readonly ICryptoService _cryptoService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IPolicyService _policyService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly II18nService _i18nService;
@@ -41,7 +40,7 @@ namespace Bit.App.Pages
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
@@ -160,7 +159,7 @@ namespace Bit.App.Pages
var kdf = KdfType.PBKDF2_SHA256;
var kdfIterations = 100000;
var email = await _userService.GetEmailAsync();
var email = await _stateService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
@@ -197,8 +196,8 @@ namespace Bit.App.Pages
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
// Set Password and relevant information
await _apiService.SetPasswordAsync(request);
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
await _userService.GetEmailAsync(), kdf, kdfIterations);
await _stateService.SetKdfTypeAsync(kdf);
await _stateService.SetKdfIterationsAsync(kdfIterations);
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
@@ -217,7 +216,7 @@ namespace Bit.App.Pages
{
ResetPasswordKey = encryptedKey.EncryptedString
};
var userId = await _userService.GetUserIdAsync();
var userId = await _stateService.GetActiveUserIdAsync();
// Enroll user
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest);
}
@@ -290,7 +289,7 @@ namespace Bit.App.Pages
private async Task<List<string>> GetPasswordStrengthUserInput()
{
var email = await _userService.GetEmailAsync();
var email = await _stateService.GetEmailAsync();
List<string> userInput = null;
var atPosition = email.IndexOf('@');
if (atPosition > -1)

View File

@@ -14,8 +14,6 @@ namespace Bit.App.Pages
{
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly AppOptions _appOptions;
private TwoFactorPageViewModel _vm;
@@ -30,10 +28,8 @@ namespace Bit.App.Pages
_authingWithSso = authingWithSso ?? false;
_appOptions = appOptions;
_orgIdentifier = orgIdentifier;
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_vm = BindingContext as TwoFactorPageViewModel;
_vm.Page = this;
_vm.StartSetPasswordAction = () =>

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@@ -18,18 +17,18 @@ using Xamarin.Forms;
namespace Bit.App.Pages
{
public class TwoFactorPageViewModel : BaseViewModel
public class TwoFactorPageViewModel : CaptchaProtectedViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IStorageService _storageService;
private readonly IApiService _apiService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IEnvironmentService _environmentService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly IStateService _stateService;
private readonly II18nService _i18nService;
private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction;
@@ -43,13 +42,13 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
@@ -114,6 +113,11 @@ namespace Bit.App.Pages
public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService;
protected override IDeviceActionService deviceActionService => _deviceActionService;
protected override IPlatformUtilsService platformUtilsService => _platformUtilsService;
public void Init()
{
@@ -288,11 +292,24 @@ namespace Bit.App.Pages
{
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
}
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, _captchaToken, Remember);
if (result.CaptchaNeeded)
{
if (await HandleCaptchaAsync(result.CaptchaSiteKey))
{
await SubmitAsync(false);
_captchaToken = null;
}
return;
}
_captchaToken = null;
var task = Task.Run(() => _syncService.FullSyncAsync(true));
await _deviceActionService.HideLoadingAsync();
_messagingService.Send("listenYubiKeyOTP", false);
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
if (_authingWithSso && result.ResetMasterPassword)
{
StartSetPasswordAction?.Invoke();
@@ -303,13 +320,12 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
TwoFactorAuthSuccessAction?.Invoke();
}
}
catch (ApiException e)
{
_captchaToken = null;
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{

View File

@@ -19,9 +19,6 @@ namespace Bit.App.Pages
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
// Service Use
_messagingService.Send("showStatusBar", true);
// Binding
InitializeComponent();
_pageName = string.Concat(nameof(UpdateTempPasswordPage), "_", DateTime.UtcNow.Ticks);

View File

@@ -43,9 +43,9 @@ namespace Bit.App.Pages
}
// Retrieve details for key generation
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _stateService.GetEmailAsync();
// Create new key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);

View File

@@ -1,19 +1,16 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Threading.Tasks;
using Bit.Core.Exceptions;
using Xamarin.Forms;
using Xamarin.CommunityToolkit.ObjectModel;
using System.Windows.Input;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -24,6 +21,7 @@ namespace Bit.App.Pages
private readonly IUserVerificationService _userVerificationService;
private readonly IApiService _apiService;
private readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
private readonly ILogger _logger;
private bool _showPassword;
private string _secret, _mainActionText, _sendCodeStatus;
@@ -35,6 +33,7 @@ namespace Bit.App.Pages
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_verificationActionsFlowHelper = ServiceContainer.Resolve<IVerificationActionsFlowHelper>("verificationActionsFlowHelper");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.VerificationCode;
@@ -118,9 +117,7 @@ namespace Bit.App.Pages
}
catch (Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Exception(ex);
await _deviceActionService.HideLoadingAsync();
SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
}
@@ -171,9 +168,7 @@ namespace Bit.App.Pages
}
catch (Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Exception(ex);
await _deviceActionService.HideLoadingAsync();
}
}

View File

@@ -1,10 +1,10 @@
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@@ -13,7 +13,7 @@ namespace Bit.App.Pages
{
public class BaseContentPage : ContentPage
{
private IStorageService _storageService;
private IStateService _stateService;
private IDeviceActionService _deviceActionService;
protected int ShowModalAnimationDelay = 400;
@@ -32,16 +32,16 @@ namespace Bit.App.Pages
public bool IsThemeDirty { get; set; }
protected override void OnAppearing()
protected async override void OnAppearing()
{
base.OnAppearing();
if (IsThemeDirty)
{
UpdateOnThemeChanged();
}
SaveActivity();
await SaveActivityAsync();
}
public bool DoOnce(Action action = null, int milliseconds = 1000)
@@ -56,6 +56,12 @@ namespace Bit.App.Pages
return true;
}
public virtual Task UpdateOnThemeChanged()
{
IsThemeDirty = false;
return Task.CompletedTask;
}
protected void SetActivityIndicator(ContentView targetView = null)
{
var indicator = new ActivityIndicator
@@ -114,11 +120,25 @@ namespace Bit.App.Pages
});
}
protected async Task<bool> ShowAccountSwitcherAsync()
{
return await _stateService.GetActiveUserIdAsync() != null;
}
protected async Task<AvatarImageSource> GetAvatarImageSourceAsync(bool useCurrentActiveAccount = true)
{
if (useCurrentActiveAccount)
{
return new AvatarImageSource(await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
}
return new AvatarImageSource();
}
private void SetServices()
{
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
if (_deviceActionService == null)
{
@@ -126,16 +146,10 @@ namespace Bit.App.Pages
}
}
private void SaveActivity()
private async Task SaveActivityAsync()
{
SetServices();
_storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime());
}
public virtual Task UpdateOnThemeChanged()
{
IsThemeDirty = false;
return Task.CompletedTask;
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Utilities;
using Bit.App.Controls;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -6,6 +7,7 @@ namespace Bit.App.Pages
public abstract class BaseViewModel : ExtendedViewModel
{
private string _pageTitle = string.Empty;
private AvatarImageSource _avatar;
public string PageTitle
{
@@ -13,6 +15,12 @@ namespace Bit.App.Pages
set => SetProperty(ref _pageTitle, value);
}
public AvatarImageSource AvatarImageSource
{
get => _avatar ?? new AvatarImageSource();
set => SetProperty(ref _avatar, value);
}
public ContentPage Page { get; set; }
}
}

View File

@@ -27,13 +27,11 @@ namespace Bit.App.Pages
captchaRequiredText = AppResources.CaptchaRequired,
});
var url = environmentService.GetWebVaultUrl();
if (url == null)
{
url = "https://vault.bitwarden.com";
}
url += "/captcha-mobile-connector.html?" + "data=" + data +
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=1";
var url = environmentService.GetWebVaultUrl() +
"/captcha-mobile-connector.html?" +
"data=" + data +
"&parent=" + Uri.EscapeDataString(callbackUri) +
"&v=1";
WebAuthenticatorResult authResult = null;
bool cancelled = false;

View File

@@ -4,9 +4,6 @@ using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -16,6 +13,7 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IClipboardService _clipboardService;
private readonly ILogger _logger;
private bool _showNoData;
@@ -24,6 +22,7 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.PasswordHistory;
History = new ExtendedObservableCollection<GeneratedPasswordHistory>();
@@ -70,9 +69,7 @@ namespace Bit.App.Pages
}
catch (System.Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Exception(ex);
}
}
}

View File

@@ -36,251 +36,255 @@
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView Padding="0, 0, 0, 20">
<StackLayout Spacing="0" Padding="0">
<StackLayout StyleClass="box">
<Grid IsVisible="{Binding IsPolicyInEffect}"
Margin="0, 12, 0, 0"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n PasswordGeneratorPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
</Grid>
<controls:MonoLabel
x:Name="lblPassword"
StyleClass="text-lg, text-html"
Text="{Binding ColoredPassword, Mode=OneWay}"
Margin="0, 20"
HorizontalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
LineBreakMode="CharacterWrap" />
<Button Text="{u:I18n RegeneratePassword}"
StyleClass="btn-primary"
HorizontalOptions="FillAndExpand"
Clicked="Regenerate_Clicked"></Button>
<Button Text="{u:I18n CopyPassword}"
HorizontalOptions="FillAndExpand"
Clicked="Copy_Clicked"></Button>
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Options, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
<Label
Text="{u:I18n Type}"
StyleClass="box-label" />
<Picker
x:Name="_typePicker"
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
SelectedIndex="{Binding TypeSelectedIndex}"
StyleClass="box-value" />
</StackLayout>
<StackLayout Spacing="0"
Padding="0"
IsVisible="{Binding IsPassword, Converter={StaticResource inverseBool}}">
<StackLayout StyleClass="box-row, box-row-stepper">
<Label
Text="{u:I18n NumberOfWords}"
StyleClass="box-label-regular"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Label
Text="{Binding NumWords}"
StyleClass="box-sub-label"
<!--WORKAROUND: Wrapped in a ContentView to fix bottom screen but when using large font size on Android.
Check when https://github.com/xamarin/Xamarin.Forms/pull/15076 is released that may fix this without wrapping
in ContentView.-->
<ContentView>
<ScrollView Padding="0, 0, 0, 20">
<StackLayout Spacing="0" Padding="0">
<StackLayout StyleClass="box">
<Grid IsVisible="{Binding IsPolicyInEffect}"
Margin="0, 12, 0, 0"
RowSpacing="0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Frame Padding="10"
Margin="0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n PasswordGeneratorPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
</Grid>
<controls:MonoLabel
x:Name="lblPassword"
StyleClass="text-lg, text-html"
Text="{Binding ColoredPassword, Mode=OneWay}"
Margin="0, 20"
HorizontalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
LineBreakMode="CharacterWrap" />
<Button Text="{u:I18n RegeneratePassword}"
StyleClass="btn-primary"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
Value="{Binding NumWords}"
Maximum="20"
Minimum="3"
Increment="1" />
Clicked="Regenerate_Clicked"></Button>
<Button Text="{u:I18n CopyPassword}"
HorizontalOptions="FillAndExpand"
Clicked="Copy_Clicked"></Button>
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Options, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-input">
<Label
Text="{u:I18n WordSeparator}"
Text="{u:I18n Type}"
StyleClass="box-label" />
<Entry
Text="{Binding WordSeparator}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
<Picker
x:Name="_typePicker"
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
SelectedIndex="{Binding TypeSelectedIndex}"
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n Capitalize}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Capitalize}"
IsEnabled="{Binding EnforcedPolicyOptions.Capitalize,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
<StackLayout Spacing="0"
Padding="0"
IsVisible="{Binding IsPassword, Converter={StaticResource inverseBool}}">
<StackLayout StyleClass="box-row, box-row-stepper">
<Label
Text="{u:I18n NumberOfWords}"
StyleClass="box-label-regular"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Label
Text="{Binding NumWords}"
StyleClass="box-sub-label"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
Value="{Binding NumWords}"
Maximum="20"
Minimum="3"
Increment="1" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-input">
<Label
Text="{u:I18n WordSeparator}"
StyleClass="box-label" />
<Entry
Text="{Binding WordSeparator}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n Capitalize}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Capitalize}"
IsEnabled="{Binding EnforcedPolicyOptions.Capitalize,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n IncludeNumber}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding IncludeNumber}"
IsEnabled="{Binding EnforcedPolicyOptions.IncludeNumber,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n IncludeNumber}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding IncludeNumber}"
IsEnabled="{Binding EnforcedPolicyOptions.IncludeNumber,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding IsPassword}">
<StackLayout StyleClass="box-row, box-row-slider">
<Label
Text="{u:I18n Length}"
StyleClass="box-label-regular"
VerticalOptions="CenterAndExpand" />
<Label
Text="{Binding Length}"
StyleClass="box-sub-label"
VerticalOptions="CenterAndExpand"
HorizontalTextAlignment="End"
WidthRequest="50" />
<controls:ExtendedSlider
DragCompleted="LengthSlider_DragCompleted"
Value="{Binding Length}"
AutomationProperties.HelpText="{Binding Length}"
StyleClass="box-value"
VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand"
Maximum="128"
Minimum="5" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="A-Z"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Uppercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="a-z"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Lowercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="0-9"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Number}"
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="!@#$%^&amp;*"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Special}"
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper">
<Label
Text="{u:I18n MinNumbers}"
StyleClass="box-label-regular"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Label
Text="{Binding MinNumber}"
StyleClass="box-sub-label"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
Value="{Binding MinNumber}"
Maximum="5"
Minimum="0"
Increment="1" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper">
<Label
Text="{u:I18n MinSpecial}"
StyleClass="box-label-regular"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Label
Text="{Binding MinSpecial}"
StyleClass="box-sub-label"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
Value="{Binding MinSpecial}"
Maximum="5"
Minimum="0"
Increment="1" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n AvoidAmbiguousCharacters}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding AvoidAmbiguous}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
</StackLayout>
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding IsPassword}">
<StackLayout StyleClass="box-row, box-row-slider">
<Label
Text="{u:I18n Length}"
StyleClass="box-label-regular"
VerticalOptions="CenterAndExpand" />
<Label
Text="{Binding Length}"
StyleClass="box-sub-label"
WidthRequest="30"
VerticalOptions="CenterAndExpand"
HorizontalTextAlignment="End" />
<controls:ExtendedSlider
DragCompleted="LengthSlider_DragCompleted"
Value="{Binding Length}"
AutomationProperties.HelpText="{Binding Length}"
StyleClass="box-value"
VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand"
Maximum="128"
Minimum="5" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="A-Z"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Uppercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseUppercase,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="a-z"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Lowercase}"
IsEnabled="{Binding EnforcedPolicyOptions.UseLowercase,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="0-9"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Number}"
IsEnabled="{Binding EnforcedPolicyOptions.UseNumbers,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="!@#$%^&amp;*"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Special}"
IsEnabled="{Binding EnforcedPolicyOptions.UseSpecial,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper">
<Label
Text="{u:I18n MinNumbers}"
StyleClass="box-label-regular"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Label
Text="{Binding MinNumber}"
StyleClass="box-sub-label"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
Value="{Binding MinNumber}"
Maximum="5"
Minimum="0"
Increment="1" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper">
<Label
Text="{u:I18n MinSpecial}"
StyleClass="box-label-regular"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<Label
Text="{Binding MinSpecial}"
StyleClass="box-sub-label"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<controls:ExtendedStepper
Value="{Binding MinSpecial}"
Maximum="5"
Minimum="0"
Increment="1" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n AvoidAmbiguousCharacters}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding AvoidAmbiguous}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
</StackLayout>
</StackLayout>
</ScrollView>
</ScrollView>
</ContentView>
</pages:BaseContentPage>

View File

@@ -7,9 +7,6 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@@ -21,6 +18,7 @@ namespace Bit.App.Pages
{
private readonly IBroadcasterService _broadcasterService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private AppOptions _appOptions;
private SendAddEditPageViewModel _vm;
@@ -55,9 +53,10 @@ namespace Bit.App.Pages
_vm.SegmentedButtonFontSize = 13;
_vm.SegmentedButtonMargins = new Thickness(0, 10, 0, 0);
_vm.EditorMargins = new Thickness(0, 5, 0, 0);
_btnOptions.WidthRequest = 70;
_btnOptionsDown.WidthRequest = 30;
_btnOptionsUp.WidthRequest = 30;
// Review this when https://github.com/bitwarden/mobile/pull/1454 workaround can be reverted
//_btnOptions.WidthRequest = 70;
//_btnOptionsDown.WidthRequest = 30;
//_btnOptionsUp.WidthRequest = 30;
}
else if (Device.RuntimePlatform == Device.iOS)
{
@@ -131,9 +130,7 @@ namespace Bit.App.Pages
}
catch (Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Value.Exception(ex);
await CloseAsync();
}
}

View File

@@ -10,9 +10,6 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using Xamarin.Essentials;
using Xamarin.Forms;
@@ -23,8 +20,9 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly ISendService _sendService;
private readonly ILogger _logger;
private bool _sendEnabled;
private bool _canAccessPremium;
private bool _emailVerified;
@@ -56,8 +54,10 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_sendService = ServiceContainer.Resolve<ISendService>("sendService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
TogglePasswordCommand = new Command(TogglePassword);
TypeOptions = new List<KeyValuePair<string, SendType>>
@@ -235,8 +235,8 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
_canAccessPremium = await _userService.CanAccessPremiumAsync();
_emailVerified = await _userService.GetEmailVerifiedAsync();
_canAccessPremium = await _stateService.CanAccessPremiumAsync();
_emailVerified = await _stateService.GetEmailVerifiedAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync();
SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail;
@@ -455,9 +455,7 @@ namespace Bit.App.Pages
catch (Exception ex)
{
await _deviceActionService.HideLoadingAsync();
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Exception(ex);
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
}
return false;

View File

@@ -54,7 +54,8 @@
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform">
StyleClass="list-icon, list-icon-platform"
ShouldUpdateFontSizeDynamicallyForAccesibility="True">
<controls:IconLabel.Effects>
<effects:FixedSizeEffect />
</controls:IconLabel.Effects>

View File

@@ -33,21 +33,19 @@ namespace Bit.App.Pages
private readonly ISendService _sendService;
private readonly ISyncService _syncService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStorageService _storageService;
public SendGroupingsPageViewModel()
{
_sendService = ServiceContainer.Resolve<ISendService>("sendService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
Loading = true;
PageTitle = AppResources.Send;
@@ -117,7 +115,7 @@ namespace Bit.App.Pages
{
return;
}
var authed = await _userService.IsAuthenticatedAsync();
var authed = await _stateService.IsAuthenticatedAsync();
if (!authed)
{
return;
@@ -126,7 +124,7 @@ namespace Bit.App.Pages
{
return;
}
if (await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey) && Refreshing && !SyncRefreshing)
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
{
SyncRefreshing = true;
await _syncService.FullSyncAsync(false);

View File

@@ -2,7 +2,6 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Services;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
@@ -11,7 +10,7 @@ namespace Bit.App.Pages
public class AutofillServicesPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly MobileI18nService _i18nService;
private bool _autofillServiceToggled;
@@ -23,7 +22,7 @@ namespace Bit.App.Pages
public AutofillServicesPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
PageTitle = AppResources.AutofillServices;
}
@@ -152,7 +151,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
InlineAutofillToggled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
InlineAutofillToggled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
_inited = true;
}
@@ -202,7 +201,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.InlineAutofillEnabledKey, InlineAutofillToggled);
await _stateService.SetInlineAutofillEnabledAsync(InlineAutofillToggled);
}
}
}

View File

@@ -1,17 +1,14 @@
using System;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using Bit.Core;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -26,6 +23,7 @@ namespace Bit.App.Pages
private readonly IKeyConnectorService _keyConnectorService;
private readonly IUserVerificationService _userVerificationService;
private readonly IApiService _apiService;
private readonly ILogger _logger;
private int _fileFormatSelectedIndex;
private string _exportWarningMessage;
@@ -48,6 +46,7 @@ namespace Bit.App.Pages
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.ExportVault;
TogglePasswordCommand = new Command(TogglePassword);
@@ -189,9 +188,7 @@ namespace Bit.App.Pages
ClearResult();
await _platformUtilsService.ShowDialogAsync(_i18nService.T("ExportVaultFailure"));
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Exception(ex);
}
}

View File

@@ -7,12 +7,7 @@ namespace Bit.App.Pages
{
public class ExtensionPageViewModel : BaseViewModel
{
private const string StartedKey = "appExtensionStarted";
private const string ActivatedKey = "appExtensionActivated";
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private bool _started;
private bool _activated;
@@ -20,8 +15,6 @@ namespace Bit.App.Pages
public ExtensionPageViewModel()
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
PageTitle = AppResources.AppExtension;
}
@@ -52,10 +45,8 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
var started = await _storageService.GetAsync<bool?>(StartedKey);
var activated = await _storageService.GetAsync<bool?>(ActivatedKey);
Started = started.GetValueOrDefault();
Activated = activated.GetValueOrDefault();
Started = false;
Activated = false;
}
public void ShowExtension()

View File

@@ -1,5 +1,4 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
@@ -13,9 +12,6 @@ namespace Bit.App.Pages
{
public class OptionsPageViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStorageService _storageService;
private readonly ITotpService _totpService;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
@@ -34,9 +30,6 @@ namespace Bit.App.Pages
public OptionsPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@@ -166,19 +159,17 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
AutofillDisableSavePrompt = (await _storageService.GetAsync<bool?>(
Constants.AutofillDisableSavePromptKey)).GetValueOrDefault();
var blacklistedUrisList = await _storageService.GetAsync<List<string>>(
Constants.AutofillBlacklistedUrisKey);
AutofillDisableSavePrompt = (await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
var blacklistedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
AutofillBlacklistedUris = blacklistedUrisList != null ? string.Join(", ", blacklistedUrisList) : null;
DisableAutoTotpCopy = !(await _totpService.IsAutoCopyEnabledAsync());
DisableFavicon = (await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey)).GetValueOrDefault();
var theme = await _storageService.GetAsync<string>(Constants.ThemeKey);
DisableFavicon = (await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var theme = await _stateService.GetThemeAsync();
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
var defaultUriMatch = await _storageService.GetAsync<int?>(Constants.DefaultUriMatch);
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
var clearClipboard = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
var clearClipboard = await _stateService.GetClearClipboardAsync();
ClearClipboardSelectedIndex = ClearClipboardOptions.FindIndex(k => k.Key == clearClipboard);
_inited = true;
}
@@ -187,7 +178,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.DisableAutoTotpCopyKey, DisableAutoTotpCopy);
await _stateService.SetDisableAutoTotpCopyAsync(DisableAutoTotpCopy);
}
}
@@ -195,8 +186,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.DisableFaviconKey, DisableFavicon);
await _stateService.SaveAsync(Constants.DisableFaviconKey, DisableFavicon);
await _stateService.SetDisableFaviconAsync(DisableFavicon);
}
}
@@ -204,8 +194,7 @@ namespace Bit.App.Pages
{
if (_inited && ClearClipboardSelectedIndex > -1)
{
await _storageService.SaveAsync(Constants.ClearClipboardKey,
ClearClipboardOptions[ClearClipboardSelectedIndex].Key);
await _stateService.SetClearClipboardAsync(ClearClipboardOptions[ClearClipboardSelectedIndex].Key);
}
}
@@ -214,8 +203,8 @@ namespace Bit.App.Pages
if (_inited && ThemeSelectedIndex > -1)
{
var theme = ThemeOptions[ThemeSelectedIndex].Key;
await _storageService.SaveAsync(Constants.ThemeKey, theme);
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Application.Current.Resources);
await _stateService.SetThemeAsync(theme);
ThemeManager.SetTheme(Application.Current.Resources);
_messagingService.Send("updatedTheme");
}
}
@@ -224,8 +213,7 @@ namespace Bit.App.Pages
{
if (_inited && UriMatchSelectedIndex > -1)
{
await _storageService.SaveAsync(Constants.DefaultUriMatch,
(int?)UriMatchOptions[UriMatchSelectedIndex].Key);
await _stateService.SetDefaultUriMatchAsync((int?)UriMatchOptions[UriMatchSelectedIndex].Key);
}
}
@@ -233,7 +221,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.AutofillDisableSavePromptKey, AutofillDisableSavePrompt);
await _stateService.SetAutofillDisableSavePromptAsync(AutofillDisableSavePrompt);
}
}
@@ -243,7 +231,7 @@ namespace Bit.App.Pages
{
if (string.IsNullOrWhiteSpace(AutofillBlacklistedUris))
{
await _storageService.RemoveAsync(Constants.AutofillBlacklistedUrisKey);
await _stateService.SetAutofillBlacklistedUrisAsync(null);
AutofillBlacklistedUris = null;
return;
}
@@ -265,7 +253,7 @@ namespace Bit.App.Pages
}
urisList.Add(cleanedUri);
}
await _storageService.SaveAsync(Constants.AutofillBlacklistedUrisKey, urisList);
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
AutofillBlacklistedUris = string.Join(", ", urisList);
}
catch { }

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
@@ -19,12 +18,11 @@ namespace Bit.App.Pages
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICryptoService _cryptoService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService;
private readonly IEnvironmentService _environmentService;
private readonly IMessagingService _messagingService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IStorageService _storageService;
private readonly ISyncService _syncService;
private readonly IBiometricService _biometricService;
private readonly IPolicyService _policyService;
@@ -56,11 +54,11 @@ namespace Bit.App.Pages
new KeyValuePair<string, int?>(AppResources.Never, null),
new KeyValuePair<string, int?>(AppResources.Custom, CustomVaultTimeoutValue),
};
private List<KeyValuePair<string, string>> _vaultTimeoutActions =
new List<KeyValuePair<string, string>>
private List<KeyValuePair<string, VaultTimeoutAction>> _vaultTimeoutActions =
new List<KeyValuePair<string, VaultTimeoutAction>>
{
new KeyValuePair<string, string>(AppResources.Lock, "lock"),
new KeyValuePair<string, string>(AppResources.LogOut, "logOut"),
new KeyValuePair<string, VaultTimeoutAction>(AppResources.Lock, VaultTimeoutAction.Lock),
new KeyValuePair<string, VaultTimeoutAction>(AppResources.LogOut, VaultTimeoutAction.Logout),
};
private Policy _vaultTimeoutPolicy;
@@ -70,12 +68,11 @@ namespace Bit.App.Pages
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
@@ -113,7 +110,7 @@ namespace Bit.App.Pages
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey) ?? "lock";
var action = await _stateService.GetVaultTimeoutActionAsync() ?? VaultTimeoutAction.Lock;
_vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2;
@@ -137,10 +134,10 @@ namespace Bit.App.Pages
#if DEBUG
var pushNotificationsRegistered = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService").IsRegisteredForPush;
var pnServerRegDate = await _storageService.GetAsync<DateTime>(Constants.PushLastRegistrationDateKey);
var pnServerError = await _storageService.GetAsync<string>(Constants.PushInstallationRegistrationError);
var pnServerRegDate = await _stateService.GetPushLastRegistrationDateAsync();
var pnServerError = await _stateService.GetPushInstallationRegistrationErrorAsync();
var pnServerRegDateMessage = default(DateTime) == pnServerRegDate ? "-" : $"{pnServerRegDate.ToShortDateString()}-{pnServerRegDate.ToShortTimeString()} UTC";
var pnServerRegDateMessage = default(DateTime) == pnServerRegDate ? "-" : $"{pnServerRegDate.GetValueOrDefault().ToShortDateString()}-{pnServerRegDate.GetValueOrDefault().ToShortTimeString()} UTC";
var errorMessage = string.IsNullOrEmpty(pnServerError) ? string.Empty : $"Push Notifications Server Registration error: {pnServerError}";
var text = string.Format("© Bitwarden Inc. 2015-{0}\n\n{1}\nPush Notifications registered:{2}\nPush Notifications Server Last Date :{3}\n{4}", DateTime.Now.Year, debugText, pushNotificationsRegistered, pnServerRegDateMessage, errorMessage);
@@ -166,7 +163,7 @@ namespace Bit.App.Pages
List<string> fingerprint;
try
{
fingerprint = await _cryptoService.GetFingerprintAsync(await _userService.GetUserIdAsync());
fingerprint = await _cryptoService.GetFingerprintAsync(await _stateService.GetActiveUserIdAsync());
}
catch (Exception e) when (e.Message == "No public key available.")
{
@@ -194,12 +191,7 @@ namespace Bit.App.Pages
public void WebVault()
{
var url = _environmentService.GetWebVaultUrl();
if (url == null)
{
url = "https://vault.bitwarden.com";
}
_platformUtilsService.LaunchUri(url);
_platformUtilsService.LaunchUri(_environmentService.GetWebVaultUrl());
}
public async Task ShareAsync()
@@ -218,7 +210,7 @@ namespace Bit.App.Pages
AppResources.TwoStepLogin, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
_platformUtilsService.LaunchUri("https://bitwarden.com/help/setup-two-step-login/");
_platformUtilsService.LaunchUri($"{_environmentService.GetWebVaultUrl()}/#/settings");
}
}
@@ -228,7 +220,7 @@ namespace Bit.App.Pages
AppResources.ChangeMasterPassword, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
_platformUtilsService.LaunchUri("https://bitwarden.com/help/master-password/#change-your-master-password");
_platformUtilsService.LaunchUri($"{_environmentService.GetWebVaultUrl()}/#/settings");
}
}
@@ -344,9 +336,9 @@ namespace Bit.App.Pages
AppResources.Yes, AppResources.No);
}
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
@@ -356,12 +348,12 @@ namespace Bit.App.Pages
if (masterPassOnRestart)
{
var encPin = await _cryptoService.EncryptAsync(pin);
await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString);
_vaultTimeoutService.PinProtectedKey = pinProtectedKey;
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
}
else
{
await _storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString);
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
}
}
else
@@ -396,13 +388,13 @@ namespace Bit.App.Pages
if (_biometric)
{
await _biometricService.SetupBiometricAsync();
await _storageService.SaveAsync(Constants.BiometricUnlockKey, true);
await _stateService.SetBiometricUnlockAsync(true);
}
else
{
await _storageService.RemoveAsync(Constants.BiometricUnlockKey);
await _stateService.SetBiometricUnlockAsync(null);
}
_vaultTimeoutService.BiometricLocked = false;
await _stateService.SetBiometricLockedAsync(false);
await _cryptoService.ToggleKeyAsync();
BuildList();
}
@@ -575,7 +567,7 @@ namespace Bit.App.Pages
return true;
}
private string GetVaultTimeoutActionFromKey(string key)
private VaultTimeoutAction GetVaultTimeoutActionFromKey(string key)
{
return _vaultTimeoutActions.FirstOrDefault(o => o.Key == key).Value;
}

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
@@ -12,7 +11,7 @@ namespace Bit.App.Pages
{
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly ISyncService _syncService;
private readonly ILocalizeService _localizeService;
@@ -24,7 +23,7 @@ namespace Bit.App.Pages
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
@@ -52,7 +51,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
await SetLastSyncAsync();
EnableSyncOnRefresh = await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey);
EnableSyncOnRefresh = await _stateService.GetSyncOnRefreshAsync();
_inited = true;
}
@@ -60,7 +59,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.SyncOnRefreshKey, _syncOnRefresh);
await _stateService.SetSyncOnRefreshAsync(_syncOnRefresh);
}
}

View File

@@ -2,6 +2,7 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -45,7 +46,7 @@ namespace Bit.App.Pages
var settingsPage = new NavigationPage(new SettingsPage(this))
{
Title = AppResources.Settings,
IconImageSource = "cog.png"
IconImageSource = "cog_settings.png"
};
Children.Add(settingsPage);
@@ -102,8 +103,13 @@ namespace Bit.App.Pages
{
if (CurrentPage is NavigationPage navPage)
{
if (_groupingsPage?.RootPage is GroupingsPage groupingsPage)
{
await groupingsPage.HideAccountSwitchingOverlayAsync();
}
_messagingService.Send("updatedTheme");
if (navPage.RootPage is GroupingsPage groupingsPage)
if (navPage.RootPage is GroupingsPage)
{
// Load something?
}
@@ -117,5 +123,13 @@ namespace Bit.App.Pages
}
}
}
public void OnPageReselected()
{
if (_groupingsPage?.RootPage is GroupingsPage groupingsPage)
{
groupingsPage.HideAccountSwitchingOverlayAsync().FireAndForget();
}
}
}
}

View File

@@ -338,7 +338,7 @@
<Entry
x:Name="_identityFirstNameEntry"
Text="{Binding Cipher.Identity.FirstName}"
StyleClass="box-value" />
StyleClass="box-value,capitalize-word-input"/>
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
<Label
@@ -347,7 +347,7 @@
<Entry
x:Name="_identityMiddleNameEntry"
Text="{Binding Cipher.Identity.MiddleName}"
StyleClass="box-value" />
StyleClass="box-value,capitalize-word-input" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
<Label
@@ -356,7 +356,7 @@
<Entry
x:Name="_identityLastNameEntry"
Text="{Binding Cipher.Identity.LastName}"
StyleClass="box-value" />
StyleClass="box-value,capitalize-word-input" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
<Label
@@ -420,6 +420,7 @@
<Entry
x:Name="_identityPhoneEntry"
Text="{Binding Cipher.Identity.Phone}"
Keyboard="Telephone"
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
@@ -456,7 +457,7 @@
<Entry
x:Name="_identityCityEntry"
Text="{Binding Cipher.Identity.City}"
StyleClass="box-value" />
StyleClass="box-value,capitalize-sentence-input" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
<Label
@@ -465,7 +466,7 @@
<Entry
x:Name="_identityStateEntry"
Text="{Binding Cipher.Identity.State}"
StyleClass="box-value" />
StyleClass="box-value,capitalize-sentence-input" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
<Label
@@ -483,7 +484,7 @@
<Entry
x:Name="_identityCountryEntry"
Text="{Binding Cipher.Identity.Country}"
StyleClass="box-value" />
StyleClass="box-value,capitalize-sentence-input" />
</StackLayout>
</StackLayout>
</StackLayout>

View File

@@ -2,7 +2,6 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
@@ -18,7 +17,7 @@ namespace Bit.App.Pages
public partial class AddEditPage : BaseContentPage
{
private readonly AppOptions _appOptions;
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IKeyConnectorService _keyConnectorService;
@@ -38,7 +37,7 @@ namespace Bit.App.Pages
bool cloneMode = false,
ViewPage viewPage = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
@@ -333,10 +332,10 @@ namespace Bit.App.Pages
{
return;
}
var addLoginShown = await _storageService.GetAsync<bool?>(Constants.AddSitePromptShownKey);
var addLoginShown = await _stateService.GetAddSitePromptShownAsync();
if (_vm.Cipher.Type == CipherType.Login && !_fromAutofill && !addLoginShown.GetValueOrDefault())
{
await _storageService.SaveAsync(Constants.AddSitePromptShownKey, true);
await _stateService.SetAddSitePromptShownAsync(true);
if (Device.RuntimePlatform == Device.iOS)
{
if (_deviceActionService.SystemMajorVersion() < 12)

View File

@@ -1,21 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.Core;
using Xamarin.Forms;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
namespace Bit.App.Pages
{
@@ -25,12 +22,15 @@ namespace Bit.App.Pages
private readonly ICipherService _cipherService;
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IOrganizationService _organizationService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService;
private readonly IEventService _eventService;
private readonly IPolicyService _policyService;
private readonly ILogger _logger;
private CipherView _cipher;
private bool _showNotesSeparator;
private bool _showPassword;
@@ -67,19 +67,22 @@ namespace Bit.App.Pages
new KeyValuePair<UriMatchType?, string>(UriMatchType.Exact, AppResources.Exact),
new KeyValuePair<UriMatchType?, string>(UriMatchType.Never, AppResources.Never)
};
public AddEditPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
@@ -305,9 +308,9 @@ namespace Bit.App.Pages
public async Task<bool> LoadAsync(AppOptions appOptions = null)
{
var myEmail = await _userService.GetEmailAsync();
var myEmail = await _stateService.GetEmailAsync();
OwnershipOptions.Add(new KeyValuePair<string, string>(myEmail, null));
var orgs = await _userService.GetAllOrganizationAsync();
var orgs = await _organizationService.GetAllAsync();
foreach (var org in orgs.OrderBy(o => o.Name))
{
if (org.Enabled && org.Status == OrganizationUserStatusType.Confirmed)
@@ -536,9 +539,7 @@ namespace Bit.App.Pages
}
catch(Exception genex)
{
#if !FDROID
Crashes.TrackError(genex);
#endif
_logger.Exception(genex);
await _deviceActionService.HideLoadingAsync();
}
return false;

View File

@@ -17,8 +17,8 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly ICryptoService _cryptoService;
private readonly IUserService _userService;
private readonly IVaultTimeoutService _timeoutService;
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IPlatformUtilsService _platformUtilsService;
private CipherView _cipher;
private Cipher _cipherDomain;
@@ -33,8 +33,8 @@ namespace Bit.App.Pages
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_timeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
Attachments = new ExtendedObservableCollection<AttachmentView>();
DeleteAttachmentCommand = new Command<AttachmentView>(DeleteAsync);
PageTitle = AppResources.Attachments;
@@ -66,7 +66,7 @@ namespace Bit.App.Pages
Cipher = await _cipherDomain.DecryptAsync();
LoadAttachments();
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
var canAccessPremium = await _userService.CanAccessPremiumAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
if (!_canAccessAttachments)
{
@@ -140,7 +140,7 @@ namespace Bit.App.Pages
// Prevent Android from locking if vault timeout set to "immediate"
if (Device.RuntimePlatform == Device.Android)
{
_timeoutService.DelayLockAndLogoutMs = 60000;
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
}
await _deviceActionService.SelectFileAsync();
}

View File

@@ -8,10 +8,8 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
@@ -89,8 +87,7 @@ namespace Bit.App.Pages
public async Task LoadAsync()
{
WebsiteIconsEnabled = !(await _stateService.GetAsync<bool?>(Constants.DisableFaviconKey))
.GetValueOrDefault();
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
ShowList = false;
var groupedItems = new List<GroupingsPageListGroup>();
var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null);

View File

@@ -76,8 +76,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
WebsiteIconsEnabled = !(await _stateService.GetAsync<bool?>(Constants.DisableFaviconKey))
.GetValueOrDefault();
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
{
Search((Page as CiphersPage).SearchBar.Text, 200);

View File

@@ -15,6 +15,15 @@
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Search}" />
@@ -48,9 +57,10 @@
<controls:ExtendedStackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform">
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
ShouldUpdateFontSizeDynamicallyForAccesibility="True">
<controls:IconLabel.Effects>
<effects:FixedSizeEffect />
</controls:IconLabel.Effects>
@@ -135,6 +145,8 @@
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<!-- Android FAB -->
<Button
x:Name="_fab"
Image="plus.png"
@@ -148,6 +160,14 @@
<effects:FabShadowEffect />
</Button.Effects>
</Button>
<controls:AccountSwitchingOverlayView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
MainPage="{Binding Source={x:Reference _page}}"
MainFab="{Binding Source={x:Reference _fab}, Path=.}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -1,14 +1,13 @@
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using System;
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -18,7 +17,7 @@ namespace Bit.App.Pages
private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService;
private readonly IPushNotificationService _pushNotificationService;
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService;
private readonly IDeviceActionService _deviceActionService;
@@ -37,7 +36,7 @@ namespace Bit.App.Pages
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@@ -70,6 +69,10 @@ namespace Bit.App.Pages
_absLayout.Children.Remove(_fab);
ToolbarItems.Remove(_addItem);
}
if (!mainPage)
{
ToolbarItems.Remove(_accountAvatar);
}
}
protected async override void OnAppearing()
@@ -80,6 +83,12 @@ namespace Bit.App.Pages
IsBusy = true;
}
_accountAvatar?.OnAppearing();
if (_vm.MainPage)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
_broadcasterService.Subscribe(_pageName, async (message) =>
{
if (message.Command == "syncStarted")
@@ -100,7 +109,6 @@ namespace Bit.App.Pages
}
});
var migratedFromV1 = await _storageService.GetAsync<bool?>(Constants.MigratedFromV1);
await LoadOnAppearedAsync(_mainLayout, false, async () =>
{
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
@@ -123,18 +131,6 @@ namespace Bit.App.Pages
await _vm.LoadAsync();
}
}
// Forced sync if for some reason we have no data after a v1 migration
if (_vm.MainPage && !_syncService.SyncInProgress && migratedFromV1.GetValueOrDefault() &&
!_vm.HasCiphers &&
Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
{
var triedV1ReSync = await _storageService.GetAsync<bool?>(Constants.TriedV1Resync);
if (!triedV1ReSync.GetValueOrDefault())
{
await _storageService.SaveAsync(Constants.TriedV1Resync, true);
await _syncService.FullSyncAsync(true);
}
}
await ShowPreviousPageAsync();
AdjustToolbar();
}, _mainContent);
@@ -145,14 +141,14 @@ namespace Bit.App.Pages
}
// Push registration
var lastPushRegistration = await _storageService.GetAsync<DateTime?>(Constants.PushLastRegistrationDateKey);
var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync();
lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue);
if (Device.RuntimePlatform == Device.iOS)
{
var pushPromptShow = await _storageService.GetAsync<bool?>(Constants.PushInitialPromptShownKey);
var pushPromptShow = await _stateService.GetPushInitialPromptShownAsync();
if (!pushPromptShow.GetValueOrDefault(false))
{
await _storageService.SaveAsync(Constants.PushInitialPromptShownKey, true);
await _stateService.SetPushInitialPromptShownAsync(true);
await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert,
AppResources.OkGotIt);
}
@@ -168,30 +164,26 @@ namespace Bit.App.Pages
{
await _pushNotificationService.RegisterAsync();
}
if (!_deviceActionService.AutofillAccessibilityServiceRunning()
&& !_deviceActionService.AutofillServiceEnabled())
{
if (migratedFromV1.GetValueOrDefault())
{
var migratedFromV1AutofillPromptShown = await _storageService.GetAsync<bool?>(
Constants.MigratedFromV1AutofillPromptShown);
if (!migratedFromV1AutofillPromptShown.GetValueOrDefault())
{
await DisplayAlert(AppResources.Autofill,
AppResources.AutofillServiceNotEnabled, AppResources.Ok);
}
}
}
await _storageService.SaveAsync(Constants.MigratedFromV1AutofillPromptShown, true);
}
}
protected override bool OnBackButtonPressed()
{
if (_accountListOverlay.IsVisible)
{
_accountListOverlay.HideAsync().FireAndForget();
return true;
}
return false;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
IsBusy = false;
_broadcasterService.Unsubscribe(_pageName);
_vm.DisableRefreshing();
_accountAvatar?.OnDisappearing();
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
@@ -230,6 +222,7 @@ namespace Bit.App.Pages
private async void Search_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
if (DoOnce())
{
var page = new CiphersPage(_vm.Filter, _vm.FolderId != null, _vm.CollectionId != null,
@@ -240,21 +233,31 @@ namespace Bit.App.Pages
private async void Sync_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
await _vm.SyncAsync();
}
private async void Lock_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
await _vaultTimeoutService.LockAsync(true, true);
}
private async void Exit_Clicked(object sender, EventArgs e)
{
await _accountListOverlay.HideAsync();
await _vm.ExitAsync();
}
private async void AddButton_Clicked(object sender, EventArgs e)
{
var skipAction = _accountListOverlay.IsVisible && Device.RuntimePlatform == Device.Android;
await _accountListOverlay.HideAsync();
if (skipAction)
{
// Account list in the process of closing via tapping on invisible FAB, skip this attempt
return;
}
if (!_vm.Deleted && DoOnce())
{
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId);
@@ -268,6 +271,7 @@ namespace Bit.App.Pages
{
return;
}
await _accountListOverlay.HideAsync();
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId))
{
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.CipherId)));
@@ -284,5 +288,10 @@ namespace Bit.App.Pages
_addItem.IsEnabled = !_vm.Deleted;
_addItem.IconImageSource = _vm.Deleted ? null : "plus.png";
}
public async Task HideAccountSwitchingOverlayAsync()
{
await _accountListOverlay.HideAsync();
}
}
}

View File

@@ -1,16 +1,16 @@
using Bit.App.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
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.Models.View;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
@@ -40,14 +40,13 @@ namespace Bit.App.Pages
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly ISyncService _syncService;
private readonly IUserService _userService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
private readonly IStateService _stateService;
private readonly IStorageService _storageService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly ILogger _logger;
public GroupingsPageViewModel()
{
@@ -55,14 +54,13 @@ namespace Bit.App.Pages
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
Loading = true;
PageTitle = AppResources.MyVault;
@@ -73,6 +71,11 @@ namespace Bit.App.Pages
await LoadAsync();
});
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = true
};
}
public bool MainPage { get; set; }
@@ -81,7 +84,6 @@ namespace Bit.App.Pages
public string CollectionId { get; set; }
public Func<CipherView, bool> Filter { get; set; }
public bool Deleted { get; set; }
public bool HasCiphers { get; set; }
public bool HasFolders { get; set; }
public bool HasCollections { get; set; }
@@ -140,6 +142,9 @@ namespace Bit.App.Pages
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public Command RefreshCommand { get; set; }
public Command<CipherView> CipherOptionsCommand { get; set; }
@@ -151,7 +156,7 @@ namespace Bit.App.Pages
{
return;
}
var authed = await _userService.IsAuthenticatedAsync();
var authed = await _stateService.IsAuthenticatedAsync();
if (!authed)
{
return;
@@ -160,7 +165,7 @@ namespace Bit.App.Pages
{
return;
}
if (await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey) && Refreshing && !SyncRefreshing)
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
{
SyncRefreshing = true;
await _syncService.FullSyncAsync(false);
@@ -176,8 +181,7 @@ namespace Bit.App.Pages
var groupedItems = new List<GroupingsPageListGroup>();
var page = Page as GroupingsPage;
WebsiteIconsEnabled = !(await _stateService.GetAsync<bool?>(Constants.DisableFaviconKey))
.GetValueOrDefault();
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
try
{
await LoadDataAsync();

View File

@@ -2,7 +2,8 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AppCenter.Crashes;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -14,6 +15,8 @@ namespace Bit.App.Pages
private CancellationTokenSource _autofocusCts;
private Task _continuousAutofocusTask;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public ScanPage(Action<string> callback)
{
_callback = callback;
@@ -61,9 +64,7 @@ namespace Bit.App.Pages
catch (TaskCanceledException) { }
catch (Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
_logger.Value.Exception(ex);
}
}, autofocusCts.Token);
}

View File

@@ -16,7 +16,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly ICollectionService _collectionService;
private readonly IUserService _userService;
private readonly IOrganizationService _organizationService;
private readonly IPlatformUtilsService _platformUtilsService;
private CipherView _cipher;
private int _organizationSelectedIndex;
@@ -28,7 +28,7 @@ namespace Bit.App.Pages
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
Collections = new ExtendedObservableCollection<CollectionViewModel>();
@@ -67,7 +67,7 @@ namespace Bit.App.Pages
var allCollections = await _collectionService.GetAllDecryptedAsync();
_writeableCollections = allCollections.Where(c => !c.ReadOnly).ToList();
var orgs = await _userService.GetAllOrganizationAsync();
var orgs = await _organizationService.GetAllAsync();
OrganizationOptions = orgs.OrderBy(o => o.Name)
.Where(o => o.Enabled && o.Status == OrganizationUserStatusType.Confirmed)
.Select(o => new KeyValuePair<string, string>(o.Name, o.Id)).ToList();
@@ -110,7 +110,7 @@ namespace Bit.App.Pages
await _cipherService.ShareWithServerAsync(cipherView, OrganizationId, checkedCollectionIds);
await _deviceActionService.HideLoadingAsync();
var movedItemToOrgText = string.Format(AppResources.MovedItemToOrg, cipherView.Name,
(await _userService.GetOrganizationAsync(OrganizationId)).Name);
(await _organizationService.GetAsync(OrganizationId)).Name);
_platformUtilsService.ShowToast("success", null, movedItemToOrgText);
await Page.Navigation.PopModalAsync();
return true;

View File

@@ -579,8 +579,8 @@
Grid.Row="1"
Grid.Column="0">
<controls:MonoLabel
Text="{Binding ValueText, Mode=OneWay}"
StyleClass="box-value"
Text="{Binding ColoredHiddenValue, Mode=OneWay}"
StyleClass="box-value, text-html"
IsVisible="{Binding ShowHiddenValue}" />
<controls:MonoLabel
Text="{Binding Field.MaskedValue, Mode=OneWay}"

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -10,10 +10,6 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core;
using Xamarin.Forms;
@@ -23,7 +19,7 @@ namespace Bit.App.Pages
{
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly ITotpService _totpService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
@@ -53,7 +49,7 @@ namespace Bit.App.Pages
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_totpService = ServiceContainer.Resolve<ITotpService>("totpService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
@@ -253,7 +249,7 @@ namespace Bit.App.Pages
return false;
}
Cipher = await cipher.DecryptAsync();
CanAccessPremium = await _userService.CanAccessPremiumAsync();
CanAccessPremium = await _stateService.CanAccessPremiumAsync();
Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(this, Cipher, f)).ToList();
if (Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
@@ -772,6 +768,8 @@ namespace Bit.App.Pages
}
}
public FormattedString ColoredHiddenValue => PasswordFormatter.FormatPassword(_field.Value);
public Command ToggleHiddenValueCommand { get; set; }
public string ShowHiddenValueIcon => _showHiddenValue ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;

View File

@@ -311,6 +311,30 @@ namespace Bit.App.Resources {
}
}
public static string RemoveAccount {
get {
return ResourceManager.GetString("RemoveAccount", resourceCulture);
}
}
public static string RemoveAccountConfirmation {
get {
return ResourceManager.GetString("RemoveAccountConfirmation", resourceCulture);
}
}
public static string AccountAlreadyAdded {
get {
return ResourceManager.GetString("AccountAlreadyAdded", resourceCulture);
}
}
public static string SwitchToAlreadyAddedAccountConfirmation {
get {
return ResourceManager.GetString("SwitchToAlreadyAddedAccountConfirmation", resourceCulture);
}
}
public static string MasterPassword {
get {
return ResourceManager.GetString("MasterPassword", resourceCulture);
@@ -3719,6 +3743,54 @@ namespace Bit.App.Resources {
}
}
public static string AddAccount {
get {
return ResourceManager.GetString("AddAccount", resourceCulture);
}
}
public static string AccountUnlocked {
get {
return ResourceManager.GetString("AccountUnlocked", resourceCulture);
}
}
public static string AccountLocked {
get {
return ResourceManager.GetString("AccountLocked", resourceCulture);
}
}
public static string AccountLoggedOut {
get {
return ResourceManager.GetString("AccountLoggedOut", resourceCulture);
}
}
public static string AccountSwitchedAutomatically {
get {
return ResourceManager.GetString("AccountSwitchedAutomatically", resourceCulture);
}
}
public static string AccountLockedSuccessfully {
get {
return ResourceManager.GetString("AccountLockedSuccessfully", resourceCulture);
}
}
public static string AccountLoggedOutSuccessfully {
get {
return ResourceManager.GetString("AccountLoggedOutSuccessfully", resourceCulture);
}
}
public static string AccountRemovedSuccessfully {
get {
return ResourceManager.GetString("AccountRemovedSuccessfully", resourceCulture);
}
}
public static string DeleteAccount {
get {
return ResourceManager.GetString("DeleteAccount", resourceCulture);
@@ -3755,6 +3827,12 @@ namespace Bit.App.Resources {
}
}
public static string RequestOTP {
get {
return ResourceManager.GetString("RequestOTP", resourceCulture);
}
}
public static string SendCode {
get {
return ResourceManager.GetString("SendCode", resourceCulture);

View File

@@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve">
<value>Is u seker u wil uitteken?</value>
</data>
<data name="RemoveAccount" xml:space="preserve">
<value>Remove Account</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Are you sure you want to remove this account?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Account Already Added</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Would you like to switch to it now?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Hoofwagwoord</value>
<comment>Label for a master password.</comment>
@@ -2092,6 +2104,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect">
<value>Een of meer organisasiebeleide verhoed u om u persoonlike kluis uit te stuur.</value>
</data>
<data name="AddAccount" xml:space="preserve">
<value>Add Account</value>
</data>
<data name="AccountUnlocked" xml:space="preserve">
<value>Unlocked</value>
</data>
<data name="AccountLocked" xml:space="preserve">
<value>Locked</value>
</data>
<data name="AccountLoggedOut" xml:space="preserve">
<value>Logged Out</value>
</data>
<data name="AccountSwitchedAutomatically" xml:space="preserve">
<value>Switched to next available account</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Skrap rekening</value>
</data>

View File

@@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve">
<value>Çıxış etmək istədiyinizə əminsiniz?</value>
</data>
<data name="RemoveAccount" xml:space="preserve">
<value>Remove Account</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Are you sure you want to remove this account?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Account Already Added</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Would you like to switch to it now?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Ana parol</value>
<comment>Label for a master password.</comment>
@@ -2092,6 +2104,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect">
<value>Bir və ya daha çox təşkilat siyasəti, fərdi anbarınızı ixrac etməyinizin qarşısını alır.</value>
</data>
<data name="AddAccount" xml:space="preserve">
<value>Add Account</value>
</data>
<data name="AccountUnlocked" xml:space="preserve">
<value>Unlocked</value>
</data>
<data name="AccountLocked" xml:space="preserve">
<value>Locked</value>
</data>
<data name="AccountLoggedOut" xml:space="preserve">
<value>Logged Out</value>
</data>
<data name="AccountSwitchedAutomatically" xml:space="preserve">
<value>Switched to next available account</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Hesabı sil</value>
</data>

View File

@@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve">
<value>Вы ўпэўнены, што хочаце выйсці?</value>
</data>
<data name="RemoveAccount" xml:space="preserve">
<value>Remove Account</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Are you sure you want to remove this account?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Account Already Added</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Would you like to switch to it now?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Асноўны пароль</value>
<comment>Label for a master password.</comment>
@@ -2092,6 +2104,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect">
<value>One or more organization policies prevents your from exporting your personal vault.</value>
</data>
<data name="AddAccount" xml:space="preserve">
<value>Add Account</value>
</data>
<data name="AccountUnlocked" xml:space="preserve">
<value>Unlocked</value>
</data>
<data name="AccountLocked" xml:space="preserve">
<value>Locked</value>
</data>
<data name="AccountLoggedOut" xml:space="preserve">
<value>Logged Out</value>
</data>
<data name="AccountSwitchedAutomatically" xml:space="preserve">
<value>Switched to next available account</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Delete Account</value>
</data>

View File

@@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve">
<value>Сигурни ли сте, че искате да се отпишете?</value>
</data>
<data name="RemoveAccount" xml:space="preserve">
<value>Премахване на регистрацията</value>
</data>
<data name="RemoveAccountConfirmation" xml:space="preserve">
<value>Наистина ли искате да премахнете тази регистрация?</value>
</data>
<data name="AccountAlreadyAdded" xml:space="preserve">
<value>Регистрацията вече е добавена</value>
</data>
<data name="SwitchToAlreadyAddedAccountConfirmation" xml:space="preserve">
<value>Искате ли да превключите към нея сега?</value>
</data>
<data name="MasterPassword" xml:space="preserve">
<value>Главна парола</value>
<comment>Label for a master password.</comment>
@@ -2093,6 +2105,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect">
<value>Една или повече от настройките на организацията Ви не позволяват да изнасяте личния си трезор.</value>
</data>
<data name="AddAccount" xml:space="preserve">
<value>Добавяне на регистрация</value>
</data>
<data name="AccountUnlocked" xml:space="preserve">
<value>Отключено</value>
</data>
<data name="AccountLocked" xml:space="preserve">
<value>Заключено</value>
</data>
<data name="AccountLoggedOut" xml:space="preserve">
<value>Отписано</value>
</data>
<data name="AccountSwitchedAutomatically" xml:space="preserve">
<value>Превключено към следващата налична регистрация</value>
</data>
<data name="DeleteAccount" xml:space="preserve">
<value>Изтриване на регистрацията</value>
</data>

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