1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-16 16:23:29 +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: branches-ignore:
- 'l10n_master' - 'l10n_master'
- 'gh-pages' - 'gh-pages'
paths-ignore:
- '.github/workflows/**'
workflow_dispatch:
inputs: {}
jobs: jobs:
cloc: cloc:
@@ -237,6 +241,7 @@ jobs:
run: | run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj"); $androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.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"); $androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
@@ -302,6 +307,18 @@ jobs:
$appCenterNode.ParentNode.RemoveChild($appCenterNode); $appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath); $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 shell: pwsh
- name: Restore packages - name: Restore packages
@@ -529,8 +546,7 @@ jobs:
|| github.ref == 'refs/heads/hotfix-rc' || github.ref == 'refs/heads/hotfix-rc'
env: env:
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }} APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
run: | run: appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
appcenter crashes upload-symbols -a kspearrin/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
shell: bash shell: bash
- name: Deploy to App Store - 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: options:
- Initial Release - Initial Release
- Redeploy - Redeploy
- dry-run
jobs: jobs:
release: release:
@@ -21,6 +22,7 @@ jobs:
branch-name: ${{ steps.branch.outputs.branch-name }} branch-name: ${{ steps.branch.outputs.branch-name }}
steps: steps:
- name: Branch check - name: Branch check
if: github.event.inputs.release_type != 'dry-run'
run: | run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "===================================" echo "==================================="
@@ -30,12 +32,13 @@ jobs:
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Retrieve Mobile release version - name: Retrieve Mobile release version
id: retrieve-mobile-version id: retrieve-mobile-version
run: | 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}" echo "::set-output name=mobile_version::${ver}"
shell: bash shell: bash
@@ -62,7 +65,7 @@ jobs:
echo "::set-output name=branch-name::$BRANCH_NAME" echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Download all artifacts - name: Download all artifacts
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0 uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -72,7 +75,8 @@ jobs:
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
- name: Create release - 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: with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab, artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk, ./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
@@ -92,10 +96,10 @@ jobs:
needs: release needs: release
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Download F-Droid .apk artifact - 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: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -103,7 +107,7 @@ jobs:
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node - name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.3.0 uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
with: with:
node-version: '10.x' node-version: '10.x'
@@ -167,4 +171,5 @@ jobs:
cd $GITHUB_WORKSPACE cd $GITHUB_WORKSPACE
- name: Deploy to gh-pages - name: Deploy to gh-pages
if: github.event.inputs.release_type != 'dry-run'
run: npm run deploy 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. - Restore the nuget packages.
- Build and run the app. - 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 # Contribute
Code contributions are welcome! Visual Studio with Xamarin is required to work on this project. Please commit any pull requests against the `master` branch. Code contributions are welcome! 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: // So keep them in sync with:
// - AutofillHelpers.{TrustedBrowsers,CompatBrowsers} // - AutofillHelpers.{TrustedBrowsers,CompatBrowsers}
// - Resources/xml/autofillservice.xml // - 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.amazon.cloud9", "url"),
new Browser("com.android.browser", "url"), new Browser("com.android.browser", "url"),
new Browser("com.android.chrome", "url_bar"), new Browser("com.android.chrome", "url_bar"),

View File

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

View File

@@ -84,7 +84,7 @@
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" /> <PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" /> <PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.0</Version> <Version>1.7.1</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging"> <PackageReference Include="Xamarin.Firebase.Messaging">
<Version>122.0.0</Version> <Version>122.0.0</Version>
@@ -171,7 +171,8 @@
<AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" /> <AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" /> <AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable\card.xml" /> <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\icon.xml" />
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" /> <AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
<AndroidResource Include="Resources\drawable\ic_warning.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. // - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
public static HashSet<string> CompatBrowsers = new HashSet<string> public static HashSet<string> CompatBrowsers = new HashSet<string>
{ {
"alook.browser",
"alook.browser.google",
"com.amazon.cloud9", "com.amazon.cloud9",
"com.android.browser", "com.android.browser",
"com.android.chrome", "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.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
@@ -9,12 +12,6 @@ using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
namespace Bit.Droid.Autofill namespace Bit.Droid.Autofill
{ {
@@ -26,9 +23,9 @@ namespace Bit.Droid.Autofill
{ {
private ICipherService _cipherService; private ICipherService _cipherService;
private IVaultTimeoutService _vaultTimeoutService; private IVaultTimeoutService _vaultTimeoutService;
private IStorageService _storageService;
private IPolicyService _policyService; 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, public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback) FillCallback callback)
@@ -44,18 +41,18 @@ namespace Bit.Droid.Autofill
var parser = new Parser(structure, ApplicationContext); var parser = new Parser(structure, ApplicationContext);
parser.Parse(); 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) if (!shouldAutofill)
{ {
return; return;
} }
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true; var inlineAutofillEnabled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
if (_vaultTimeoutService == null) if (_vaultTimeoutService == null)
{ {
@@ -76,7 +73,7 @@ namespace Bit.Droid.Autofill
// build response // build response
var response = AutofillHelpers.CreateFillResponse(parser, items, locked, inlineAutofillEnabled, request); 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()) if (!disableSavePrompt.GetValueOrDefault())
{ {
AutofillHelpers.AddSaveInfo(parser, request, response, parser.FieldCollection); AutofillHelpers.AddSaveInfo(parser, request, response, parser.FieldCollection);
@@ -85,9 +82,7 @@ namespace Bit.Droid.Autofill
} }
catch (Exception e) catch (Exception e)
{ {
#if !FDROID _logger.Value.Exception(e);
Crashes.TrackError(e);
#endif
} }
} }
@@ -101,12 +96,12 @@ namespace Bit.Droid.Autofill
return; 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()) if (disableSavePrompt.GetValueOrDefault())
{ {
return; return;
@@ -161,9 +156,7 @@ namespace Bit.Droid.Autofill
} }
catch (Exception e) catch (Exception e)
{ {
#if !FDROID _logger.Value.Exception(e);
Crashes.TrackError(e);
#endif
} }
} }
} }

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) && var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
FieldCollection != null && FieldCollection.Fillable; FieldCollection != null && FieldCollection.Fillable;
if (fillable) if (fillable)
{ {
var blacklistedUris = await storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey); var blacklistedUris = await stateService.GetAutofillBlacklistedUrisAsync();
if (blacklistedUris != null && blacklistedUris.Count > 0) if (blacklistedUris != null && blacklistedUris.Count > 0)
{ {
fillable = !new HashSet<string>(blacklistedUris).Contains(Uri); fillable = !new HashSet<string>(blacklistedUris).Contains(Uri);

View File

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

View File

@@ -53,7 +53,8 @@ namespace Bit.Droid
ServiceContainer.Resolve<IApiService>("apiService"), ServiceContainer.Resolve<IApiService>("apiService"),
ServiceContainer.Resolve<IMessagingService>("messagingService"), ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"), ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService")); ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"),
ServiceContainer.Resolve<ILogger>("logger"));
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner); ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper( var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
@@ -87,7 +88,14 @@ namespace Bit.Droid
private void RegisterLocalServices() 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. // Note: This might cause a race condition. Investigate more.
Task.Run(() => Task.Run(() =>
@@ -113,13 +121,16 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService(); var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService(); var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var 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")); broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService, var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService); broadcasterService);
var biometricService = new BiometricService(); var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService); ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
@@ -129,7 +140,9 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService); ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService); ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService); 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<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService); ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService); ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
@@ -148,7 +161,7 @@ namespace Bit.Droid
ServiceContainer.Register<IPushNotificationListenerService>( ServiceContainer.Register<IPushNotificationListenerService>(
"pushNotificationListenerService", notificationListenerService); "pushNotificationListenerService", notificationListenerService);
var androidPushNotificationService = new AndroidPushNotificationService( var androidPushNotificationService = new AndroidPushNotificationService(
mobileStorageService, notificationListenerService); stateService, notificationListenerService);
ServiceContainer.Register<IPushNotificationService>( ServiceContainer.Register<IPushNotificationService>(
"pushNotificationService", androidPushNotificationService); "pushNotificationService", androidPushNotificationService);
#endif #endif
@@ -164,10 +177,6 @@ namespace Bit.Droid
private async Task BootstrapAsync() 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(); await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
} }
} }

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="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"/> <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) 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"); var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token); await stateService.SetPushRegisteredTokenAsync(token);
await pushNotificationService.RegisterAsync(); await pushNotificationService.RegisterAsync();
} }

View File

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

View File

@@ -1,7 +1,9 @@
using Android.Content; using Android.Content;
using Android.Views; using Android.Views;
using Bit.App.Pages;
using Bit.Droid.Renderers; using Bit.Droid.Renderers;
using Google.Android.Material.BottomNavigation; using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Navigation;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Platform.Android; using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat; using Xamarin.Forms.Platform.Android.AppCompat;
@@ -9,7 +11,7 @@ using Xamarin.Forms.Platform.Android.AppCompat;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))] [assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
namespace Bit.Droid.Renderers namespace Bit.Droid.Renderers
{ {
public class CustomTabbedRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener public class CustomTabbedRenderer : TabbedPageRenderer, NavigationBarView.IOnItemReselectedListener
{ {
private TabbedPage _page; private TabbedPage _page;
@@ -21,7 +23,7 @@ namespace Bit.Droid.Renderers
if (e.NewElement != null) if (e.NewElement != null)
{ {
_page = e.NewElement; _page = e.NewElement;
GetBottomNavigationView()?.SetOnNavigationItemReselectedListener(this); GetBottomNavigationView()?.SetOnItemReselectedListener(this);
} }
else else
{ {
@@ -53,6 +55,10 @@ namespace Bit.Droid.Renderers
{ {
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0) 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()); 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 <path
android:fillColor="#FF000000" 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" /> 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" <autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsInlineSuggestions="true"> android:supportsInlineSuggestions="true">
<compatibility-package
android:name="alook.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="alook.browser.google"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.amazon.cloud9" android:name="com.amazon.cloud9"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>

View File

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

View File

@@ -3,7 +3,6 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using AndroidX.Core.App; using AndroidX.Core.App;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Xamarin.Forms; using Xamarin.Forms;
@@ -11,14 +10,14 @@ namespace Bit.Droid.Services
{ {
public class AndroidPushNotificationService : IPushNotificationService public class AndroidPushNotificationService : IPushNotificationService
{ {
private readonly IStorageService _storageService; private readonly IStateService _stateService;
private readonly IPushNotificationListenerService _pushNotificationListenerService; private readonly IPushNotificationListenerService _pushNotificationListenerService;
public AndroidPushNotificationService( public AndroidPushNotificationService(
IStorageService storageService, IStateService stateService,
IPushNotificationListenerService pushNotificationListenerService) IPushNotificationListenerService pushNotificationListenerService)
{ {
_storageService = storageService; _stateService = stateService;
_pushNotificationListenerService = pushNotificationListenerService; _pushNotificationListenerService = pushNotificationListenerService;
} }
@@ -26,12 +25,12 @@ namespace Bit.Droid.Services
public async Task<string> GetTokenAsync() public async Task<string> GetTokenAsync()
{ {
return await _storageService.GetAsync<string>(Constants.PushCurrentTokenKey); return await _stateService.GetPushCurrentTokenAsync();
} }
public async Task RegisterAsync() public async Task RegisterAsync()
{ {
var registeredToken = await _storageService.GetAsync<string>(Constants.PushRegisteredTokenKey); var registeredToken = await _stateService.GetPushRegisteredTokenAsync();
var currentToken = await GetTokenAsync(); var currentToken = await GetTokenAsync();
if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken) if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken)
{ {
@@ -39,7 +38,7 @@ namespace Bit.Droid.Services
} }
else 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 public class ClipboardService : IClipboardService
{ {
private readonly IStorageService _storageService; private readonly IStateService _stateService;
private readonly Lazy<PendingIntent> _clearClipboardPendingIntent; private readonly Lazy<PendingIntent> _clearClipboardPendingIntent;
public ClipboardService(IStorageService storageService) public ClipboardService(IStateService stateService)
{ {
_storageService = storageService; _stateService = stateService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() => _clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity, PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
@@ -39,7 +39,7 @@ namespace Bit.Droid.Services
if (clearMs < 0) if (clearMs < 0)
{ {
// if not set then we need to check if the user set this config // 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) if (clearSeconds != null)
{ {
clearMs = clearSeconds.Value * 1000; clearMs = clearSeconds.Value * 1000;

View File

@@ -35,7 +35,7 @@ namespace Bit.Droid.Services
{ {
public class DeviceActionService : IDeviceActionService public class DeviceActionService : IDeviceActionService
{ {
private readonly IStorageService _storageService; private readonly IStateService _stateService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly Func<IEventService> _eventServiceFunc; private readonly Func<IEventService> _eventServiceFunc;
@@ -47,12 +47,12 @@ namespace Bit.Droid.Services
private string _userAgent; private string _userAgent;
public DeviceActionService( public DeviceActionService(
IStorageService storageService, IStateService stateService,
IMessagingService messagingService, IMessagingService messagingService,
IBroadcasterService broadcasterService, IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc) Func<IEventService> eventServiceFunc)
{ {
_storageService = storageService; _stateService = stateService;
_messagingService = messagingService; _messagingService = messagingService;
_broadcasterService = broadcasterService; _broadcasterService = broadcasterService;
_eventServiceFunc = eventServiceFunc; _eventServiceFunc = eventServiceFunc;
@@ -333,7 +333,7 @@ namespace Bit.Droid.Services
try try
{ {
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir); DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
await _storageService.SaveAsync(Constants.LastFileCacheClearKey, DateTime.UtcNow); await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
} }
catch (Exception) { } catch (Exception) { }
} }
@@ -916,9 +916,8 @@ namespace Bit.Droid.Services
{ {
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp)) if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
{ {
var userService = ServiceContainer.Resolve<IUserService>("userService"); var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
var autoCopyDisabled = await _storageService.GetAsync<bool?>(Constants.DisableAutoTotpCopyKey); var canAccessPremium = await _stateService.CanAccessPremiumAsync();
var canAccessPremium = await userService.CanAccessPremiumAsync();
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault()) if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
{ {
var totpService = ServiceContainer.Resolve<ITotpService>("totpService"); var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
@@ -938,5 +937,16 @@ namespace Bit.Droid.Services
Context.ClipboardService) as Android.Content.ClipboardManager; Context.ClipboardService) as Android.Content.ClipboardManager;
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text); 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.Runtime;
using Android.Service.QuickSettings; using Android.Service.QuickSettings;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Accessibility; using Bit.Droid.Accessibility;
@@ -18,7 +17,7 @@ namespace Bit.Droid.Tile
[Register("com.x8bit.bitwarden.AutofillTileService")] [Register("com.x8bit.bitwarden.AutofillTileService")]
public class AutofillTileService : TileService public class AutofillTileService : TileService
{ {
private IStorageService _storageService; private IStateService _stateService;
public override void OnTileAdded() public override void OnTileAdded()
{ {
@@ -59,11 +58,11 @@ namespace Bit.Droid.Tile
private void SetTileAdded(bool isAdded) private void SetTileAdded(bool isAdded)
{ {
AccessibilityHelpers.IsAutofillTileAdded = 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() private void ScanAndFill()

View File

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

View File

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

View File

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

View File

@@ -4,12 +4,12 @@ using Bit.App.Pages;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.Xaml; using Xamarin.Forms.Xaml;
@@ -18,7 +18,6 @@ namespace Bit.App
{ {
public partial class App : Application public partial class App : Application
{ {
private readonly IUserService _userService;
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
@@ -26,7 +25,6 @@ namespace Bit.App
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly IStorageService _storageService;
private readonly IStorageService _secureStorageService; private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
@@ -40,7 +38,6 @@ namespace Bit.App
Current = this; Current = this;
return; return;
} }
_userService = ServiceContainer.Resolve<IUserService>("userService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
@@ -48,7 +45,6 @@ namespace Bit.App
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_authService = ServiceContainer.Resolve<IAuthService>("authService"); _authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService"); _secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@@ -77,7 +73,10 @@ namespace Bit.App
} }
else if (message.Command == "locked") 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") else if (message.Command == "lockVault")
{ {
@@ -85,8 +84,11 @@ namespace Bit.App
} }
else if (message.Command == "logout") else if (message.Command == "logout")
{ {
Device.BeginInvokeOnMainThread(async () => var extras = message.Data as Tuple<string, bool, bool>;
await LogOutAsync((message.Data as bool?).GetValueOrDefault())); 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") else if (message.Command == "loggedOut")
{ {
@@ -107,6 +109,18 @@ namespace Bit.App
await SleptAsync(); await SleptAsync();
} }
} }
else if (message.Command == "addAccount")
{
await AddAccount();
}
else if (message.Command == "accountAdded")
{
await UpdateThemeAsync();
}
else if (message.Command == "switchedAccount")
{
await SwitchedAccountAsync();
}
else if (message.Command == "migrated") else if (message.Command == "migrated")
{ {
await Task.Delay(1000); await Task.Delay(1000);
@@ -168,7 +182,7 @@ namespace Bit.App
if (string.IsNullOrWhiteSpace(Options.Uri)) if (string.IsNullOrWhiteSpace(Options.Uri))
{ {
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService, var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_storageService); _stateService);
if (!updated) if (!updated)
{ {
SyncIfNeeded(); SyncIfNeeded();
@@ -192,7 +206,7 @@ namespace Bit.App
var isLocked = await _vaultTimeoutService.IsLockedAsync(); var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked) if (!isLocked)
{ {
await _storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime()); await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
} }
SetTabsPageFromAutofill(isLocked); SetTabsPageFromAutofill(isLocked);
await SleptAsync(); await SleptAsync();
@@ -233,7 +247,7 @@ namespace Bit.App
{ {
await Device.InvokeOnMainThreadAsync(() => await Device.InvokeOnMainThreadAsync(() =>
{ {
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources); ThemeManager.SetTheme(Current.Resources);
_messagingService.Send("updatedTheme"); _messagingService.Send("updatedTheme");
}); });
} }
@@ -246,12 +260,12 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar(); 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(() => _authService.LogOut(() =>
{ {
Current.MainPage = new HomePage();
if (expired) if (expired)
{ {
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired); _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() private async Task SetMainPageAsync()
{ {
var authed = await _userService.IsAuthenticatedAsync(); var authed = await _stateService.IsAuthenticatedAsync();
if (authed) 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)); Current.MainPage = new NavigationPage(new LockPage(Options));
} }
@@ -287,13 +339,26 @@ namespace Bit.App
} }
else 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() 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) if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
{ {
var task = Task.Run(() => _deviceActionService.ClearCacheAsync()); var task = Task.Run(() => _deviceActionService.ClearCacheAsync());
@@ -336,12 +401,12 @@ namespace Bit.App
{ {
InitializeComponent(); InitializeComponent();
SetCulture(); SetCulture();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources); ThemeManager.SetTheme(Current.Resources);
Current.RequestedThemeChanged += (s, a) => Current.RequestedThemeChanged += (s, a) =>
{ {
UpdateThemeAsync(); UpdateThemeAsync();
}; };
Current.MainPage = new HomePage(); Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = SetMainPageAsync(); var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init(); 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) if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{ {
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey); var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0) if (vaultTimeout == 0)
{ {
autoPromptBiometric = false; 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); var lockPage = new LockPage(Options, autoPromptBiometric);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage)); 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"> x:DataType="controls:CipherViewCellViewModel">
<Grid.Resources> <Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter"/> <u:IconGlyphConverter x:Key="iconGlyphConverter"/>
<u:IconImageConverter x:Key="iconImageConverter"/> <u:IconImageConverter x:Key="iconImageConverter"/>
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValueConverter" /> <u:StringHasValueConverter x:Key="stringHasValueConverter" />
</Grid.Resources> </Grid.Resources>
<Grid.RowDefinitions> <Grid.RowDefinitions>
@@ -23,7 +23,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="40" /> <ColumnDefinition Width="40" x:Name="_iconColumn" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="60" /> <ColumnDefinition Width="60" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -35,17 +35,21 @@
StyleClass="list-icon, list-icon-platform" StyleClass="list-icon, list-icon-platform"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}" IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}" Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False" /> AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage <ff:CachedImage
x:Name="_iconImage"
Grid.Column="0" Grid.Column="0"
BitmapOptimizations="True" BitmapOptimizations="True"
ErrorPlaceholder="login.png" ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png" LoadingPlaceholder="login.png"
HorizontalOptions="Center" HorizontalOptions="CenterAndExpand"
VerticalOptions="Center" VerticalOptions="CenterAndExpand"
Margin="9"
WidthRequest="22" WidthRequest="22"
HeightRequest="22" HeightRequest="22"
Aspect="AspectFit"
IsVisible="{Binding ShowIconImage}" IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}" Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" /> AutomationProperties.IsInAccessibleTree="False" />
@@ -112,4 +116,4 @@
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" /> AutomationProperties.Name="{u:I18n Options}" />
</controls:ExtendedGrid> </controls:ExtendedGrid>

View File

@@ -1,11 +1,16 @@
using System; using System;
using Bit.App.Abstractions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
public partial class CipherViewCell : ExtendedGrid 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( public static readonly BindableProperty CipherProperty = BindableProperty.Create(
nameof(Cipher), typeof(CipherView), typeof(CipherViewCell), default(CipherView), BindingMode.OneWay); nameof(Cipher), typeof(CipherView), typeof(CipherViewCell), default(CipherView), BindingMode.OneWay);
@@ -18,6 +23,11 @@ namespace Bit.App.Controls
public CipherViewCell() public CipherViewCell()
{ {
InitializeComponent(); 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 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 class IconLabel : Label
{ {
public bool ShouldUpdateFontSizeDynamicallyForAccesibility { get; set; }
public IconLabel() public IconLabel()
{ {
switch (Device.RuntimePlatform) switch (Device.RuntimePlatform)

View File

@@ -19,7 +19,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="40" /> <ColumnDefinition Width="40" x:Name="_iconColumn" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="60" /> <ColumnDefinition Width="60" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -31,6 +31,7 @@
VerticalOptions="Center" VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform" StyleClass="list-icon, list-icon-platform"
Text="{Binding Send, Converter={StaticResource sendIconGlyphConverter}}" Text="{Binding Send, Converter={StaticResource sendIconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False" /> AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7"> <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 System;
using Bit.App.Abstractions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls
@@ -18,6 +20,9 @@ namespace Bit.App.Controls
public SendViewCell() public SendViewCell()
{ {
InitializeComponent(); InitializeComponent();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_iconColumn.Width = new GridLength(40 * deviceActionService.GetSystemFontSizeScale(), GridUnitType.Absolute);
} }
public SendView Send 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 bool IosExtension { get; set; }
public Tuple<SendType, string, byte[], string> CreateSend { get; set; } public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
public bool CopyInsteadOfShareAfterSaving { get; set; } public bool CopyInsteadOfShareAfterSaving { get; set; }
public bool HideAccountSwitcher { get; set; }
public void SetAllFrom(AppOptions o) public void SetAllFrom(AppOptions o)
{ {
@@ -46,6 +47,7 @@ namespace Bit.App.Models
IosExtension = o.IosExtension; IosExtension = o.IosExtension;
CreateSend = o.CreateSend; CreateSend = o.CreateSend;
CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving; CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving;
HideAccountSwitcher = o.HideAccountSwitcher;
} }
} }
} }

View File

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

View File

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

View File

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

View File

@@ -6,49 +6,76 @@
xmlns:pages="clr-namespace:Bit.App.Pages" xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:HomeViewModel" x:DataType="pages:HomeViewModel"
x:Name="_page"
Title="{Binding PageTitle}"> Title="{Binding PageTitle}">
<ContentPage.BindingContext> <ContentPage.BindingContext>
<pages:HomeViewModel /> <pages:HomeViewModel />
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.ToolbarItems> <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> </ContentPage.ToolbarItems>
<StackLayout Spacing="0" Padding="10, 5"> <ContentPage.Resources>
<controls:IconButton Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}" <ResourceDictionary>
StyleClass="btn-muted, btn-icon, btn-icon-platform" <StackLayout x:Name="_mainLayout" x:Key="mainLayout" Spacing="0" Padding="10, 5">
HorizontalOptions="Start" <StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
Clicked="Environment_Clicked" <Image
AutomationProperties.IsInAccessibleTree="True" x:Name="_logo"
AutomationProperties.Name="{u:I18n Options}"> Source="logo.png"
<controls:IconButton.Margin> VerticalOptions="Center" />
<OnPlatform x:TypeArguments="Thickness"> <Label Text="{u:I18n LoginOrCreateNewAccount}"
<On Platform="iOS" Value="0, 10, 0, 0" /> StyleClass="text-lg"
<On Platform="Android" Value="0" /> HorizontalTextAlignment="Center">
</OnPlatform> </Label>
</controls:IconButton.Margin> <StackLayout Spacing="5">
</controls:IconButton> <Button Text="{u:I18n LogIn}"
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20"> StyleClass="btn-primary"
<Image Clicked="LogIn_Clicked" />
x:Name="_logo" <Button Text="{u:I18n CreateAccount}"
Source="logo.png" Clicked="Register_Clicked" />
VerticalOptions="Center" /> <Button Text="{u:I18n LogInSso}"
<Label Text="{u:I18n LoginOrCreateNewAccount}" Clicked="LogInSso_Clicked" />
StyleClass="text-lg" <Button Text="{u:I18n Cancel}"
HorizontalTextAlignment="Center"></Label> IsVisible="{Binding ShowCancelButton}"
<StackLayout Spacing="5"> Margin="0,10,0,0"
<Button Text="{u:I18n LogIn}" Clicked="Cancel_Clicked" />
StyleClass="btn-primary" </StackLayout>
Clicked="LogIn_Clicked" /> </StackLayout>
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked" />
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked" />
</StackLayout> </StackLayout>
</StackLayout> </ResourceDictionary>
</StackLayout> </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> </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.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -12,13 +12,10 @@ namespace Bit.App.Pages
{ {
private readonly HomeViewModel _vm; private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService; private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null) public HomePage(AppOptions appOptions = null)
{ {
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", false);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions; _appOptions = appOptions;
InitializeComponent(); InitializeComponent();
@@ -29,6 +26,16 @@ namespace Bit.App.Pages
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync()); _vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync()); _vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
UpdateLogo(); UpdateLogo();
if (_appOptions?.IosExtension ?? false)
{
_vm.ShowCancelButton = true;
}
if (_appOptions?.HideAccountSwitcher ?? false)
{
ToolbarItems.Remove(_accountAvatar);
}
} }
public async Task DismissRegisterPageAndLogInAsync(string email) public async Task DismissRegisterPageAndLogInAsync(string email)
@@ -37,10 +44,16 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(new LoginPage(email, _appOptions))); await Navigation.PushModalAsync(new NavigationPage(new LoginPage(email, _appOptions)));
} }
protected override void OnAppearing() protected override async void OnAppearing()
{ {
base.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) => _broadcasterService.Subscribe(nameof(HomePage), async (message) =>
{ {
if (message.Command == "updatedTheme") 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() protected override void OnDisappearing()
{ {
base.OnDisappearing(); base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(HomePage)); _broadcasterService.Unsubscribe(nameof(HomePage));
_accountAvatar?.OnDisappearing();
} }
private void UpdateLogo() private void UpdateLogo()
@@ -64,7 +88,7 @@ namespace Bit.App.Pages
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png"; _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()) if (DoOnce())
{ {
@@ -124,6 +148,7 @@ namespace Bit.App.Pages
private async Task StartEnvironmentAsync() private async Task StartEnvironmentAsync()
{ {
await _accountListOverlay.HideAsync();
var page = new EnvironmentPage(); var page = new EnvironmentPage();
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }

View File

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

View File

@@ -7,12 +7,26 @@
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LockPageViewModel" x:DataType="pages:LockPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}"> Title="{Binding PageTitle}">
<ContentPage.BindingContext> <ContentPage.BindingContext>
<pages:LockPageViewModel /> <pages:LockPageViewModel />
</ContentPage.BindingContext> </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> <ContentPage.Resources>
<ResourceDictionary> <ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />
@@ -25,119 +39,136 @@
x:Name="_logOut" x:Name="_logOut"
Clicked="LogOut_Clicked" Clicked="LogOut_Clicked"
Order="Secondary"/> 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> </ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>
<ContentPage.ToolbarItems> <AbsoluteLayout
</ContentPage.ToolbarItems> x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<ScrollView> <controls:AccountSwitchingOverlayView
<StackLayout Spacing="20"> x:Name="_accountListOverlay"
<StackLayout StyleClass="box"> AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
<Grid AbsoluteLayout.LayoutFlags="All"
StyleClass="box-row" MainPage="{Binding Source={x:Reference _page}}"
IsVisible="{Binding PinLock}" BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
Padding="0, 10, 0, 0">
<Grid.RowDefinitions> </AbsoluteLayout>
<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>
</pages:BaseContentPage> </pages:BaseContentPage>

View File

@@ -1,10 +1,9 @@
using Bit.App.Models; using System;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -62,7 +61,14 @@ namespace Bit.App.Pages
{ {
return; return;
} }
_appeared = true; _appeared = true;
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
await _vm.InitAsync(); await _vm.InitAsync();
if (!_vm.BiometricLock) 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) private void Unlock_Clicked(object sender, EventArgs e)
{ {
if (DoOnce()) if (DoOnce())
@@ -107,6 +130,7 @@ namespace Bit.App.Pages
private async void LogOut_Clicked(object sender, EventArgs e) private async void LogOut_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync();
if (DoOnce()) if (DoOnce())
{ {
await _vm.LogOutAsync(); await _vm.LogOutAsync();
@@ -123,6 +147,8 @@ namespace Bit.App.Pages
private async void More_Clicked(object sender, System.EventArgs e) private async void More_Clicked(object sender, System.EventArgs e)
{ {
await _accountListOverlay.HideAsync();
if (!DoOnce()) if (!DoOnce())
{ {
return; return;

View File

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

View File

@@ -7,12 +7,26 @@
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LoginPageViewModel" x:DataType="pages:LoginPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}"> Title="{Binding PageTitle}">
<ContentPage.BindingContext> <ContentPage.BindingContext>
<pages:LoginPageViewModel /> <pages:LoginPageViewModel />
</ContentPage.BindingContext> </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> <ContentPage.Resources>
<ResourceDictionary> <ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" /> <u:InverseBoolConverter x:Key="inverseBool" />
@@ -24,69 +38,101 @@
x:Key="getPasswordHint" x:Key="getPasswordHint"
x:Name="_getPasswordHint" x:Name="_getPasswordHint"
Clicked="Hint_Clicked" 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> </ResourceDictionary>
</ContentPage.Resources> </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> <controls:AccountSwitchingOverlayView
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" /> x:Name="_accountListOverlay"
</ContentPage.ToolbarItems> AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
<ScrollView> MainPage="{Binding Source={x:Reference _page}}"
<StackLayout Spacing="20"> BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
<StackLayout StyleClass="box"> </AbsoluteLayout>
<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>
</pages:BaseContentPage> </pages:BaseContentPage>

View File

@@ -1,18 +1,15 @@
using Bit.App.Models; using System;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public partial class LoginPage : BaseContentPage public partial class LoginPage : BaseContentPage
{ {
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly LoginPageViewModel _vm; private readonly LoginPageViewModel _vm;
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
@@ -20,9 +17,6 @@ namespace Bit.App.Pages
public LoginPage(string email = null, AppOptions appOptions = null) public LoginPage(string email = null, AppOptions appOptions = null)
{ {
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions; _appOptions = appOptions;
InitializeComponent(); InitializeComponent();
_vm = BindingContext as LoginPageViewModel; _vm = BindingContext as LoginPageViewModel;
@@ -33,15 +27,19 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () => _vm.CloseAction = async () =>
{ {
_messagingService.Send("showStatusBar", false); await _accountListOverlay.HideAsync();
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
}; };
if (!string.IsNullOrWhiteSpace(email))
{
_email.IsEnabled = false;
}
else
{
_vm.ShowCancelButton = true;
}
_vm.Email = email; _vm.Email = email;
MasterPasswordEntry = _masterPassword; MasterPasswordEntry = _masterPassword;
if (Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
}
_email.ReturnType = ReturnType.Next; _email.ReturnType = ReturnType.Next;
_email.ReturnCommand = new Command(() => _masterPassword.Focus()); _email.ReturnCommand = new Command(() => _masterPassword.Focus());
@@ -54,6 +52,21 @@ namespace Bit.App.Pages
{ {
ToolbarItems.Add(_getPasswordHint); 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; } public Entry MasterPasswordEntry { get; set; }
@@ -61,6 +74,13 @@ namespace Bit.App.Pages
protected override async void OnAppearing() protected override async void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
if (!_appOptions?.HideAccountSwitcher ?? false)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
await _vm.InitAsync(); await _vm.InitAsync();
if (!_inputFocused) 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) private async void LogIn_Clicked(object sender, EventArgs e)
{ {
if (DoOnce()) 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()) if (DoOnce())
{ {
@@ -95,18 +141,25 @@ namespace Bit.App.Pages
private async void More_Clicked(object sender, System.EventArgs e) private async void More_Clicked(object sender, System.EventArgs e)
{ {
await _accountListOverlay.HideAsync();
if (!DoOnce()) if (!DoOnce())
{ {
return; return;
} }
var selection = await DisplayActionSheet(AppResources.Options, var buttons = _email.IsEnabled ? new[] { AppResources.GetPasswordHint }
AppResources.Cancel, null, AppResources.GetPasswordHint); : new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount };
var selection = await DisplayActionSheet(AppResources.Options,
AppResources.Cancel, null, buttons);
if (selection == AppResources.GetPasswordHint) if (selection == AppResources.GetPasswordHint)
{ {
await Navigation.PushModalAsync(new NavigationPage(new HintPage())); await Navigation.PushModalAsync(new NavigationPage(new HintPage()));
} }
else if (selection == AppResources.RemoveAccount)
{
await _vm.RemoveAccountAsync();
}
} }
private async Task StartTwoFactorAsync() 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.Resources;
using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class LoginPageViewModel : CaptchaProtectedViewModel public class LoginPageViewModel : CaptchaProtectedViewModel
{ {
private const string Keys_RememberedEmail = "rememberedEmail";
private const string Keys_RememberEmail = "rememberEmail";
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IEnvironmentService _environmentService; private readonly IEnvironmentService _environmentService;
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly IMessagingService _messagingService;
private readonly ILogger _logger;
private bool _showPassword; private bool _showPassword;
private bool _showCancelButton;
private string _email; private string _email;
private string _masterPassword; private string _masterPassword;
@@ -34,15 +34,22 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_authService = ServiceContainer.Resolve<IAuthService>("authService"); _authService = ServiceContainer.Resolve<IAuthService>("authService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService"); _environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService"); _i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
PageTitle = AppResources.Bitwarden; PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
LogInCommand = new Command(async () => await LogInAsync()); LogInCommand = new Command(async () => await LogInAsync());
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = true,
AllowActiveAccountSelection = true
};
} }
public bool ShowPassword public bool ShowPassword
@@ -55,6 +62,12 @@ namespace Bit.App.Pages
}); });
} }
public bool ShowCancelButton
{
get => _showCancelButton;
set => SetProperty(ref _showCancelButton, value);
}
public string Email public string Email
{ {
get => _email; get => _email;
@@ -67,10 +80,11 @@ namespace Bit.App.Pages
set => SetProperty(ref _masterPassword, value); set => SetProperty(ref _masterPassword, value);
} }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command LogInCommand { get; } public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public bool RememberEmail { get; set; }
public Action StartTwoFactorAction { get; set; } public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; } public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; } public Action UpdateTempPasswordAction { get; set; }
@@ -85,13 +99,11 @@ namespace Bit.App.Pages
{ {
if (string.IsNullOrWhiteSpace(Email)) 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) if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{ {
@@ -123,20 +135,27 @@ namespace Bit.App.Pages
ShowPassword = false; ShowPassword = false;
try 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) if (showLoading)
{ {
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn); await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
} }
var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken); var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken);
if (RememberEmail) await _stateService.SetRememberedEmailAsync(Email);
{
await _storageService.SaveAsync(Keys_RememberedEmail, Email);
}
else
{
await _storageService.RemoveAsync(Keys_RememberedEmail);
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (response.CaptchaNeeded) if (response.CaptchaNeeded)
@@ -163,8 +182,6 @@ namespace Bit.App.Pages
} }
else 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)); var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
LogInSuccessAction?.Invoke(); LogInSuccessAction?.Invoke();
} }
@@ -189,5 +206,34 @@ namespace Bit.App.Pages
entry.Focus(); entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length; 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 public partial class LoginSsoPage : BaseContentPage
{ {
private readonly IStorageService _storageService;
private readonly IMessagingService _messagingService;
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly LoginSsoPageViewModel _vm; private readonly LoginSsoPageViewModel _vm;
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
@@ -20,10 +18,7 @@ namespace Bit.App.Pages
public LoginSsoPage(AppOptions appOptions = null) public LoginSsoPage(AppOptions appOptions = null)
{ {
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions; _appOptions = appOptions;
InitializeComponent(); InitializeComponent();
_vm = BindingContext as LoginSsoPageViewModel; _vm = BindingContext as LoginSsoPageViewModel;
@@ -36,7 +31,6 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () => _vm.CloseAction = async () =>
{ {
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
}; };
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System; using System;
@@ -19,12 +18,11 @@ namespace Bit.App.Pages
{ {
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
private readonly IUserService _userService; private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IEnvironmentService _environmentService; private readonly IEnvironmentService _environmentService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IStorageService _storageService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IBiometricService _biometricService; private readonly IBiometricService _biometricService;
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
@@ -56,11 +54,11 @@ namespace Bit.App.Pages
new KeyValuePair<string, int?>(AppResources.Never, null), new KeyValuePair<string, int?>(AppResources.Never, null),
new KeyValuePair<string, int?>(AppResources.Custom, CustomVaultTimeoutValue), new KeyValuePair<string, int?>(AppResources.Custom, CustomVaultTimeoutValue),
}; };
private List<KeyValuePair<string, string>> _vaultTimeoutActions = private List<KeyValuePair<string, VaultTimeoutAction>> _vaultTimeoutActions =
new List<KeyValuePair<string, string>> new List<KeyValuePair<string, VaultTimeoutAction>>
{ {
new KeyValuePair<string, string>(AppResources.Lock, "lock"), new KeyValuePair<string, VaultTimeoutAction>(AppResources.Lock, VaultTimeoutAction.Lock),
new KeyValuePair<string, string>(AppResources.LogOut, "logOut"), new KeyValuePair<string, VaultTimeoutAction>(AppResources.LogOut, VaultTimeoutAction.Logout),
}; };
private Policy _vaultTimeoutPolicy; private Policy _vaultTimeoutPolicy;
@@ -70,12 +68,11 @@ namespace Bit.App.Pages
{ {
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService"); _cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_userService = ServiceContainer.Resolve<IUserService>("userService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService"); _environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService"); _biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService"); _policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
@@ -113,7 +110,7 @@ namespace Bit.App.Pages
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout(); _vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == _vaultTimeout).Key; _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; _vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync(); var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2; _pin = pinSet.Item1 || pinSet.Item2;
@@ -137,10 +134,10 @@ namespace Bit.App.Pages
#if DEBUG #if DEBUG
var pushNotificationsRegistered = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService").IsRegisteredForPush; var pushNotificationsRegistered = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService").IsRegisteredForPush;
var pnServerRegDate = await _storageService.GetAsync<DateTime>(Constants.PushLastRegistrationDateKey); var pnServerRegDate = await _stateService.GetPushLastRegistrationDateAsync();
var pnServerError = await _storageService.GetAsync<string>(Constants.PushInstallationRegistrationError); 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 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); 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; List<string> fingerprint;
try 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.") catch (Exception e) when (e.Message == "No public key available.")
{ {
@@ -194,12 +191,7 @@ namespace Bit.App.Pages
public void WebVault() public void WebVault()
{ {
var url = _environmentService.GetWebVaultUrl(); _platformUtilsService.LaunchUri(_environmentService.GetWebVaultUrl());
if (url == null)
{
url = "https://vault.bitwarden.com";
}
_platformUtilsService.LaunchUri(url);
} }
public async Task ShareAsync() public async Task ShareAsync()
@@ -218,7 +210,7 @@ namespace Bit.App.Pages
AppResources.TwoStepLogin, AppResources.Yes, AppResources.Cancel); AppResources.TwoStepLogin, AppResources.Yes, AppResources.Cancel);
if (confirmed) 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); AppResources.ChangeMasterPassword, AppResources.Yes, AppResources.Cancel);
if (confirmed) 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); AppResources.Yes, AppResources.No);
} }
var kdf = await _userService.GetKdfAsync(); var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync(); var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256), kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000)); kdfIterations.GetValueOrDefault(5000));
@@ -356,12 +348,12 @@ namespace Bit.App.Pages
if (masterPassOnRestart) if (masterPassOnRestart)
{ {
var encPin = await _cryptoService.EncryptAsync(pin); var encPin = await _cryptoService.EncryptAsync(pin);
await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString); await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
_vaultTimeoutService.PinProtectedKey = pinProtectedKey; await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
} }
else else
{ {
await _storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString); await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
} }
} }
else else
@@ -396,13 +388,13 @@ namespace Bit.App.Pages
if (_biometric) if (_biometric)
{ {
await _biometricService.SetupBiometricAsync(); await _biometricService.SetupBiometricAsync();
await _storageService.SaveAsync(Constants.BiometricUnlockKey, true); await _stateService.SetBiometricUnlockAsync(true);
} }
else else
{ {
await _storageService.RemoveAsync(Constants.BiometricUnlockKey); await _stateService.SetBiometricUnlockAsync(null);
} }
_vaultTimeoutService.BiometricLocked = false; await _stateService.SetBiometricLockedAsync(false);
await _cryptoService.ToggleKeyAsync(); await _cryptoService.ToggleKeyAsync();
BuildList(); BuildList();
} }
@@ -575,7 +567,7 @@ namespace Bit.App.Pages
return true; return true;
} }
private string GetVaultTimeoutActionFromKey(string key) private VaultTimeoutAction GetVaultTimeoutActionFromKey(string key)
{ {
return _vaultTimeoutActions.FirstOrDefault(o => o.Key == key).Value; return _vaultTimeoutActions.FirstOrDefault(o => o.Key == key).Value;
} }

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@@ -12,7 +11,7 @@ namespace Bit.App.Pages
{ {
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStorageService _storageService; private readonly IStateService _stateService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly ILocalizeService _localizeService; private readonly ILocalizeService _localizeService;
@@ -24,7 +23,7 @@ namespace Bit.App.Pages
{ {
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService"); _localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
@@ -52,7 +51,7 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
await SetLastSyncAsync(); await SetLastSyncAsync();
EnableSyncOnRefresh = await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey); EnableSyncOnRefresh = await _stateService.GetSyncOnRefreshAsync();
_inited = true; _inited = true;
} }
@@ -60,7 +59,7 @@ namespace Bit.App.Pages
{ {
if (_inited) 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.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
@@ -45,7 +46,7 @@ namespace Bit.App.Pages
var settingsPage = new NavigationPage(new SettingsPage(this)) var settingsPage = new NavigationPage(new SettingsPage(this))
{ {
Title = AppResources.Settings, Title = AppResources.Settings,
IconImageSource = "cog.png" IconImageSource = "cog_settings.png"
}; };
Children.Add(settingsPage); Children.Add(settingsPage);
@@ -102,8 +103,13 @@ namespace Bit.App.Pages
{ {
if (CurrentPage is NavigationPage navPage) if (CurrentPage is NavigationPage navPage)
{ {
if (_groupingsPage?.RootPage is GroupingsPage groupingsPage)
{
await groupingsPage.HideAccountSwitchingOverlayAsync();
}
_messagingService.Send("updatedTheme"); _messagingService.Send("updatedTheme");
if (navPage.RootPage is GroupingsPage groupingsPage) if (navPage.RootPage is GroupingsPage)
{ {
// Load something? // 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 <Entry
x:Name="_identityFirstNameEntry" x:Name="_identityFirstNameEntry"
Text="{Binding Cipher.Identity.FirstName}" Text="{Binding Cipher.Identity.FirstName}"
StyleClass="box-value" /> StyleClass="box-value,capitalize-word-input"/>
</StackLayout> </StackLayout>
<StackLayout StyleClass="box-row, box-row-input"> <StackLayout StyleClass="box-row, box-row-input">
<Label <Label
@@ -347,7 +347,7 @@
<Entry <Entry
x:Name="_identityMiddleNameEntry" x:Name="_identityMiddleNameEntry"
Text="{Binding Cipher.Identity.MiddleName}" Text="{Binding Cipher.Identity.MiddleName}"
StyleClass="box-value" /> StyleClass="box-value,capitalize-word-input" />
</StackLayout> </StackLayout>
<StackLayout StyleClass="box-row, box-row-input"> <StackLayout StyleClass="box-row, box-row-input">
<Label <Label
@@ -356,7 +356,7 @@
<Entry <Entry
x:Name="_identityLastNameEntry" x:Name="_identityLastNameEntry"
Text="{Binding Cipher.Identity.LastName}" Text="{Binding Cipher.Identity.LastName}"
StyleClass="box-value" /> StyleClass="box-value,capitalize-word-input" />
</StackLayout> </StackLayout>
<StackLayout StyleClass="box-row, box-row-input"> <StackLayout StyleClass="box-row, box-row-input">
<Label <Label
@@ -420,6 +420,7 @@
<Entry <Entry
x:Name="_identityPhoneEntry" x:Name="_identityPhoneEntry"
Text="{Binding Cipher.Identity.Phone}" Text="{Binding Cipher.Identity.Phone}"
Keyboard="Telephone"
StyleClass="box-value" /> StyleClass="box-value" />
</StackLayout> </StackLayout>
<StackLayout StyleClass="box-row, box-row-input"> <StackLayout StyleClass="box-row, box-row-input">
@@ -456,7 +457,7 @@
<Entry <Entry
x:Name="_identityCityEntry" x:Name="_identityCityEntry"
Text="{Binding Cipher.Identity.City}" Text="{Binding Cipher.Identity.City}"
StyleClass="box-value" /> StyleClass="box-value,capitalize-sentence-input" />
</StackLayout> </StackLayout>
<StackLayout StyleClass="box-row, box-row-input"> <StackLayout StyleClass="box-row, box-row-input">
<Label <Label
@@ -465,7 +466,7 @@
<Entry <Entry
x:Name="_identityStateEntry" x:Name="_identityStateEntry"
Text="{Binding Cipher.Identity.State}" Text="{Binding Cipher.Identity.State}"
StyleClass="box-value" /> StyleClass="box-value,capitalize-sentence-input" />
</StackLayout> </StackLayout>
<StackLayout StyleClass="box-row, box-row-input"> <StackLayout StyleClass="box-row, box-row-input">
<Label <Label
@@ -483,7 +484,7 @@
<Entry <Entry
x:Name="_identityCountryEntry" x:Name="_identityCountryEntry"
Text="{Binding Cipher.Identity.Country}" Text="{Binding Cipher.Identity.Country}"
StyleClass="box-value" /> StyleClass="box-value,capitalize-sentence-input" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,15 @@
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.ToolbarItems> <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" <ToolbarItem Icon="search.png" Clicked="Search_Clicked"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Search}" /> AutomationProperties.Name="{u:I18n Search}" />
@@ -48,9 +57,10 @@
<controls:ExtendedStackLayout Orientation="Horizontal" <controls:ExtendedStackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform"> StyleClass="list-row, list-row-platform">
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}" <controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
HorizontalOptions="Start" HorizontalOptions="Start"
VerticalOptions="Center" VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"> StyleClass="list-icon, list-icon-platform"
ShouldUpdateFontSizeDynamicallyForAccesibility="True">
<controls:IconLabel.Effects> <controls:IconLabel.Effects>
<effects:FixedSizeEffect /> <effects:FixedSizeEffect />
</controls:IconLabel.Effects> </controls:IconLabel.Effects>
@@ -135,6 +145,8 @@
AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"> AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView> </ContentView>
<!-- Android FAB -->
<Button <Button
x:Name="_fab" x:Name="_fab"
Image="plus.png" Image="plus.png"
@@ -148,6 +160,14 @@
<effects:FabShadowEffect /> <effects:FabShadowEffect />
</Button.Effects> </Button.Effects>
</Button> </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> </AbsoluteLayout>
</pages:BaseContentPage> </pages:BaseContentPage>

View File

@@ -1,14 +1,13 @@
using Bit.App.Abstractions; using System;
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.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -18,7 +17,7 @@ namespace Bit.App.Pages
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IPushNotificationService _pushNotificationService; private readonly IPushNotificationService _pushNotificationService;
private readonly IStorageService _storageService; private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService; private readonly ICipherService _cipherService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
@@ -37,7 +36,7 @@ namespace Bit.App.Pages
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService"); _pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService"); _cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@@ -70,6 +69,10 @@ namespace Bit.App.Pages
_absLayout.Children.Remove(_fab); _absLayout.Children.Remove(_fab);
ToolbarItems.Remove(_addItem); ToolbarItems.Remove(_addItem);
} }
if (!mainPage)
{
ToolbarItems.Remove(_accountAvatar);
}
} }
protected async override void OnAppearing() protected async override void OnAppearing()
@@ -80,6 +83,12 @@ namespace Bit.App.Pages
IsBusy = true; IsBusy = true;
} }
_accountAvatar?.OnAppearing();
if (_vm.MainPage)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
_broadcasterService.Subscribe(_pageName, async (message) => _broadcasterService.Subscribe(_pageName, async (message) =>
{ {
if (message.Command == "syncStarted") 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 () => await LoadOnAppearedAsync(_mainLayout, false, async () =>
{ {
if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any()) if (!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
@@ -123,18 +131,6 @@ namespace Bit.App.Pages
await _vm.LoadAsync(); 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(); await ShowPreviousPageAsync();
AdjustToolbar(); AdjustToolbar();
}, _mainContent); }, _mainContent);
@@ -145,14 +141,14 @@ namespace Bit.App.Pages
} }
// Push registration // Push registration
var lastPushRegistration = await _storageService.GetAsync<DateTime?>(Constants.PushLastRegistrationDateKey); var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync();
lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue); lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue);
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
{ {
var pushPromptShow = await _storageService.GetAsync<bool?>(Constants.PushInitialPromptShownKey); var pushPromptShow = await _stateService.GetPushInitialPromptShownAsync();
if (!pushPromptShow.GetValueOrDefault(false)) if (!pushPromptShow.GetValueOrDefault(false))
{ {
await _storageService.SaveAsync(Constants.PushInitialPromptShownKey, true); await _stateService.SetPushInitialPromptShownAsync(true);
await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert, await DisplayAlert(AppResources.EnableAutomaticSyncing, AppResources.PushNotificationAlert,
AppResources.OkGotIt); AppResources.OkGotIt);
} }
@@ -168,30 +164,26 @@ namespace Bit.App.Pages
{ {
await _pushNotificationService.RegisterAsync(); 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() protected override void OnDisappearing()
{ {
base.OnDisappearing(); base.OnDisappearing();
IsBusy = false; IsBusy = false;
_broadcasterService.Unsubscribe(_pageName); _broadcasterService.Unsubscribe(_pageName);
_vm.DisableRefreshing(); _vm.DisableRefreshing();
_accountAvatar?.OnDisappearing();
} }
private async void RowSelected(object sender, SelectionChangedEventArgs e) 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) private async void Search_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync();
if (DoOnce()) if (DoOnce())
{ {
var page = new CiphersPage(_vm.Filter, _vm.FolderId != null, _vm.CollectionId != null, 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) private async void Sync_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync();
await _vm.SyncAsync(); await _vm.SyncAsync();
} }
private async void Lock_Clicked(object sender, EventArgs e) private async void Lock_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync();
await _vaultTimeoutService.LockAsync(true, true); await _vaultTimeoutService.LockAsync(true, true);
} }
private async void Exit_Clicked(object sender, EventArgs e) private async void Exit_Clicked(object sender, EventArgs e)
{ {
await _accountListOverlay.HideAsync();
await _vm.ExitAsync(); await _vm.ExitAsync();
} }
private async void AddButton_Clicked(object sender, EventArgs e) 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()) if (!_vm.Deleted && DoOnce())
{ {
var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId); var page = new AddEditPage(null, _vm.Type, _vm.FolderId, _vm.CollectionId);
@@ -268,6 +271,7 @@ namespace Bit.App.Pages
{ {
return; return;
} }
await _accountListOverlay.HideAsync();
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId)) if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId))
{ {
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.CipherId))); await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.CipherId)));
@@ -284,5 +288,10 @@ namespace Bit.App.Pages
_addItem.IsEnabled = !_vm.Deleted; _addItem.IsEnabled = !_vm.Deleted;
_addItem.IconImageSource = _vm.Deleted ? null : "plus.png"; _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.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
@@ -40,14 +40,13 @@ namespace Bit.App.Pages
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService; private readonly ICollectionService _collectionService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IUserService _userService;
private readonly IVaultTimeoutService _vaultTimeoutService; private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IStorageService _storageService;
private readonly IPasswordRepromptService _passwordRepromptService; private readonly IPasswordRepromptService _passwordRepromptService;
private readonly ILogger _logger;
public GroupingsPageViewModel() public GroupingsPageViewModel()
{ {
@@ -55,14 +54,13 @@ namespace Bit.App.Pages
_folderService = ServiceContainer.Resolve<IFolderService>("folderService"); _folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService"); _collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"); _passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
Loading = true; Loading = true;
PageTitle = AppResources.MyVault; PageTitle = AppResources.MyVault;
@@ -73,6 +71,11 @@ namespace Bit.App.Pages
await LoadAsync(); await LoadAsync();
}); });
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync); CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = true
};
} }
public bool MainPage { get; set; } public bool MainPage { get; set; }
@@ -81,7 +84,6 @@ namespace Bit.App.Pages
public string CollectionId { get; set; } public string CollectionId { get; set; }
public Func<CipherView, bool> Filter { get; set; } public Func<CipherView, bool> Filter { get; set; }
public bool Deleted { get; set; } public bool Deleted { get; set; }
public bool HasCiphers { get; set; } public bool HasCiphers { get; set; }
public bool HasFolders { get; set; } public bool HasFolders { get; set; }
public bool HasCollections { get; set; } public bool HasCollections { get; set; }
@@ -140,6 +142,9 @@ namespace Bit.App.Pages
get => _websiteIconsEnabled; get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value); set => SetProperty(ref _websiteIconsEnabled, value);
} }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; } public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public Command RefreshCommand { get; set; } public Command RefreshCommand { get; set; }
public Command<CipherView> CipherOptionsCommand { get; set; } public Command<CipherView> CipherOptionsCommand { get; set; }
@@ -151,7 +156,7 @@ namespace Bit.App.Pages
{ {
return; return;
} }
var authed = await _userService.IsAuthenticatedAsync(); var authed = await _stateService.IsAuthenticatedAsync();
if (!authed) if (!authed)
{ {
return; return;
@@ -160,7 +165,7 @@ namespace Bit.App.Pages
{ {
return; return;
} }
if (await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey) && Refreshing && !SyncRefreshing) if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
{ {
SyncRefreshing = true; SyncRefreshing = true;
await _syncService.FullSyncAsync(false); await _syncService.FullSyncAsync(false);
@@ -176,8 +181,7 @@ namespace Bit.App.Pages
var groupedItems = new List<GroupingsPageListGroup>(); var groupedItems = new List<GroupingsPageListGroup>();
var page = Page as GroupingsPage; var page = Page as GroupingsPage;
WebsiteIconsEnabled = !(await _stateService.GetAsync<bool?>(Constants.DisableFaviconKey)) WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
.GetValueOrDefault();
try try
{ {
await LoadDataAsync(); await LoadDataAsync();

View File

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

View File

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

View File

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

View File

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

View File

@@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve"> <data name="LogoutConfirmation" xml:space="preserve">
<value>Is u seker u wil uitteken?</value> <value>Is u seker u wil uitteken?</value>
</data> </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"> <data name="MasterPassword" xml:space="preserve">
<value>Hoofwagwoord</value> <value>Hoofwagwoord</value>
<comment>Label for a master password.</comment> <comment>Label for a master password.</comment>
@@ -2092,6 +2104,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect"> <data name="DisablePersonalVaultExportPolicyInEffect">
<value>Een of meer organisasiebeleide verhoed u om u persoonlike kluis uit te stuur.</value> <value>Een of meer organisasiebeleide verhoed u om u persoonlike kluis uit te stuur.</value>
</data> </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"> <data name="DeleteAccount" xml:space="preserve">
<value>Skrap rekening</value> <value>Skrap rekening</value>
</data> </data>

View File

@@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve"> <data name="LogoutConfirmation" xml:space="preserve">
<value>Çıxış etmək istədiyinizə əminsiniz?</value> <value>Çıxış etmək istədiyinizə əminsiniz?</value>
</data> </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"> <data name="MasterPassword" xml:space="preserve">
<value>Ana parol</value> <value>Ana parol</value>
<comment>Label for a master password.</comment> <comment>Label for a master password.</comment>
@@ -2092,6 +2104,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect"> <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> <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>
<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"> <data name="DeleteAccount" xml:space="preserve">
<value>Hesabı sil</value> <value>Hesabı sil</value>
</data> </data>

View File

@@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve"> <data name="LogoutConfirmation" xml:space="preserve">
<value>Вы ўпэўнены, што хочаце выйсці?</value> <value>Вы ўпэўнены, што хочаце выйсці?</value>
</data> </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"> <data name="MasterPassword" xml:space="preserve">
<value>Асноўны пароль</value> <value>Асноўны пароль</value>
<comment>Label for a master password.</comment> <comment>Label for a master password.</comment>
@@ -2092,6 +2104,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect"> <data name="DisablePersonalVaultExportPolicyInEffect">
<value>One or more organization policies prevents your from exporting your personal vault.</value> <value>One or more organization policies prevents your from exporting your personal vault.</value>
</data> </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"> <data name="DeleteAccount" xml:space="preserve">
<value>Delete Account</value> <value>Delete Account</value>
</data> </data>

View File

@@ -275,6 +275,18 @@
<data name="LogoutConfirmation" xml:space="preserve"> <data name="LogoutConfirmation" xml:space="preserve">
<value>Сигурни ли сте, че искате да се отпишете?</value> <value>Сигурни ли сте, че искате да се отпишете?</value>
</data> </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"> <data name="MasterPassword" xml:space="preserve">
<value>Главна парола</value> <value>Главна парола</value>
<comment>Label for a master password.</comment> <comment>Label for a master password.</comment>
@@ -2093,6 +2105,21 @@
<data name="DisablePersonalVaultExportPolicyInEffect"> <data name="DisablePersonalVaultExportPolicyInEffect">
<value>Една или повече от настройките на организацията Ви не позволяват да изнасяте личния си трезор.</value> <value>Една или повече от настройките на организацията Ви не позволяват да изнасяте личния си трезор.</value>
</data> </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"> <data name="DeleteAccount" xml:space="preserve">
<value>Изтриване на регистрацията</value> <value>Изтриване на регистрацията</value>
</data> </data>

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