1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-24 21:33:23 +00:00

Compare commits

...

102 Commits

Author SHA1 Message Date
Federico Maccaroni
a4e599f680 WIP Fix toolbar item avatar update issue iOS 2022-01-21 11:08:12 -03:00
Matt Portune
d41e1a0bc3 logout fixes and token handling improvements 2022-01-19 16:15:09 -05:00
Matt Portune
e717992b2b account list status enhancements 2022-01-19 12:06:45 -05:00
Matt Portune
d52a1bf668 Merge branch 'master' into feature-accountswitch 2022-01-19 09:31:13 -05:00
Jake Fink
6f3999016f Supress lock and logout when showing fileswitcher on Android (#1626)
* Supress lock and logout when showing fileswitcher on Android

* convert suppress bool to delay long
- move HandleVaultTimeoutAsync to vaultTimeoutService
2022-01-19 09:09:30 -05:00
Matt Portune
796cf6dc25 vault timeout fixes 2022-01-18 22:55:22 -05:00
Matt Portune
59e7969856 cleanup 2022-01-18 19:31:32 -05:00
Matt Portune
afecc1f569 fixes 2022-01-18 17:43:20 -05:00
Matt Portune
a3922f7fbb fixes 2022-01-18 17:21:24 -05:00
Matt Portune
7e288c62f9 switch fixes 2022-01-18 15:20:16 -05:00
Matt Portune
ed2bb9b94e fixes for theme handling on account switching and re-adding existing account 2022-01-18 14:50:04 -05:00
Matt Portune
1c6a8964fe fixed Send tests 2022-01-18 13:51:09 -05:00
Matt Portune
6aa96b8964 Merge branch 'master' into feature-accountswitch 2022-01-18 10:36:54 -05:00
Matt Portune
f6d4b0a443 updates to send test logic 2022-01-18 10:28:34 -05:00
Federico Maccaroni
2791d4b8ec Fixes for iOS push notifications (#1708)
* WIP Fixes for iOS push notifications

* WIP Fixes for iOS push notifications, fix missed implementation on android

* Fix some issues on the push notifications, changed to Debug Console.WriteLine, and added update on entitlements on the build.yml
2022-01-18 11:52:08 -03:00
Matt Portune
e20ae26808 wip 2022-01-17 23:15:32 -05:00
Matt Portune
e3a8d3bb55 Merge branch 'master' into feature-accountswitch 2022-01-17 21:59:39 -05:00
Matt Portune
65739489a7 wip 2022-01-17 21:58:04 -05:00
Indranil012
42403210a0 Added kiwi Browser "Dev" package name for autofill support (#1694)
* Added Kiwi Browser Dev support
2022-01-17 22:10:33 +00:00
Matt Portune
120fb70039 WIP 2022-01-17 00:41:14 -05:00
Matt Portune
d1ece79d2e Merge branch 'master' into feature-accountswitch 2022-01-16 22:48:04 -05:00
Matt Portune
ebae2585f6 Account switching 2022-01-16 22:43:37 -05:00
Vince Grassia
5e15a2f30e Update Version Bump action (#1717) 2022-01-12 16:07:01 -05:00
github-actions[bot]
c6547771a5 Autosync the updated translations (#1709)
Co-authored-by: github-actions <>
2022-01-07 13:51:27 +01:00
Federico Maccaroni
9fdcba386e Possible fix for iOS crash after long time idling (#1700) 2022-01-03 12:46:42 -03:00
github-actions[bot]
86397a6f1e Autosync the updated translations (#1705)
Co-authored-by: github-actions <>
2022-01-01 17:52:21 +01:00
github-actions[bot]
4c2e7331e3 Autosync the updated translations (#1699)
Co-authored-by: github-actions <>
2021-12-24 01:12:47 +01:00
Micaiah Martin
b55a450f44 Added logic for version check step (#1695) 2021-12-20 13:19:46 -07:00
Federico Maccaroni
b28e265ed4 Updated Delete account view UI (#1692) 2021-12-20 12:31:12 -03:00
github-actions[bot]
6164c764b4 Autosync the updated translations (#1693)
Co-authored-by: github-actions <>
2021-12-17 01:25:55 +01:00
Jake Fink
ad3b401ed3 remove re-throws of exceptions, hiding stack trace (#1680)
* remove re-throws of exceptions, hiding stack trace

* revert to catch all ApiExceptions

* add back throw in auditService

* whitespace
2021-12-16 15:34:33 -05:00
Federico Maccaroni
adb8bb4f1b Fix by workaround crash on LabelRenderer and when changing themes #1689 (#1690) 2021-12-16 11:36:06 -03:00
Federico Maccaroni
04c7409418 Fix Unsafe deserialization of Parcel data Intent (#1691)
* Fix crash produced by unsafe deserialization of Parcel data passed on the intent

* Fix crash produced by unsafe deserialization of Parcel data passed on the intent on other activities and renamed intent extension method
2021-12-15 15:09:08 -03:00
Federico Maccaroni
705b8ac12b Fix Clipboard clear after time on iOS (#1679)
* Fixed Clipboard clear after x seconds depending on what the user set. Also refactored a bit to make the Clipboard a custom service to provide a better way to handle this situation #1464

* Clear some usings #1464
2021-12-10 17:41:36 -03:00
Jake Fink
23a164b245 include entitlements in ios.extension simulator builds (#1684) 2021-12-10 10:46:40 -05:00
github-actions[bot]
6f936343ae Bumped version to 2.15.1 (#1683)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2021-12-10 10:03:06 -05:00
github-actions[bot]
5eeec7d9ed Autosync the updated translations (#1681)
Co-authored-by: github-actions <>
2021-12-10 01:18:44 +01:00
github-actions[bot]
b95efae7fb Bumped version to 2.15.0 (#1676)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2021-12-08 09:57:29 -05:00
github-actions[bot]
4a1f28caf8 Autosync the updated translations (#1661)
Co-authored-by: github-actions <>
2021-12-07 17:03:34 +01:00
Jake Fink
dddc38ef64 move splash screen logic to OnResignActivation (#1674) 2021-12-07 09:45:05 -05:00
Jake Fink
05bcc10277 remove "singleTask" launch mode on Android 11+ (#1673)
* remove "singleTask" launch mode on Android 12+

* remove commented and unneccesary code

* fix formatting and change from Android 12 to Android 11
2021-12-06 14:17:56 -05:00
Micaiah Martin
ea1ee2c3d3 Added version bump workflow (#1669) 2021-11-30 15:53:18 -07:00
Jake Fink
2a373dd3fc only prompt for sso if using key connector (#1667) 2021-11-29 15:37:19 -05:00
Federico Maccaroni
14d2b833d8 Fix crash produced when adding a custom field on a Secure Note, because it try to load the control of linked fields even if it's not the type (#1668) 2021-11-29 15:33:29 -03:00
Federico Maccaroni
9fdf2ada6f Added account deletion feature on settings (#1621)
* Added account deletion feature on settings

* Disabled using Microsoft.AppCenter.Crashes for FDroid

* Moved drawable on Android.csproj to be with the others

Co-authored-by: Federico Maccaroni <fmaccaroni@bitwarden.com>
2021-11-24 16:09:39 -03:00
qflair
833103b2a0 Add support for Pluma Browser for Android (#1639) 2021-11-24 10:12:30 -05:00
Jonathan Almeida
6bae85b22d Update Focus/Klar accessibility and autofill IDs (#1535) 2021-11-24 09:44:10 -05:00
Jake Fink
34dfb0b57e Add workaround to Android entry renderer (#1658) 2021-11-23 08:18:31 -05:00
Thomas Rittson
ff35e3c022 [Key Connector] Hide MP input in iOS extensions (#1656)
* Hide MP on iOS unlock screen

* Update navbar if using biometric auth only

* Tidy up logic
2021-11-23 09:50:34 +10:00
Jake Fink
316cb4d21c ui changes for lock screen if using key connector with biometrics (#1654) 2021-11-19 17:25:19 -05:00
Federico Maccaroni
7d42d19ae3 Revert "Add Share app Extension on iOS for Send (#1647)" (#1655)
This reverts commit 75ed72f91b.
2021-11-19 17:00:54 -03:00
Federico Maccaroni
75ed72f91b Add Share app Extension on iOS for Send (#1647)
* WIP Add Share app extension on iOS for Send

* Added Share app extension on iOS for Send and some code fixes as well

* Updated iOS csprojs configs to linkskip the new extension project and also added AdHoc and AppStore configurations to iOS.ShareExtension.csproj

* Code clean up and transformed bundle resources into links to the already used pngs of the main iOS project on ShareExtension

* Updated build.yml to include provisioning profile for iOS Share extension

* Adding in the missing provisioning profile

* Removed .DS_Store from the iOS.ShareExtension csproj Resources

* switching out the share extension profile

* Added Share extension provisioning profile configuration on export options app store for github and also removed custom info.plist config for localhost which is not necessary

Co-authored-by: Joseph Flinn <joseph.s.flinn@gmail.com>
2021-11-19 15:05:00 -03:00
Jake Fink
e9b0bbb3a9 Bug/cme autofill unlock (#1653)
* Show SSO login if using key-connector without bio or pin

* remove additional call to enable biometrics and change method name
- ordered methods to group private and public

* allow sso for first biometric authenitcation
2021-11-19 11:24:48 -05:00
Jake Fink
94994af4a9 ui changes export vault screen for key connector (#1651)
* ui changes export vault screen for key connector

* update label and remove period
2021-11-19 09:44:01 -05:00
github-actions[bot]
68c484b67f Autosync the updated translations (#1652)
Co-authored-by: github-actions <>
2021-11-19 13:04:21 +01:00
Federico Maccaroni
1b60ac3699 Fixed missing FavIcons on Android #1640 (#1649)
* Set custom HttpClient for FFImageLoading to set AndroidClientHandler in order to load icons from icon server due to a problem with the SSL certificate when using the default HttpClientHandler #1640

* Using cleanup for FDroid  #1640

* Added System.Net.Http on Android.csproj for FDroid only to check if that fixes the build
2021-11-18 17:38:23 -03:00
Thomas Rittson
1c006d6218 Hide Master Pass On Restart for Key Connector (#1650) 2021-11-19 05:01:44 +10:00
Jake Fink
3e0e620bb7 Show SSO login if using key-connector without bio or pin (#1648)
* Show SSO login if using key-connector without bio or pin

* remove additional call to enable biometrics and change method name
- ordered methods to group private and public
2021-11-17 12:44:27 -05:00
github-actions[bot]
14177efdda Autosync the updated translations (#1642)
Co-authored-by: github-actions <>
2021-11-16 10:58:30 -05:00
Jake Fink
3ee80beda8 pass OrgId to SSO login while using CME (#1646) 2021-11-16 09:34:26 -05:00
Jake Fink
13869b5a1b [KeyConnector] Add support for key connector OTP (#1633)
* initial commit
- add UsesKeyConnector to UserService
- add models
- begin work on authentication

* finish auth workflow for key connector sso login
- finish api call for get user key
- start api calls for posts to key connector

* Bypass lock page if already unlocked

* Move logic to KeyConnectorService, log out if no pin or biometric is set

* Disable password reprompt when using key connector

* hide password reprompt checkbox when editing or adding cipher

* add PostUserKey and PostSetKeyConnector calls

* add ConvertMasterPasswordPage

* add functionality to RemoveMasterPasswordPage
- rename Convert to Remove

* Hide Change Master Password button if using key connector

* Add OTP verification for export component

* Update src/App/Pages/Vault/AddEditPage.xaml.cs

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* remove toolbar item "close"

* Update src/Core/Models/Request/KeyConnectorUserKeyRequest.cs

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* remove new line in resource string
- format warning as two labels
- set label in code behind for loading simultaneously

* implement GetAndSetKey in KeyConnectorService
- ignore EnvironmentService call

* remove unnecesary orgIdentifier

* move RemoveMasterPasswordPage call to LockPage

* add spacing to export vault page

* log out if no PIN or bio on lock page with key connector

* Delete excessive whitespace

* Delete excessive whitespace

* Change capitalisation of OTP

* add default value to models for backwards compatibility

* remove this keyword

* actually handle exceptions

* move RemoveMasterPasswordPage to TabPage using messaging service

* add minor improvements

* remove 'this.'

Co-authored-by: Hinton <oscar@oscarhinton.com>
Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2021-11-10 20:46:48 -05:00
Thomas Rittson
90b62d61ae [Linked fields] Add Linked Field as a custom field type (#1563)
* Add linked fields support

* Fix style, don't show linked field if Secure Note

* Finish basic linked fields for Login

* Use Field.LinkedId to store linked field info

* Reset Linked Custom Fields if cipherType changes

* Refactor to use ItemView class

* Use enum for LinkedId

* Detect if no linkedFieldOptions
2021-11-09 07:34:16 +10:00
Vince Grassia
3cb8adeeff Change Release workflow to allow releases from 'rc' and 'hotfix' branches (#1632) 2021-11-08 09:48:40 -05:00
Joseph Flinn
5b972eec24 fixing a typo (#1634) 2021-11-05 11:38:51 -07:00
Joseph Flinn
e97ac1dd9b Updating the crowdin sync process (#1630) 2021-11-05 10:59:15 -07:00
Jake Fink
df496e39ff load app resources into ThemeManager before appearance adjustments (#1631) 2021-11-05 09:51:48 -04:00
Joseph Flinn
dbf94c1b56 Updating gh-pages (#1628) 2021-11-04 09:29:07 -07:00
Matt Portune
4b0fb2840e bump version for testflight (#1629) 2021-11-03 13:04:54 -04:00
stevenlele
629c696c81 [SupportedBrowsers] Add Captive Login and drop Alook (#1625) 2021-11-02 09:17:10 -04:00
Joseph Flinn
bf1aa7c4eb Version bump 2.14.2 (#1622) 2021-10-29 12:50:11 -07:00
Jake Fink
318a3e4de9 fix for bug stopping vault timeout to never (#1618)
- use nullable int on settings page and in vault service
2021-10-29 10:31:38 -04:00
Makoto Kato
0f992d27b3 Turn off autofill compatibility mode on the latest Firefox and Firefox beta. (#1592) 2021-10-28 14:44:24 -04:00
Jake Fink
83fd6736f6 add date and time formatting methods to localize service (#1616)
- uses Apple APIs for formatting on iOS
- uses .Net APIs for formatting Android
- implemented across project
- remove unnecesary calls to DateTimeConverter
2021-10-28 12:52:41 -04:00
Matt Portune
397250368a remove numeric restriction on cc number field (#1617) 2021-10-28 12:52:04 -04:00
Joseph Flinn
5e4365084b Version bump 2.14.1 (#1614) 2021-10-28 06:50:13 -07:00
Joseph Flinn
ea5e4aafa3 adding the missing flag for sed in the fdroid build (#1613) 2021-10-28 06:49:29 -07:00
Joseph Flinn
69d1de47c6 Fixing release template name (#1611) 2021-10-27 13:10:21 -07:00
Joseph Flinn
0d3f819e93 Version Bump 2.14.0 (#1610) 2021-10-27 08:40:08 -07:00
github-actions[bot]
3760e0f9f4 Autosync the updated translations (#1609)
Co-authored-by: github-actions <>
2021-10-27 07:52:31 -07:00
Thomas Rittson
5a13cb53ba Add PR template (#1608) 2021-10-27 18:59:59 +10:00
Jake Fink
0e9cbe4539 add reveal button to password reprompt on iOS (#1607)
* add reveal button to password reprompt on iOS

* format special chars as unicode
2021-10-26 17:46:11 -04:00
Federico Maccaroni
b8c1107c94 Fixed long secure notes edition scrolling when focused issue (#1257) (#1601)
* Fixed long secure notes edition scrolling when focused issue (#1257)

* Improved fix long secure notes edition scrolling when focused issue to not use a new editor custom renderer but an effect (#1257)

* Fixed long editor, on text and notes on send when scrolling when focused issue (#1257)
2021-10-25 16:28:45 -03:00
Federico Maccaroni
a07ef1a1d6 Fix html labels colors issue (#1516) (#1603) 2021-10-25 16:28:14 -03:00
Joseph Flinn
99ccd62bcd Release branch constraint on build (#1599)
* adding in the release branch constraint to the build workflow for mobile

* moving the branch check to a setup job
2021-10-22 13:16:57 -07:00
Joseph Flinn
bfb050a6f9 Change release branch contraint (#1598)
* removing the master branch release ci code execution

* updating some verbiage
2021-10-22 09:24:48 -07:00
Matt Portune
4e0b05571d utilize iOS safe area in UI (#1597) 2021-10-22 10:06:17 -04:00
Matt Portune
d93d70fd66 Autofill support for upcoming changes to google search (#1596) 2021-10-21 10:45:48 -04:00
Matt Portune
41098ff05b fix gap in background color application resulting in flashing during transitions, part 2 (#1595) 2021-10-18 09:56:20 -04:00
Matt Portune
4ed7491116 fix for crash when checking for running accessibility service without activity (#1591) 2021-10-18 09:56:12 -04:00
Matt Portune
1ebad6bca5 fix for crash when terminating app (#1589) 2021-10-16 07:56:17 -04:00
Matt Portune
48e3986264 fix gap in theme application resulting in flashing during transitions (#1588) 2021-10-15 15:46:24 -04:00
Vince Grassia
88a1d8d4e8 Add notify constraint (#1587) 2021-10-15 13:06:53 -04:00
Jake Fink
f3ff991abe check password for null before setting cursor position (#1586) 2021-10-15 10:56:12 -04:00
Kyle Spearrin
17b89dc21c New Crowdin updates (#1583)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Norwegian Nynorsk)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Tamil)

* New translations AppResources.resx (Bengali)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Latvian)

* New translations AppResources.resx (Azerbaijani)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Filipino)

* New translations AppResources.resx (Malayalam)

* New translations AppResources.resx (Bosnian)

* New translations AppResources.resx (Sinhala)

* New translations AppResources.resx (Kannada)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Slovenian)

* New translations AppResources.resx (Serbian (Cyrillic))

* New translations AppResources.resx (English, India)

* New translations copy.resx (English, United Kingdom)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (English, India)

* New translations copy.resx (English, United Kingdom)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (English, India)
2021-10-14 19:22:33 -04:00
github-actions[bot]
ff76a3ec15 Autosync the updated translations (#1582)
Co-authored-by: github-actions <>
2021-10-14 18:04:06 -04:00
Vince Grassia
3a2e012c42 Add Slack alerts for Build workflow failures (#1581) 2021-10-14 14:34:24 -04:00
Matt Portune
a0bb16c35f fix for crash on startup when policies are non-existent (#1579) 2021-10-14 13:53:57 -04:00
Jake Fink
62a8d1c017 fix for Captcha crashing on account creation (#1580) 2021-10-14 13:42:48 -04:00
Matt Portune
ce4e3ed1cd support for new btnReturnText in mobile webauthn connector (#1576)
* support for new btnReturnText in mobile webauthn connector

* added header text for connector localization
2021-10-14 11:53:41 -04:00
Matt Portune
4669275680 Fix for Android 5.x crash caused by new switch styling (#1575) 2021-10-13 12:26:54 -04:00
Jake Fink
fc1000acc1 loop through URIs to find website before showing default icons (#1572)
- create static class for uri logic (keeping converter for future)
- create new property in viewmodel for icon source
- check if icons are enabled and source for icon before showing default glyph
2021-10-12 11:00:33 -04:00
Oscar Hinton
c9ce7256e5 MaximumVaultTimeout policy fixes (#1573) 2021-10-12 15:35:01 +02:00
322 changed files with 20648 additions and 3898 deletions

32
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,32 @@
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why
## Screenshots
<!--Required for any UI changes. Delete if not applicable-->
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@@ -23,9 +23,38 @@ jobs:
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
setup:
name: Setup
runs-on: ubuntu-20.04
outputs:
rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }}
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Check if special branches exist
id: branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
echo "::set-output name=rc_branch_exists::1"
else
echo "::set-output name=rc_branch_exists::0"
fi
if [[ $(git ls-remote --heads origin hotfix) ]]; then
echo "::set-output name=hotfix_branch_exists::1"
else
echo "::set-output name=hotfix_branch_exists::0"
fi
shell: bash
android:
name: Android
runs-on: windows-2019
needs: setup
steps:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
@@ -147,20 +176,13 @@ jobs:
path: ./com.x8bit.bitwarden.apk
if-no-files-found: error
- name: Check if RC branch exists
id: rc-branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
echo "::set-output name=branch_exists::1"
else
echo "::set-output name=branch_exists::0"
fi
shell: bash
- name: Deploy to Play Store
if: |
(github.ref == 'refs/heads/master' && steps.rc-branch-check.outputs.branch_exists == 0)
|| github.ref == 'refs/heads/rc'
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix'
run: |
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json"
@@ -207,7 +229,7 @@ jobs:
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./src/Android/Properties/AndroidManifest.xml
shell: bash
@@ -331,6 +353,7 @@ jobs:
ios:
name: Apple iOS
runs-on: macos-11
needs: setup
steps:
- name: Print environment
run: |
@@ -374,6 +397,15 @@ jobs:
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
shell: bash
- name: Update Entitlements
run: |
echo "########################################"
echo "##### Updating Entitlements"
echo "########################################"
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>production<\/string>/' ./src/iOS/Entitlements.plist
shell: bash
- name: Set up Keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
@@ -447,20 +479,13 @@ jobs:
path: ./bitwarden-export/Bitwarden.ipa
if-no-files-found: error
- name: Check if RC branch exists
id: rc-branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
echo "::set-output name=branch_exists::1"
else
echo "::set-output name=branch_exists::0"
fi
shell: bash
- name: Deploy to App Store
if: |
(github.ref == 'refs/heads/master' && steps.rc-branch-check.outputs.branch_exists == 0)
|| github.ref == 'refs/heads/rc'
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix'
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
@@ -468,3 +493,99 @@ jobs:
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash
crowdin-push:
name: Crowdin Push
if: github.ref == 'refs/heads/master'
needs:
- android
- f-droid
- ios
runs-on: ubuntu-20.04
env:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
with:
config: crowdin.yml
crowdin_branch_name: master
upload_sources: true
upload_translations: false
check-failures:
name: Check for failures
if: always()
runs-on: ubuntu-20.04
needs:
- cloc
- android
- f-droid
- ios
- crowdin-push
steps:
- name: Check if any job failed
if: |
(github.ref == 'refs/heads/master')
|| (github.ref == 'refs/heads/rc')
|| (github.ref == 'refs/heads/hotfix')
env:
CLOC_STATUS: ${{ needs.cloc.result }}
ANDROID_STATUS: ${{ needs.android.result }}
F_DROID_STATUS: ${{ needs.f-droid.result }}
IOS_STATUS: ${{ needs.ios.result }}
CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }}
run: |
if [ "$CLOC_STATUS" = "failure" ]; then
exit 1
elif [ "$ANDROID_STATUS" = "failure" ]; then
exit 1
elif [ "$F_DROID_STATUS" = "failure" ]; then
exit 1
elif [ "$IOS_STATUS" = "failure" ]; then
exit 1
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - Prod Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
if: failure()
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
if: failure()
with:
keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

View File

@@ -4,8 +4,8 @@ name: Crowdin Sync
on:
workflow_dispatch:
inputs: {}
# schedule:
# - cron: '0 0 * * *'
schedule:
- cron: '0 0 * * 5'
jobs:
crowdin-sync:

View File

@@ -3,16 +3,34 @@ name: Release
on:
workflow_dispatch:
inputs:
release_type:
description: 'Release Options'
required: true
default: 'Initial Release'
type: choice
options:
- Initial Release
- Redeploy
jobs:
release:
name: Create Release
runs-on: ubuntu-20.04
outputs:
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
- name: Branch check
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then
echo "==================================="
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
echo "==================================="
exit 1
fi
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: rc
- name: Retrieve Mobile release version
id: retrieve-mobile-version
@@ -22,6 +40,7 @@ jobs:
shell: bash
- name: Check to make sure Mobile release version has been bumped
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
@@ -36,12 +55,18 @@ jobs:
fi
shell: bash
- name: Get branch name
id: branch
run: |
BRANCH_NAME=$(basename ${{ github.ref }})
echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Download all artifacts
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
with:
workflow: build.yml
workflow_conclusion: success
branch: rc
branch: ${{ steps.branch.outputs.branch-name }}
- name: Create release
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
@@ -52,7 +77,7 @@ jobs:
./Bitwarden.ipa/Bitwarden.ipa"
commit: ${{ github.sha }}
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
name: Test Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
body: "<insert release notes here>"
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
@@ -65,15 +90,13 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
with:
ref: rc
- name: Download F-Droid .apk artifact
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
with:
workflow: build.yml
workflow_conclusion: success
branch: rc
branch: ${{ needs.release.outputs.branch-name }}
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node

83
.github/workflows/version-bump.yml vendored Normal file
View File

@@ -0,0 +1,83 @@
---
name: Version Bump
on:
workflow_dispatch:
inputs:
version_number:
description: "New Version"
required: true
jobs:
bump_version:
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
runs-on: ubuntu-20.04
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Create Version Branch
run: |
git switch -c version_bump_${{ github.event.inputs.version_number }}
git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Checkout Version Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
with:
ref: version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/Android/Properties/AndroidManifest.xml"
- name: Bump Version - iOS.Autofill
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.Autofill/Info.plist"
- name: Bump Version - iOS.Extension
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.Extension/Info.plist"
- name: Bump Version - iOS
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS/Info.plist"
- name: Commit files
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
- name: Create Version PR
env:
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
BASE_BRANCH: master
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
run: |
gh pr create --title "$TITLE" \
--base "$BASE" \
--head "$PR_BRANCH" \
--label "version update" \
--label "automated pr" \
--body "
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [X] Other
## Objective
Automated version bump to ${{ github.event.inputs.version_number }}"

View File

@@ -1,7 +1,9 @@
project_id_env: _CROWDIN_PROJECT_ID
api_token_env: CROWDIN_API_TOKEN
preserve_hierarchy: true
files:
- source: /src/App/Resources/AppResources.resx
dest: /src/App/Resources/%original_file_name%
translation: /src/App/Resources/AppResources.%two_letters_code%.resx
update_option: update_as_unapproved
languages_mapping:
@@ -13,6 +15,7 @@ files:
en-GB: en-GB
en-IN: en-IN
- source: /store/apple/en/copy.resx
dest: /store/apple/en/%original_file_name%
translation: /store/apple/%two_letters_code%/copy.resx
update_option: update_as_unapproved
languages_mapping:
@@ -24,6 +27,7 @@ files:
en-GB: en-GB
en-IN: en-IN
- source: /store/google/en/copy.resx
dest: /store/google/en/%original_file_name%
translation: /store/google/%two_letters_code%/copy.resx
update_option: update_as_unapproved
languages_mapping:

710
package-lock.json generated
View File

@@ -1,8 +1,468 @@
{
"name": "bitwarden-mobile",
"version": "0.0.0",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitwarden-mobile",
"version": "0.0.0",
"devDependencies": {
"gh-pages": "^3.2.3"
}
},
"node_modules/array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
"dev": true,
"dependencies": {
"array-uniq": "^1.0.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/array-uniq": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"dependencies": {
"lodash": "^4.17.14"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"node_modules/email-addresses": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz",
"integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==",
"dev": true
},
"node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/filename-reserved-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
"integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/filenamify": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
"integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
"dev": true,
"dependencies": {
"filename-reserved-regex": "^2.0.0",
"strip-outer": "^1.0.1",
"trim-repeated": "^1.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/find-cache-dir": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
"integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
"dev": true,
"dependencies": {
"commondir": "^1.0.1",
"make-dir": "^3.0.2",
"pkg-dir": "^4.1.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
}
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"node_modules/gh-pages": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz",
"integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==",
"dev": true,
"dependencies": {
"async": "^2.6.1",
"commander": "^2.18.0",
"email-addresses": "^3.0.1",
"filenamify": "^4.3.0",
"find-cache-dir": "^3.3.1",
"fs-extra": "^8.1.0",
"globby": "^6.1.0"
},
"bin": {
"gh-pages": "bin/gh-pages.js",
"gh-pages-clean": "bin/gh-pages-clean.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/globby": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
"dev": true,
"dependencies": {
"array-union": "^1.0.1",
"glob": "^7.0.3",
"object-assign": "^4.0.1",
"pify": "^2.0.0",
"pinkie-promise": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/graceful-fs": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
"dev": true
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"dev": true,
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true,
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/pinkie-promise": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
"dev": true,
"dependencies": {
"pinkie": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
"dev": true,
"dependencies": {
"find-up": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/strip-outer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
"integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
"dev": true,
"dependencies": {
"escape-string-regexp": "^1.0.2"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/trim-repeated": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
"integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
"dev": true,
"dependencies": {
"escape-string-regexp": "^1.0.2"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
},
"dependencies": {
"array-union": {
"version": "1.0.2",
@@ -20,18 +480,18 @@
"dev": true
},
"async": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"dev": true,
"requires": {
"lodash": "^4.17.10"
"lodash": "^4.17.14"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"brace-expansion": {
@@ -45,9 +505,15 @@
}
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"concat-map": {
@@ -56,6 +522,12 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"email-addresses": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz",
"integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -63,39 +535,50 @@
"dev": true
},
"filename-reserved-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz",
"integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
"integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=",
"dev": true
},
"filenamify": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz",
"integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
"integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
"dev": true,
"requires": {
"filename-reserved-regex": "^1.0.0",
"strip-outer": "^1.0.0",
"filename-reserved-regex": "^2.0.0",
"strip-outer": "^1.0.1",
"trim-repeated": "^1.0.0"
}
},
"filenamify-url": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz",
"integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=",
"find-cache-dir": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
"integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
"dev": true,
"requires": {
"filenamify": "^1.0.0",
"humanize-url": "^1.0.0"
"commondir": "^1.0.1",
"make-dir": "^3.0.2",
"pkg-dir": "^4.1.0"
}
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"fs-extra": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
@@ -107,24 +590,24 @@
"dev": true
},
"gh-pages": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-1.2.0.tgz",
"integrity": "sha512-cGLYAvxtlQ1iTwAS4g7FreZPXoE/g62Fsxln2mmR19mgs4zZI+XJ+wVVUhBFCF/0+Nmvbq+abyTWue1m1BSnmg==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz",
"integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==",
"dev": true,
"requires": {
"async": "2.6.1",
"commander": "2.15.1",
"filenamify-url": "^1.0.0",
"fs-extra": "^5.0.0",
"globby": "^6.1.0",
"graceful-fs": "4.1.11",
"rimraf": "^2.6.2"
"async": "^2.6.1",
"commander": "^2.18.0",
"email-addresses": "^3.0.1",
"filenamify": "^4.3.0",
"find-cache-dir": "^3.3.1",
"fs-extra": "^8.1.0",
"globby": "^6.1.0"
}
},
"glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -149,21 +632,11 @@
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
"dev": true
},
"humanize-url": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz",
"integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=",
"dev": true,
"requires": {
"normalize-url": "^1.0.0",
"strip-url-auth": "^1.0.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -175,15 +648,9 @@
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"is-plain-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"jsonfile": {
@@ -195,12 +662,30 @@
"graceful-fs": "^4.1.6"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true,
"requires": {
"semver": "^6.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -210,18 +695,6 @@
"brace-expansion": "^1.1.7"
}
},
"normalize-url": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
"integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
"dev": true,
"requires": {
"object-assign": "^4.0.1",
"prepend-http": "^1.0.0",
"query-string": "^4.1.0",
"sort-keys": "^1.0.0"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -237,6 +710,36 @@
"wrappy": "1"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -264,44 +767,19 @@
"pinkie": "^2.0.0"
}
},
"prepend-http": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
"dev": true
},
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
"dev": true,
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
"find-up": "^4.0.0"
}
},
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"sort-keys": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
"dev": true,
"requires": {
"is-plain-obj": "^1.0.0"
}
},
"strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"strip-outer": {
@@ -313,12 +791,6 @@
"escape-string-regexp": "^1.0.2"
}
},
"strip-url-auth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz",
"integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=",
"dev": true
},
"trim-repeated": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",

View File

@@ -6,6 +6,6 @@
"clean:l10n": "git push origin --delete l10n_master"
},
"devDependencies": {
"gh-pages": "^1.2.0"
"gh-pages": "^3.2.3"
}
}

View File

@@ -6,6 +6,7 @@ using Android.Views;
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Utilities;
namespace Bit.Droid.Accessibility
{
@@ -17,6 +18,7 @@ namespace Bit.Droid.Accessibility
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
base.OnCreate(bundle);
HandleIntent(Intent, 932473);
}

View File

@@ -31,7 +31,6 @@ namespace Bit.Droid.Accessibility
// So keep them in sync with:
// - AutofillHelpers.{TrustedBrowsers,CompatBrowsers}
// - Resources/xml/autofillservice.xml
new Browser("alook.browser", "search_fragment_input_view"),
new Browser("com.amazon.cloud9", "url"),
new Browser("com.android.browser", "url"),
new Browser("com.android.chrome", "url_bar"),
@@ -52,8 +51,10 @@ namespace Bit.Droid.Accessibility
new Browser("com.ecosia.android", "url_bar"),
new Browser("com.google.android.apps.chrome", "url_bar"),
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
// Rem. for "com.google.android.captiveportallogin": URL displayed in ActionBar subtitle without viewId.
new Browser("com.jamal2367.styx", "search"),
new Browser("com.kiwibrowser.browser", "url_bar"),
new Browser("com.kiwibrowser.browser.dev", "url_bar"),
new Browser("com.microsoft.emmx", "url_bar"),
new Browser("com.microsoft.emmx.beta", "url_bar"),
new Browser("com.microsoft.emmx.canary", "url_bar"),
@@ -67,6 +68,7 @@ namespace Bit.Droid.Accessibility
new Browser("com.opera.mini.native", "url_field"),
new Browser("com.opera.mini.native.beta", "url_field"),
new Browser("com.opera.touch", "addressbarEdit"),
new Browser("com.qflair.browserq", "url"),
new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
@@ -101,8 +103,10 @@ namespace Bit.Droid.Accessibility
new Browser("org.mozilla.fennec_fdroid", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
new Browser("org.mozilla.firefox", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
new Browser("org.mozilla.firefox_beta", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
new Browser("org.mozilla.focus", "display_url"),
new Browser("org.mozilla.klar", "display_url"),
new Browser("org.mozilla.focus", "mozac_browser_toolbar_url_view,display_url"), // 2nd = Legacy
new Browser("org.mozilla.focus.beta", "mozac_browser_toolbar_url_view,display_url"), // 2nd = Legacy
new Browser("org.mozilla.focus.nightly", "mozac_browser_toolbar_url_view,display_url"), // 2nd = Legacy
new Browser("org.mozilla.klar", "mozac_browser_toolbar_url_view,display_url"), // 2nd = Legacy
new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"),
new Browser("org.mozilla.rocket", "display_url"),
new Browser("org.torproject.torbrowser", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0.3)

View File

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

View File

@@ -68,6 +68,7 @@
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
<Reference Include="System.Net.Http" Condition="'$(Configuration)'=='FDroid'" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Plugin.CurrentActivity">
@@ -148,6 +149,8 @@
<Compile Include="Utilities\ThemeHelpers.cs" />
<Compile Include="WebAuthCallbackActivity.cs" />
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
<Compile Include="Services\ClipboardService.cs" />
<Compile Include="Utilities\IntentExtensions.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\FontAwesome.ttf" />
@@ -168,9 +171,11 @@
<AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable\card.xml" />
<AndroidResource Include="Resources\drawable\cog.xml" />
<AndroidResource Include="Resources\drawable\cog_environment.xml" />
<AndroidResource Include="Resources\drawable\cog_settings.xml" />
<AndroidResource Include="Resources\drawable\icon.xml" />
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
<AndroidResource Include="Resources\drawable\ic_warning.xml" />
<AndroidResource Include="Resources\drawable\id.xml" />
<AndroidResource Include="Resources\drawable\info.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg.xml" />
@@ -204,6 +209,8 @@
<AndroidResource Include="Resources\values-night\styles.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\values\manifest.xml" />
<AndroidResource Include="Resources\values-v30\manifest.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
@@ -269,5 +276,8 @@
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\values-v30\" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project>

View File

@@ -35,7 +35,10 @@ namespace Bit.Droid.Autofill
public static HashSet<string> TrustedBrowsers = new HashSet<string>
{
"com.duckduckgo.mobile.android",
"com.google.android.googlequicksearchbox",
"org.mozilla.focus",
"org.mozilla.focus.beta",
"org.mozilla.focus.nightly",
"org.mozilla.klar",
};
@@ -48,7 +51,6 @@ namespace Bit.Droid.Autofill
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
public static HashSet<string> CompatBrowsers = new HashSet<string>
{
"alook.browser",
"com.amazon.cloud9",
"com.android.browser",
"com.android.chrome",
@@ -68,8 +70,10 @@ namespace Bit.Droid.Autofill
"com.ecosia.android",
"com.google.android.apps.chrome",
"com.google.android.apps.chrome_dev",
"com.google.android.captiveportallogin",
"com.jamal2367.styx",
"com.kiwibrowser.browser",
"com.kiwibrowser.browser.dev",
"com.microsoft.emmx",
"com.microsoft.emmx.beta",
"com.microsoft.emmx.canary",
@@ -83,6 +87,7 @@ namespace Bit.Droid.Autofill
"com.opera.mini.native",
"com.opera.mini.native.beta",
"com.opera.touch",
"com.qflair.browserq",
"com.qwant.liberty",
"com.sec.android.app.sbrowser",
"com.sec.android.app.sbrowser.beta",

View File

@@ -22,9 +22,8 @@ namespace Bit.Droid.Autofill
{
private ICipherService _cipherService;
private IVaultTimeoutService _vaultTimeoutService;
private IStorageService _storageService;
private IPolicyService _policyService;
private IUserService _userService;
private IStateService _stateService;
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback)
@@ -38,18 +37,18 @@ namespace Bit.Droid.Autofill
var parser = new Parser(structure, ApplicationContext);
parser.Parse();
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
var shouldAutofill = await parser.ShouldAutofillAsync(_stateService);
if (!shouldAutofill)
{
return;
}
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
var inlineAutofillEnabled = await _stateService.GetInlineAutofillEnabledAsync() ?? true;
if (_vaultTimeoutService == null)
{
@@ -81,12 +80,12 @@ namespace Bit.Droid.Autofill
return;
}
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
var disableSavePrompt = await _stateService.GetAutofillDisableSavePromptAsync();
if (disableSavePrompt.GetValueOrDefault())
{
return;

View File

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

View File

@@ -19,39 +19,23 @@ using System.Threading.Tasks;
using AndroidX.Core.Content;
using Bit.App.Utilities;
using ZXing.Net.Mobile.Android;
using Android.Util;
namespace Bit.Droid
{
[Activity(
Label = "Bitwarden",
Icon = "@mipmap/ic_launcher",
Theme = "@style/LaunchTheme",
MainLauncher = true,
LaunchMode = LaunchMode.SingleTask,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden |
ConfigChanges.Navigation | ConfigChanges.UiMode)]
[IntentFilter(
new[] { Intent.ActionSend },
Categories = new[] { Intent.CategoryDefault },
DataMimeTypes = new[]
{
@"application/*",
@"image/*",
@"video/*",
@"text/*"
})]
// Activity and IntentFilter declarations have been moved to Properties/AndroidManifest.xml
// They have been hardcoded so we can use the default LaunchMode on Android 11+
// LaunchMode defined in values/manifest.xml for Android 10- and values-v30/manifest.xml for Android 11+
// See https://github.com/bitwarden/mobile/pull/1673 for details
[Register("com.x8bit.bitwarden.MainActivity")]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
private IDeviceActionService _deviceActionService;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
private IUserService _userService;
private IStateService _stateService;
private IAppIdService _appIdService;
private IStorageService _storageService;
private IEventService _eventService;
private PendingIntent _clearClipboardPendingIntent;
private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
@@ -63,9 +47,6 @@ namespace Bit.Droid
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
PendingIntentFlags.UpdateCurrent);
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
PendingIntentFlags.UpdateCurrent);
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
StrictMode.SetThreadPolicy(policy);
@@ -73,14 +54,16 @@ namespace Bit.Droid
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
// this needs to be called here before base.OnCreate(...)
Intent?.Validate();
base.OnCreate(savedInstanceState);
if (!CoreHelpers.InDebugMode())
{
@@ -88,7 +71,7 @@ namespace Bit.Droid
}
#if !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
var appCenterTask = appCenterHelper.InitAsync();
#endif
@@ -123,10 +106,6 @@ namespace Bit.Droid
{
ExitApp();
}
else if (message.Command == "copiedToClipboard")
{
var task = ClearClipboardAlarmAsync(message.Data as Tuple<string, int?, bool>);
}
});
}
@@ -159,7 +138,15 @@ namespace Bit.Droid
base.OnNewIntent(intent);
try
{
if (intent.GetBooleanExtra("generatorTile", false))
if (intent?.GetStringExtra("uri") is string uri)
{
_messagingService.Send("popAllAndGoToAutofillCiphers");
if (_appOptions != null)
{
_appOptions.Uri = uri;
}
}
else if (intent.GetBooleanExtra("generatorTile", false))
{
_messagingService.Send("popAllAndGoToTabGenerator");
if (_appOptions != null)
@@ -385,7 +372,8 @@ namespace Bit.Droid
private void AppearanceAdjustments()
{
Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor);
ThemeHelpers.SetAppearance(ThemeManager.GetTheme(true), ThemeManager.OsDarkModeEnabled());
Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor);
ThemeHelpers.SetAppearance(ThemeManager.GetTheme(), ThemeManager.OsDarkModeEnabled());
}
private void ExitApp()
@@ -394,30 +382,6 @@ namespace Bit.Droid
Java.Lang.JavaSystem.Exit(0);
}
private async Task ClearClipboardAlarmAsync(Tuple<string, int?, bool> data)
{
if (data.Item3)
{
return;
}
var clearMs = data.Item2;
if (clearMs == null)
{
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if (clearMs == null)
{
return;
}
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs.Value;
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent);
}
private void StartEventAlarm()
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;

View File

@@ -16,6 +16,8 @@ using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
using Plugin.Fingerprint;
using Xamarin.Android.Net;
using System.Net.Http;
using System.Net;
#if !FDROID
using Android.Gms.Security;
#endif
@@ -78,7 +80,8 @@ namespace Bit.Droid
FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration
{
FadeAnimationEnabled = false,
FadeAnimationForCachedImages = false
FadeAnimationForCachedImages = false,
HttpClient = new HttpClient(new AndroidClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate })
});
ZXing.Net.Mobile.Forms.Android.Platform.Init();
});
@@ -94,13 +97,14 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService,
var stateService = new StateService(mobileStorageService, secureStorageService);
var deviceActionService = new DeviceActionService(stateService, messagingService,
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
@@ -110,6 +114,8 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
@@ -128,7 +134,7 @@ namespace Bit.Droid
ServiceContainer.Register<IPushNotificationListenerService>(
"pushNotificationListenerService", notificationListenerService);
var androidPushNotificationService = new AndroidPushNotificationService(
mobileStorageService, notificationListenerService);
stateService, notificationListenerService);
ServiceContainer.Register<IPushNotificationService>(
"pushNotificationService", androidPushNotificationService);
#endif
@@ -144,10 +150,6 @@ namespace Bit.Droid
private async Task BootstrapAsync()
{
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService")
.GetAsync<bool?>(Constants.DisableFaviconKey);
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
Constants.DisableFaviconKey, disableFavicon);
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
}
}

View File

@@ -1,61 +1,57 @@
<?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.13.1"
android:installLocation="internalOnly"
package="com.x8bit.bitwarden">
<?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.15.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/>
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<application
android:label="Bitwarden"
android:theme="@style/LaunchTheme"
android:allowBackup="false"
tools:replace="android:allowBackup"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:networkSecurityConfig="@xml/network_security_config">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.x8bit.bitwarden.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
<application android:label="Bitwarden" android:theme="@style/LaunchTheme" android:allowBackup="false" tools:replace="android:allowBackup" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.x8bit.bitwarden.fileprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
</provider>
<meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
<meta-data android:name="android.max_aspect" android:value="2.1"/>
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions"/>
<!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) -->
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true" />
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true" />
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true"/>
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true"/>
<!-- Support for LG "Dual Window" mode (for Android < 7.0 users) -->
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true" />
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true"/>
<!-- Declare MainActivity manually so we can set LaunchMode using API dependant resource -->
<activity android:name="com.x8bit.bitwarden.MainActivity" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="Bitwarden" android:launchMode="@integer/launchModeAPIlevel" android:theme="@style/LaunchTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="application/*"/>
<data android:mimeType="image/*"/>
<data android:mimeType="video/*"/>
<data android:mimeType="text/*"/>
</intent-filter>
</activity>
</application>
<!-- Package visibility (for Android 11+) -->
<queries>
<intent>
<action android:name="*"/>
</intent>
<intent>
<action android:name="*"/>
</intent>
</queries>
</manifest>

View File

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

View File

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

View File

@@ -29,7 +29,16 @@ namespace Bit.Droid.Renderers
Control.PaddingBottom + 20);
Control.ImeOptions = Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
(ImeAction)ImeFlags.NoExtractUi;
}
}
}
// Workaround for bug preventing long-press -> copy/paste on Android 11
// See https://issuetracker.google.com/issues/37095917
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
Control.Enabled = false;
Control.Enabled = true;
}
// Workaround for failure to disable text prediction on non-password fields

View File

@@ -2,6 +2,7 @@
using Android.Content;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.OS;
using AndroidX.Core.Content.Resources;
using Bit.Droid.Renderers;
using Bit.Droid.Utilities;
@@ -35,6 +36,12 @@ namespace Bit.Droid.Renderers
private void UpdateColors()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
{
// Android 5.x doesn't support ThumbTintList, and using SwitchCompat on every version after 5.x
// doesn't apply tinting the way we want. Let 5.x to do its own thing here.
return;
}
if (Control != null)
{
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1792"
android:viewportHeight="1792">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M1152,896q0,-106 -75,-181t-181,-75 -181,75 -75,181 75,181 181,75 181,-75 75,-181zM1664,787v222q0,12 -8,23t-20,13l-185,28q-19,54 -39,91 35,50 107,138 10,12 10,25t-9,23q-27,37 -99,108t-94,71q-12,0 -26,-9l-138,-108q-44,23 -91,38 -16,136 -29,186 -7,28 -36,28h-222q-14,0 -24.5,-8.5t-11.5,-21.5l-28,-184q-49,-16 -90,-37l-141,107q-10,9 -25,9 -14,0 -25,-11 -126,-114 -165,-168 -7,-10 -7,-23 0,-12 8,-23 15,-21 51,-66.5t54,-70.5q-27,-50 -41,-99l-183,-27q-13,-2 -21,-12.5t-8,-23.5v-222q0,-12 8,-23t19,-13l186,-28q14,-46 39,-92 -40,-57 -107,-138 -10,-12 -10,-24 0,-10 9,-23 26,-36 98.5,-107.5t94.5,-71.5q13,0 26,10l138,107q44,-23 91,-38 16,-136 29,-186 7,-28 36,-28h222q14,0 24.5,8.5t11.5,21.5l28,184q49,16 90,37l142,-107q9,-9 24,-9 13,0 25,10 129,119 165,170 7,8 7,22 0,12 -8,23 -15,21 -51,66.5t-54,70.5q26,50 41,98l183,28q13,2 21,12.5t8,23.5z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="19dp"
android:viewportWidth="22"
android:viewportHeight="19">
<path
android:fillColor="#dd4b39"
android:pathData="M19.16 18.71H2.64c-0.36 0-0.72-0.09-1.03-0.27c-0.31-0.2-0.57-0.46-0.74-0.78c-0.18-0.32-0.27-0.67-0.27-1.04c0-0.36 0.1-0.72 0.28-1.03L9.14 1.1C9.32 0.76 9.58 0.5 9.89 0.32c0.3-0.18 0.65-0.28 1-0.28c0.36 0 0.7 0.1 1.02 0.28c0.3 0.18 0.56 0.44 0.74 0.75l8.26 14.51c0.18 0.31 0.28 0.67 0.28 1.03c0 0.37-0.09 0.72-0.26 1.04c-0.18 0.32-0.44 0.59-0.75 0.78c-0.31 0.18-0.66 0.28-1.02 0.27zM10.9 1.38c-0.13 0-0.26 0.04-0.38 0.1c-0.11 0.07-0.2 0.16-0.27 0.28L1.99 16.27c-0.07 0.11-0.1 0.24-0.1 0.36C1.9 16.76 1.92 16.9 2 17c0.06 0.12 0.16 0.22 0.27 0.3c0.12 0.06 0.25 0.1 0.38 0.1h16.52c0.13 0 0.26-0.04 0.37-0.1c0.12-0.08 0.21-0.18 0.28-0.3c0.06-0.1 0.1-0.23 0.1-0.36c0-0.12-0.04-0.25-0.1-0.36l-8.26-14.5c-0.07-0.13-0.17-0.22-0.28-0.29c-0.11-0.06-0.24-0.1-0.37-0.1zm0 11.42c-0.17 0-0.34-0.07-0.46-0.2c-0.12-0.12-0.19-0.29-0.19-0.46v-6.1c0-0.18 0.07-0.35 0.2-0.47c0.11-0.13 0.28-0.2 0.45-0.2c0.17 0 0.33 0.07 0.45 0.2c0.12 0.12 0.19 0.3 0.19 0.47v6.1c0 0.17-0.07 0.34-0.19 0.47c-0.12 0.12-0.28 0.2-0.45 0.2zm0 3.3c0.42 0 0.76-0.36 0.76-0.8c0-0.43-0.34-0.78-0.76-0.78c-0.43 0-0.77 0.35-0.77 0.79c0 0.43 0.34 0.79 0.77 0.79z"/>
</vector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?>
<resources>
<integer name="launchModeAPIlevel">0</integer>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?>
<resources>
<integer name="launchModeAPIlevel">2</integer>
</resources>

View File

@@ -11,9 +11,6 @@
-->
<autofill-service xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsInlineSuggestions="true">
<compatibility-package
android:name="alook.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.amazon.cloud9"
android:maxLongVersionCode="10000000000"/>
@@ -71,12 +68,18 @@
<compatibility-package
android:name="com.google.android.apps.chrome_dev"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.google.android.captiveportallogin"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.jamal2367.styx"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.kiwibrowser.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.kiwibrowser.browser.dev"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.microsoft.emmx"
android:maxLongVersionCode="10000000000"/>
@@ -116,6 +119,9 @@
<compatibility-package
android:name="com.opera.touch"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.qflair.browserq"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.qwant.liberty"
android:maxLongVersionCode="10000000000"/>
@@ -211,10 +217,10 @@
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="org.mozilla.firefox"
android:maxLongVersionCode="10000000000"/>
android:maxLongVersionCode="2015836711"/>
<compatibility-package
android:name="org.mozilla.firefox_beta"
android:maxLongVersionCode="10000000000"/>
android:maxLongVersionCode="2015849447"/>
<compatibility-package
android:name="org.mozilla.reference.browser"
android:maxLongVersionCode="10000000000"/>

View File

@@ -1,8 +1,8 @@
#if !FDROID
using System;
using System.Threading.Tasks;
using AndroidX.Core.App;
using Bit.App.Abstractions;
using Bit.Core;
using Bit.Core.Abstractions;
using Xamarin.Forms;
@@ -10,25 +10,27 @@ namespace Bit.Droid.Services
{
public class AndroidPushNotificationService : IPushNotificationService
{
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly IPushNotificationListenerService _pushNotificationListenerService;
public AndroidPushNotificationService(
IStorageService storageService,
IStateService stateService,
IPushNotificationListenerService pushNotificationListenerService)
{
_storageService = storageService;
_stateService = stateService;
_pushNotificationListenerService = pushNotificationListenerService;
}
public bool IsRegisteredForPush => NotificationManagerCompat.From(Android.App.Application.Context)?.AreNotificationsEnabled() ?? false;
public async Task<string> GetTokenAsync()
{
return await _storageService.GetAsync<string>(Constants.PushCurrentTokenKey);
return await _stateService.GetPushCurrentTokenAsync();
}
public async Task RegisterAsync()
{
var registeredToken = await _storageService.GetAsync<string>(Constants.PushRegisteredTokenKey);
var registeredToken = await _stateService.GetPushRegisteredTokenAsync();
var currentToken = await GetTokenAsync();
if (!string.IsNullOrWhiteSpace(registeredToken) && registeredToken != currentToken)
{
@@ -36,7 +38,7 @@ namespace Bit.Droid.Services
}
else
{
await _storageService.SaveAsync(Constants.PushLastRegistrationDateKey, DateTime.UtcNow);
await _stateService.SetPushLastRegistrationDateAsync(DateTime.UtcNow);
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Plugin.CurrentActivity;
using Xamarin.Essentials;
namespace Bit.Droid.Services
{
public class ClipboardService : IClipboardService
{
private readonly IStateService _stateService;
private readonly Lazy<PendingIntent> _clearClipboardPendingIntent;
public ClipboardService(IStateService stateService)
{
_stateService = stateService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
0,
new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
PendingIntentFlags.UpdateCurrent));
}
public async Task CopyTextAsync(string text, int expiresInMs = -1)
{
await Clipboard.SetTextAsync(text);
await ClearClipboardAlarmAsync(expiresInMs);
}
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
{
var clearMs = expiresInMs;
if (clearMs < 0)
{
// if not set then we need to check if the user set this config
var clearSeconds = await _stateService.GetClearClipboardAsync();
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if (clearMs < 0)
{
return;
}
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
var alarmManager = CrossCurrentActivity.Current.Activity.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
}
}
}

View File

@@ -33,7 +33,7 @@ namespace Bit.Droid.Services
{
public class DeviceActionService : IDeviceActionService
{
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly Func<IEventService> _eventServiceFunc;
@@ -43,12 +43,12 @@ namespace Bit.Droid.Services
private string _userAgent;
public DeviceActionService(
IStorageService storageService,
IStateService stateService,
IMessagingService messagingService,
IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc)
{
_storageService = storageService;
_stateService = stateService;
_messagingService = messagingService;
_broadcasterService = broadcasterService;
_eventServiceFunc = eventServiceFunc;
@@ -250,7 +250,7 @@ namespace Bit.Droid.Services
try
{
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
await _storageService.SaveAsync(Constants.LastFileCacheClearKey, DateTime.UtcNow);
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
}
catch (Exception) { }
}
@@ -640,8 +640,7 @@ namespace Bit.Droid.Services
public bool AutofillAccessibilityServiceRunning()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var enabledServices = Settings.Secure.GetString(activity.ContentResolver,
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
Settings.Secure.EnabledAccessibilityServices);
return Application.Context.PackageName != null &&
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
@@ -834,9 +833,8 @@ namespace Bit.Droid.Services
{
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
{
var userService = ServiceContainer.Resolve<IUserService>("userService");
var autoCopyDisabled = await _storageService.GetAsync<bool?>(Constants.DisableAutoTotpCopyKey);
var canAccessPremium = await userService.CanAccessPremiumAsync();
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
{
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");

View File

@@ -98,5 +98,15 @@ namespace Bit.Droid.Services
Console.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
return netLanguage;
}
public string GetLocaleShortDate(DateTime? date)
{
return date?.ToShortDateString() ?? string.Empty;
}
public string GetLocaleShortTime(DateTime? time)
{
return time?.ToShortTimeString() ?? string.Empty;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
using Android.Content;
using Android.OS;
namespace Bit.Droid.Utilities
{
public static class IntentExtensions
{
public static void Validate(this Intent intent)
{
try
{
// Check if getting the bundle of the extras causes any exception when unparcelling
// Note: getting the bundle like this will cause to call unparcel() internally
var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel");
}
catch (BadParcelableException)
{
intent.ReplaceExtras((Bundle)null);
}
}
}
}

View File

@@ -16,6 +16,10 @@ namespace Bit.Droid.Utilities
{
get => ThemeManager.GetResourceColor("MutedColor").ToAndroid();
}
public static Color BackgroundColor
{
get => ThemeManager.GetResourceColor("BackgroundColor").ToAndroid();
}
public static Color NavBarBackgroundColor
{
get => ThemeManager.GetResourceColor("NavigationBarBackgroundColor").ToAndroid();

View File

@@ -1,5 +1,7 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
using Bit.Droid.Utilities;
namespace Bit.Droid
{
@@ -9,5 +11,12 @@ namespace Bit.Droid
[IntentFilter(new[] { Android.Content.Intent.ActionView },
Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
DataScheme = "bitwarden")]
public class WebAuthCallbackActivity : Xamarin.Essentials.WebAuthenticatorCallbackActivity { }
public class WebAuthCallbackActivity : Xamarin.Essentials.WebAuthenticatorCallbackActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
Intent?.Validate();
base.OnCreate(savedInstanceState);
}
}
}

View File

@@ -1,9 +1,22 @@
using System.Globalization;
using System;
using System.Globalization;
namespace Bit.App.Abstractions
{
public interface ILocalizeService
{
CultureInfo GetCurrentCultureInfo();
/// <summary>
/// Format date using device locale.
/// Needed for iOS as it provides locales unsupported in .Net
/// </summary>
string GetLocaleShortDate(DateTime? date);
/// <summary>
/// Format time using device locale.
/// Needed for iOS as it provides locales unsupported in .Net
/// </summary>
string GetLocaleShortTime(DateTime? time);
}
}

View File

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

View File

@@ -4,6 +4,7 @@ namespace Bit.App.Abstractions
{
public interface IPushNotificationService
{
bool IsRegisteredForPush { get; }
Task<string> GetTokenAsync();
Task RegisterAsync();
Task UnregisterAsync();

View File

@@ -15,9 +15,11 @@
<ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.4.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.80.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.2" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<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.2291" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
</ItemGroup>
@@ -120,16 +122,19 @@
<DependentUpon>SendGroupingsPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Remove="Pages\Accounts\AccountsPopupPage.xaml.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Behaviors\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Controls\CipherViewCell\CipherViewCell.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Remove="Pages\Accounts\AccountsPopupPage.xaml" />
</ItemGroup>
<ItemGroup>
@@ -411,4 +416,7 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Remove="Behaviors\" />
</ItemGroup>
</Project>

View File

@@ -4,8 +4,8 @@ using Bit.App.Pages;
using Bit.App.Resources;
using Bit.App.Services;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
@@ -17,7 +17,6 @@ namespace Bit.App
{
public partial class App : Application
{
private readonly IUserService _userService;
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IStateService _stateService;
@@ -25,7 +24,6 @@ namespace Bit.App
private readonly ISyncService _syncService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuthService _authService;
private readonly IStorageService _storageService;
private readonly IStorageService _secureStorageService;
private readonly IDeviceActionService _deviceActionService;
@@ -39,7 +37,6 @@ namespace Bit.App
Current = this;
return;
}
_userService = ServiceContainer.Resolve<IUserService>("userService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
@@ -47,7 +44,6 @@ namespace Bit.App
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@@ -84,8 +80,12 @@ namespace Bit.App
}
else if (message.Command == "logout")
{
var extras = message.Data as Tuple<string, bool, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2;
var expired = extras?.Item3;
Device.BeginInvokeOnMainThread(async () =>
await LogOutAsync((message.Data as bool?).GetValueOrDefault()));
await LogOutAsync(userId, userInitiated, expired));
}
else if (message.Command == "loggedOut")
{
@@ -106,6 +106,18 @@ namespace Bit.App
await SleptAsync();
}
}
else if (message.Command == "addAccount")
{
await AddAccount();
}
else if (message.Command == "accountAdded")
{
UpdateTheme();
}
else if (message.Command == "switchedAccount")
{
await SwitchedAccountAsync();
}
else if (message.Command == "migrated")
{
await Task.Delay(1000);
@@ -113,7 +125,8 @@ namespace Bit.App
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend")
message.Command == "popAllAndGoToTabSend" ||
message.Command == "popAllAndGoToAutofillCiphers")
{
Device.BeginInvokeOnMainThread(async () =>
{
@@ -123,7 +136,11 @@ namespace Bit.App
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == "popAllAndGoToTabMyVault")
if (message.Command == "popAllAndGoToAutofillCiphers")
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (message.Command == "popAllAndGoToTabMyVault")
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
@@ -140,6 +157,15 @@ namespace Bit.App
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
});
}
@@ -153,7 +179,7 @@ namespace Bit.App
if (string.IsNullOrWhiteSpace(Options.Uri))
{
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
_storageService);
_stateService);
if (!updated)
{
SyncIfNeeded();
@@ -162,6 +188,8 @@ namespace Bit.App
if (Device.RuntimePlatform == Device.Android)
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
// Reset delay on every start
_vaultTimeoutService.DelayLockAndLogoutMs = null;
}
_messagingService.Send("startEventTimer");
}
@@ -175,7 +203,7 @@ namespace Bit.App
var isLocked = await _vaultTimeoutService.IsLockedAsync();
if (!isLocked)
{
await _storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime());
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
SetTabsPageFromAutofill(isLocked);
await SleptAsync();
@@ -194,7 +222,7 @@ namespace Bit.App
private async Task SleptAsync()
{
await HandleVaultTimeoutAsync();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("stopEventTimer");
}
@@ -220,27 +248,65 @@ namespace Bit.App
new System.Globalization.UmAlQuraCalendar();
}
private async Task LogOutAsync(bool expired)
private async Task LogOutAsync(string userId, bool? userInitiated, bool? expired)
{
await AppHelpers.LogOutAsync();
await AppHelpers.LogOutAsync(userId, userInitiated.GetValueOrDefault(true));
await SetMainPageAsync();
_authService.LogOut(() =>
{
Current.MainPage = new HomePage();
if (expired)
if (expired.GetValueOrDefault())
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
private async Task AddAccount()
{
Device.BeginInvokeOnMainThread(async () =>
{
Current.MainPage = new NavigationPage(new HomePage(Options));
});
}
private async Task SwitchedAccountAsync()
{
await AppHelpers.OnAccountSwitchAsync();
var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync();
Device.BeginInvokeOnMainThread(async () =>
{
if (shouldTimeout)
{
await _vaultTimeoutService.ExecuteTimeoutActionAsync();
}
else
{
await SetMainPageAsync();
}
await Task.Delay(50);
UpdateTheme();
});
}
private async Task SetMainPageAsync()
{
var authed = await _userService.IsAuthenticatedAsync();
var authed = await _stateService.IsAuthenticatedAsync();
if (authed)
{
if (await _vaultTimeoutService.IsLockedAsync())
var isLocked = await _vaultTimeoutService.IsLockedAsync();
var shouldTimeout = await _vaultTimeoutService.ShouldTimeoutAsync();
var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (isLocked || shouldTimeout)
{
Current.MainPage = new NavigationPage(new LockPage(Options));
if (vaultTimeoutAction == "logOut")
{
var email = await _stateService.GetEmailAsync();
Current.MainPage = new NavigationPage(new LoginPage(email, Options));
}
else
{
Current.MainPage = new NavigationPage(new LockPage(Options));
}
}
else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
{
@@ -261,40 +327,13 @@ namespace Bit.App
}
else
{
Current.MainPage = new HomePage(Options);
}
}
private async Task HandleVaultTimeoutAsync()
{
if (await _vaultTimeoutService.IsLockedAsync())
{
return;
}
var authed = await _userService.IsAuthenticatedAsync();
if (!authed)
{
return;
}
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
vaultTimeout = vaultTimeout.GetValueOrDefault(-1);
if (vaultTimeout == 0)
{
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);
if (action == "logOut")
{
await _vaultTimeoutService.LogOutAsync();
}
else
{
await _vaultTimeoutService.LockAsync(true);
}
Current.MainPage = new NavigationPage(new HomePage(Options));
}
}
private async Task ClearCacheIfNeededAsync()
{
var lastClear = await _storageService.GetAsync<DateTime?>(Constants.LastFileCacheClearKey);
var lastClear = await _stateService.GetLastFileCacheClearAsync();
if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
{
var task = Task.Run(() => _deviceActionService.ClearCacheAsync());
@@ -337,12 +376,12 @@ namespace Bit.App
{
InitializeComponent();
SetCulture();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
ThemeManager.SetTheme(Current.Resources);
Current.RequestedThemeChanged += (s, a) =>
{
UpdateTheme();
};
Current.MainPage = new HomePage();
Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
}
@@ -368,17 +407,16 @@ namespace Bit.App
{
Device.BeginInvokeOnMainThread(() =>
{
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android, Current.Resources);
ThemeManager.SetTheme(Current.Resources);
_messagingService.Send("updatedTheme");
});
}
private async Task LockedAsync(bool autoPromptBiometric)
{
await _stateService.PurgeAsync();
if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
if (vaultTimeout == 0)
{
autoPromptBiometric = false;
@@ -408,7 +446,7 @@ namespace Bit.App
}
}
}
await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock);
await _stateService.SetPreviousPageInfoAsync(lastPageBeforeLock);
var lockPage = new LockPage(Options, autoPromptBiometric);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
}

View File

@@ -0,0 +1,43 @@
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Behaviors
{
/// <summary>
/// This behavior prevents the Editor to be automatically scrolled to the bottom on focus.
/// This is needed due to this Xamarin Forms issue: https://github.com/xamarin/Xamarin.Forms/issues/2233
/// </summary>
public class EditorPreventAutoBottomScrollingOnFocusedBehavior : Behavior<Editor>
{
public static readonly BindableProperty ParentScrollViewProperty
= BindableProperty.Create(nameof(ParentScrollView), typeof(ScrollView), typeof(EditorPreventAutoBottomScrollingOnFocusedBehavior));
public ScrollView ParentScrollView
{
get => (ScrollView)GetValue(ParentScrollViewProperty);
set => SetValue(ParentScrollViewProperty, value);
}
protected override void OnAttachedTo(Editor bindable)
{
base.OnAttachedTo(bindable);
bindable.Focused += OnFocused;
}
private void OnFocused(object sender, FocusEventArgs e)
{
if (DeviceInfo.Platform.Equals(DevicePlatform.iOS) && ParentScrollView != null)
{
ParentScrollView.ScrollToAsync(ParentScrollView.ScrollX, ParentScrollView.ScrollY, true);
}
}
protected override void OnDetachingFrom(Editor bindable)
{
bindable.Focused -= OnFocused;
base.OnDetachingFrom(bindable);
}
}
}

View File

@@ -0,0 +1,101 @@
<?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:DataType="controls:AccountViewCellViewModel">
<Grid RowSpacing="0"
ColumnSpacing="0">
<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="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="0"
Grid.Column="0"
RowSpacing="3"
Margin="12,0,0,0"
VerticalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Grid.Row="0"
Text="{Binding AccountView.Email}"
StyleClass="list-title"
LineBreakMode="TailTruncation" />
<Label
Grid.Column="0"
Grid.Row="1"
Text="{Binding AccountView.Hostname}"
StyleClass="list-sub"
LineBreakMode="TailTruncation" />
</Grid>
<Label
Grid.Column="1"
Grid.Row="0"
Text="{Binding AuthStatusText}"
FontSize="Small"
FontAttributes="Italic"
LineBreakMode="NoWrap"
HorizontalOptions="End"
Margin="10,0,20,0"
VerticalOptions="Center" />
</Grid>
<Grid
IsVisible="{Binding IsAccount, Converter={StaticResource inverseBool}}"
VerticalOptions="CenterAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="36" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
VerticalOptions="Center"
Margin="12,0,0,0"
WidthRequest="{OnPlatform 14, iOS=14, Android=26}"
HeightRequest="{OnPlatform 14, iOS=14, Android=26}"
Source="plus.png"
xct:IconTintColorEffect.TintColor="{DynamicResource TextColor}"
AutomationProperties.IsInAccessibleTree="False" />
<Label
Text="Add Account"
StyleClass="list-title"
LineBreakMode="TailTruncation"
VerticalOptions="Center"
Grid.Column="1" />
</Grid>
</Grid>
</ViewCell>

View File

@@ -0,0 +1,35 @@
using Bit.Core.Models.View;
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), default(AccountView), BindingMode.OneWay);
public AccountViewCell()
{
InitializeComponent();
}
public AccountView Account
{
get => GetValue(AccountProperty) as AccountView;
set => SetValue(AccountProperty, 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,31 @@
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Controls
{
public class AccountViewCellViewModel : ExtendedViewModel
{
private AccountView _accountView;
public AccountViewCellViewModel(AccountView accountView)
{
AccountView = accountView;
}
public AccountView AccountView
{
get => _accountView;
set => SetProperty(ref _accountView, value);
}
public bool IsAccount
{
get => AccountView.IsAccount;
}
public string AuthStatusText
{
get => AccountView.AuthStatus.ToString();
}
}
}

View File

@@ -0,0 +1,114 @@
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 AvatarImageSource(string data = null)
{
_data = data;
}
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 > 2)
{
upperData = _data.ToUpper();
chars = upperData.Substring(0, 2).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 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);
}
//if (Device.RuntimePlatform == Device.iOS)
//{
// // TODO remove this once iOS ToolbarItem tint issue is solved
// return Color.FromHex("#33ffffff");
//}
return Color.FromHex(color);
}
}
}

View File

@@ -46,7 +46,7 @@
WidthRequest="22"
HeightRequest="22"
IsVisible="{Binding ShowIconImage}"
Source="{Binding Cipher, Converter={StaticResource iconImageConverter}}"
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">

View File

@@ -1,4 +1,5 @@
using Bit.Core.Models.View;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Controls
@@ -7,6 +8,7 @@ namespace Bit.App.Controls
{
private CipherView _cipher;
private bool _websiteIconsEnabled;
private string _iconImageSource = string.Empty;
public CipherViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
{
@@ -28,8 +30,22 @@ namespace Bit.App.Controls
public bool ShowIconImage
{
get => WebsiteIconsEnabled && !string.IsNullOrWhiteSpace(Cipher.Login?.Uri) &&
Cipher.Login.Uri.StartsWith("http");
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null;
}
public string IconImageSource
{
get
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
}
return _iconImageSource;
}
}
}
}

View File

@@ -0,0 +1,9 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedToolbarItem : ToolbarItem
{
public bool UseOriginalImage { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using Xamarin.Forms;
namespace Bit.App.Effects
{
public class ScrollEnabledEffect : RoutingEffect
{
public static readonly BindableProperty IsScrollEnabledProperty =
BindableProperty.CreateAttached("IsScrollEnabled", typeof(bool), typeof(ScrollEnabledEffect), true);
public static bool GetIsScrollEnabled(BindableObject view)
{
return (bool)view.GetValue(IsScrollEnabledProperty);
}
public static void SetIsScrollEnabled(BindableObject view, bool value)
{
view.SetValue(IsScrollEnabledProperty, value);
}
public ScrollEnabledEffect()
: base("Bitwarden.ScrollEnabledEffect")
{
}
}
}

View File

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

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.Accounts.DeleteAccountPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:DeleteAccountViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:DeleteAccountViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<Style TargetType="Label" x:Key="lblDescription">
<Setter Property="FontSize" Value="{OnPlatform Android=Large, iOS=Small}" />
</Style>
</ContentPage.Resources>
<ContentPage.Content>
<Grid Padding="20, 30" RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image
Source="ic_warning"
WidthRequest="28"
HeightRequest="25"
HorizontalOptions="Start" />
<Label
Grid.Row="1"
Grid.ColumnSpan="2"
Text="{u:I18n DeletingYourAccountIsPermanent}"
HorizontalOptions="Start"
StyleClass="title-danger"
Margin="0,15,0,0"/>
<Label
Grid.Row="2"
Grid.ColumnSpan="2"
Text="{u:I18n DeleteAccountExplanation}"
Style="{StaticResource lblDescription}"
HorizontalOptions="Start"
Margin="0,6,50,0"
Opacity="0.6" />
<Button
Grid.Row="3"
Text="{u:I18n DeleteAccount}"
StyleClass="btn-danger"
HorizontalOptions="Start"
VerticalOptions="Start"
Margin="0,20,0,0"
Padding="16,0"
CornerRadius="2"
TextTransform="Uppercase"
Clicked="DeleteAccount_Clicked"/>
<Button
Grid.Row="3"
Grid.Column="1"
Text="{u:I18n Cancel}"
StyleClass="btn-secondary"
HorizontalOptions="Start"
VerticalOptions="Start"
Margin="0,20,0,0"
Padding="16,0"
CornerRadius="2"
TextTransform="Uppercase"
Clicked="Close_Clicked" />
</Grid>
</ContentPage.Content>
</pages:BaseContentPage>

View File

@@ -0,0 +1,33 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Pages.Accounts
{
public partial class DeleteAccountPage : BaseContentPage
{
DeleteAccountViewModel _vm;
public DeleteAccountPage()
{
InitializeComponent();
_vm = BindingContext as DeleteAccountViewModel;
_vm.Page = this;
}
private async void Close_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await Navigation.PopModalAsync();
}
}
private async void DeleteAccount_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.DeleteAccountAsync();
}
}
}
}

View File

@@ -0,0 +1,84 @@
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
namespace Bit.App.Pages
{
public class DeleteAccountViewModel : BaseViewModel
{
readonly IApiService _apiService;
readonly IPasswordRepromptService _passwordRepromptService;
readonly IMessagingService _messagingService;
readonly ICryptoService _cryptoService;
readonly IPlatformUtilsService _platformUtilsService;
readonly IDeviceActionService _deviceActionService;
public DeleteAccountViewModel()
{
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
PageTitle = AppResources.DeleteAccount;
}
public async Task DeleteAccountAsync()
{
try
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
return;
}
var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync();
if (!valid)
{
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.DeletingYourAccount);
var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(password, null);
await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest
{
MasterPasswordHash = masterPasswordHashKey
});
await _deviceActionService.HideLoadingAsync();
_messagingService.Send("logout");
await _platformUtilsService.ShowDialogAsync(AppResources.YourAccountHasBeenPermanentlyDeleted);
}
catch (ApiException apiEx)
{
await _deviceActionService.HideLoadingAsync();
if (apiEx?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(apiEx.Error.GetSingleMessage(), AppResources.AnErrorHasOccurred);
}
}
catch (System.Exception ex)
{
await _deviceActionService.HideLoadingAsync();
#if !FDROID
Crashes.TrackError(ex);
#endif
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
}
}
}
}

View File

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

View File

@@ -1,53 +1,107 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.HomePage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:view="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:HomeViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:HomeViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Clicked="AccountSwitch_Clicked"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem x:Name="_closeItem" x:Key="closeItem" Text="{u:I18n Close}"
Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem
Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</ContentPage.ToolbarItems>
<StackLayout Spacing="0" Padding="10, 5">
<controls:FaButton Text="&#xf013;"
StyleClass="btn-muted, btn-icon, btn-icon-platform"
HorizontalOptions="Start"
Clicked="Environment_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}">
<controls:FaButton.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 10, 0, 0" />
<On Platform="Android" Value="0" />
</OnPlatform>
</controls:FaButton.Margin>
</controls:FaButton>
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
<Image
x:Name="_logo"
Source="logo.png"
VerticalOptions="Center" />
<Label Text="{u:I18n LoginOrCreateNewAccount}"
StyleClass="text-lg"
HorizontalTextAlignment="Center"></Label>
<StackLayout Spacing="5">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked" />
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked" />
<ContentPage.Resources>
<ResourceDictionary>
<StackLayout x:Name="_mainLayout" x:Key="mainLayout" Spacing="0" Padding="10, 5">
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
<Image
x:Name="_logo"
Source="logo.png"
VerticalOptions="Center" />
<Label Text="{u:I18n LoginOrCreateNewAccount}"
StyleClass="text-lg"
HorizontalTextAlignment="Center">
</Label>
<StackLayout Spacing="5">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
<Button Text="{u:I18n CreateAccount}"
Clicked="Register_Clicked" />
<Button Text="{u:I18n LogInSso}"
Clicked="LogInSso_Clicked" />
</StackLayout>
</StackLayout>
</StackLayout>
</StackLayout>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<!-- Account Switching Overlay -->
<ContentView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
IsVisible="False"
BackgroundColor="#22000000"
Padding="0">
<StackLayout
x:Name="_accountListContainer"
VerticalOptions="StartAndExpand"
HorizontalOptions="FillAndExpand"
BackgroundColor="{DynamicResource BackgroundColor}"
xct:ShadowEffect.Color="Black"
xct:ShadowEffect.Radius="10"
xct:ShadowEffect.OffsetY="3">
<ListView
x:Name="_accountListView"
ItemsSource="{Binding AccountViews}"
ItemSelected="AccountRow_Selected"
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand"
RowHeight="60">
<ListView.ItemTemplate>
<DataTemplate x:DataType="view:AccountView">
<controls:AccountViewCell
Account="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentView>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -12,13 +12,10 @@ namespace Bit.App.Pages
{
private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", false);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent();
@@ -29,6 +26,11 @@ namespace Bit.App.Pages
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
UpdateLogo();
if (!_appOptions?.IosExtension ?? false)
{
ToolbarItems.Remove(_closeItem);
}
}
public async Task DismissRegisterPageAndLogInAsync(string email)
@@ -37,10 +39,18 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(new LoginPage(email, _appOptions)));
}
protected override void OnAppearing()
protected override async void OnAppearing()
{
base.OnAppearing();
_messagingService.Send("showStatusBar", false);
_mainContent.Content = _mainLayout;
if (await ShowAccountSwitcherAsync())
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
else
{
ToolbarItems.Remove(_accountAvatar);
}
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
{
if (message.Command == "updatedTheme")
@@ -127,5 +137,23 @@ namespace Bit.App.Pages
var page = new EnvironmentPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async void AccountSwitch_Clicked(object sender, EventArgs e)
{
if (_accountListOverlay.IsVisible)
{
await ShowAccountListAsync(false, _accountListContainer, _accountListOverlay);
}
else
{
await RefreshAccountViewsAsync(_accountListView, false);
await ShowAccountListAsync(true, _accountListContainer, _accountListOverlay);
}
}
private async void AccountRow_Selected(object sender, SelectedItemChangedEventArgs e)
{
await AccountRowSelectedAsync(sender, e, _accountListContainer, _accountListOverlay, null, true);
}
}
}

View File

@@ -1,15 +1,27 @@
using System;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{
public class HomeViewModel : BaseViewModel
{
private readonly IStateService _stateService;
public HomeViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
PageTitle = AppResources.Bitwarden;
}
public ExtendedObservableCollection<AccountView> AccountViews
{
get => _stateService.AccountViews;
}
public Action StartLoginAction { get; set; }
public Action StartRegisterAction { get; set; }
public Action StartSsoLoginAction { get; set; }

View File

@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.LockPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:view="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LockPageViewModel"
Title="{Binding PageTitle}">
@@ -13,6 +15,19 @@
<pages:LockPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Clicked="AccountSwitch_Clicked"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
@@ -25,106 +40,162 @@
x:Name="_logOut"
Clicked="LogOut_Clicked"
Order="Secondary"/>
<ScrollView x:Name="_mainLayout" x:Key="mainLayout">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Grid
StyleClass="box-row"
IsVisible="{Binding PinLock}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n PIN}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_pin"
Text="{Binding Pin}"
StyleClass="box-value"
Keyboard="Numeric"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
<controls:FaButton
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:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout
StyleClass="box-row"
Padding="0, 10, 0, 0">
<Label
Text="{Binding LockedVerifyText}"
StyleClass="box-footer-label" />
<Label
Text="{Binding LoggedInAsText}"
StyleClass="box-footer-label"
Margin="0, 10, 0, 0" />
</StackLayout>
</StackLayout>
<StackLayout Padding="10, 0">
<Label
Text="{u:I18n BiometricInvalidated}"
StyleClass="box-footer-label,text-danger,text-bold"
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
IsVisible="{Binding BiometricButtonVisible}">
</Button>
<Button
x:Name="_unlockButton"
Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
</ContentPage.ToolbarItems>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Grid StyleClass="box-row" IsVisible="{Binding PinLock}">
<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:FaButton
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 StyleClass="box-row" IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}">
<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:FaButton
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>
<Label
Text="{Binding LockedVerifyText}"
StyleClass="box-footer-label" />
<Label
Text="{Binding LoggedInAsText}"
StyleClass="box-footer-label"
Margin="0, 10, 0, 0" />
<!-- Account Switching Overlay -->
<ContentView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
IsVisible="False"
BackgroundColor="#22000000"
Padding="0">
<StackLayout
x:Name="_accountListContainer"
VerticalOptions="StartAndExpand"
HorizontalOptions="FillAndExpand"
BackgroundColor="{DynamicResource BackgroundColor}"
xct:ShadowEffect.Color="Black"
xct:ShadowEffect.Radius="10"
xct:ShadowEffect.OffsetY="3">
<ListView
x:Name="_accountListView"
ItemsSource="{Binding AccountViews}"
ItemSelected="AccountRow_Selected"
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand"
RowHeight="60">
<ListView.ItemTemplate>
<DataTemplate x:DataType="view:AccountView">
<controls:AccountViewCell
Account="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</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 Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
</ContentView>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -1,7 +1,5 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
@@ -11,7 +9,6 @@ namespace Bit.App.Pages
{
public partial class LockPage : BaseContentPage
{
private readonly IStorageService _storageService;
private readonly AppOptions _appOptions;
private readonly bool _autoPromptBiometric;
private readonly LockPageViewModel _vm;
@@ -21,7 +18,6 @@ namespace Bit.App.Pages
public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_appOptions = appOptions;
_autoPromptBiometric = autoPromptBiometric;
InitializeComponent();
@@ -65,7 +61,16 @@ namespace Bit.App.Pages
return;
}
_appeared = true;
await _vm.InitAsync(_autoPromptBiometric);
_mainContent.Content = _mainLayout;
if (await ShowAccountSwitcherAsync())
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
else
{
ToolbarItems.Remove(_accountAvatar);
}
await _vm.InitAsync();
if (!_vm.BiometricLock)
{
if (_vm.PinLock)
@@ -77,6 +82,22 @@ namespace Bit.App.Pages
RequestFocus(MasterPasswordEntry);
}
}
else
{
if (_vm.UsingKeyConnector && !_vm.PinLock)
{
_passwordGrid.IsVisible = false;
_unlockButton.IsVisible = false;
}
if (_autoPromptBiometric)
{
var tasks = Task.Run(async () =>
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(async () => await _vm.PromptBiometricAsync());
});
}
}
}
private void Unlock_Clicked(object sender, EventArgs e)
@@ -130,7 +151,26 @@ namespace Bit.App.Pages
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async void AccountSwitch_Clicked(object sender, EventArgs e)
{
if (_accountListOverlay.IsVisible)
{
await ShowAccountListAsync(false, _accountListContainer, _accountListOverlay);
}
else
{
await RefreshAccountViewsAsync(_accountListView, false);
await ShowAccountListAsync(true, _accountListContainer, _accountListOverlay);
}
}
private async void AccountRow_Selected(object sender, SelectedItemChangedEventArgs e)
{
await AccountRowSelectedAsync(sender, e, _accountListContainer, _accountListOverlay, null, true);
}
}
}

View File

@@ -1,7 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
@@ -10,6 +8,7 @@ using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.Request;
using Bit.Core.Models.View;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -21,13 +20,11 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICryptoService _cryptoService;
private readonly IStorageService _storageService;
private readonly IUserService _userService;
private readonly IMessagingService _messagingService;
private readonly IStorageService _secureStorageService;
private readonly IEnvironmentService _environmentService;
private readonly IStateService _stateService;
private readonly IBiometricService _biometricService;
private readonly IKeyConnectorService _keyConnectorService;
private string _email;
private bool _showPassword;
@@ -35,6 +32,7 @@ namespace Bit.App.Pages
private bool _biometricLock;
private bool _biometricIntegrityValid = true;
private bool _biometricButtonVisible;
private bool _usingKeyConnector;
private string _biometricButtonText;
private string _loggedInAsText;
private string _lockedVerifyText;
@@ -47,13 +45,11 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
@@ -76,6 +72,11 @@ namespace Bit.App.Pages
set => SetProperty(ref _pinLock, value);
}
public bool UsingKeyConnector
{
get => _usingKeyConnector;
}
public bool BiometricLock
{
get => _biometricLock;
@@ -112,6 +113,11 @@ namespace Bit.App.Pages
set => SetProperty(ref _lockedVerifyText, value);
}
public ExtendedObservableCollection<AccountView> AccountViews
{
get => _stateService.AccountViews;
}
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? "" : "";
@@ -119,12 +125,19 @@ namespace Bit.App.Pages
public string Pin { get; set; }
public Action UnlockedAction { get; set; }
public async Task InitAsync(bool autoPromptBiometric)
public async Task InitAsync()
{
_pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
PinLock = (_pinSet.Item1 && _stateService.GetPinProtectedAsync() != null) || _pinSet.Item2;
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
_email = await _userService.GetEmailAsync();
// Users with key connector and without biometric or pin has no MP to unlock with
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
if (_usingKeyConnector && !(BiometricLock || PinLock))
{
await _vaultTimeoutService.LogOutAsync();
}
_email = await _stateService.GetEmailAsync();
var webVault = _environmentService.GetWebVaultUrl();
if (string.IsNullOrWhiteSpace(webVault))
{
@@ -139,8 +152,16 @@ namespace Bit.App.Pages
}
else
{
PageTitle = AppResources.VerifyMasterPassword;
LockedVerifyText = AppResources.VaultLockedMasterPassword;
if (_usingKeyConnector)
{
PageTitle = AppResources.UnlockVault;
LockedVerifyText = AppResources.VaultLockedIdentity;
}
else
{
PageTitle = AppResources.VerifyMasterPassword;
LockedVerifyText = AppResources.VaultLockedMasterPassword;
}
}
if (BiometricLock)
@@ -159,14 +180,7 @@ namespace Bit.App.Pages
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
AppResources.UseFingerprintToUnlock;
}
if (autoPromptBiometric)
{
var tasks = Task.Run(async () =>
{
await Task.Delay(500);
Device.BeginInvokeOnMainThread(async () => await PromptBiometricAsync());
});
}
}
}
@@ -188,8 +202,8 @@ namespace Bit.App.Pages
}
ShowPassword = false;
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
if (PinLock)
{
@@ -200,9 +214,9 @@ namespace Bit.App.Pages
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
_vaultTimeoutService.PinProtectedKey);
await _stateService.GetPinProtectedCachedAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin;
if (!failed)
@@ -271,12 +285,12 @@ namespace Bit.App.Pages
{
if (_pinSet.Item1)
{
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
await _stateService.SetPinProtectedCachedAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
}
MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
@@ -317,8 +331,9 @@ namespace Bit.App.Pages
ShowPassword = !ShowPassword;
var page = (Page as LockPage);
var entry = PinLock ? page.PinEntry : page.MasterPasswordEntry;
var str = PinLock ? Pin : MasterPassword;
entry.Focus();
entry.CursorPosition = PinLock ? Pin.Length : MasterPassword.Length;
entry.CursorPosition = String.IsNullOrEmpty(str) ? 0 : str.Length;
}
public async Task PromptBiometricAsync()
@@ -341,7 +356,7 @@ namespace Bit.App.Pages
page.MasterPasswordEntry.Focus();
}
});
_vaultTimeoutService.BiometricLocked = !success;
_stateService.BiometricLocked = !success;
if (success)
{
await DoContinueAsync();
@@ -360,9 +375,7 @@ namespace Bit.App.Pages
private async Task DoContinueAsync()
{
_vaultTimeoutService.BiometricLocked = false;
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
_stateService.BiometricLocked = false;
_messagingService.Send("unlocked");
UnlockedAction?.Invoke();
}

View File

@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.LoginPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:view="clr-namespace:Bit.Core.Models.View;assembly=BitwardenCore"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LoginPageViewModel"
Title="{Binding PageTitle}">
@@ -13,6 +15,21 @@
<pages:LoginPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Clicked="AccountSwitch_Clicked"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem x:Name="_closeItem" x:Key="closeItem" Text="{u:I18n Close}"
Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
@@ -25,68 +42,109 @@
x:Name="_getPasswordHint"
Clicked="Hint_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" />
</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:FaButton
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>
</ResourceDictionary>
</ContentPage.Resources>
<AbsoluteLayout
x:Name="_absLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ContentView
x:Name="_mainContent"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1">
</ContentView>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<!-- Account Switching Overlay -->
<ContentView
x:Name="_accountListOverlay"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All"
IsVisible="False"
BackgroundColor="#22000000"
Padding="0">
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n EmailAddress}"
StyleClass="box-label" />
<Entry
x:Name="_email"
Text="{Binding Email}"
Keyboard="Email"
StyleClass="box-value" />
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n MasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding LogInCommand}" />
<controls:FaButton
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
x:Name="_accountListContainer"
VerticalOptions="StartAndExpand"
HorizontalOptions="FillAndExpand"
BackgroundColor="{DynamicResource BackgroundColor}"
xct:ShadowEffect.Color="Black"
xct:ShadowEffect.Radius="10"
xct:ShadowEffect.OffsetY="3">
<ListView
x:Name="_accountListView"
ItemsSource="{Binding AccountViews}"
ItemSelected="AccountRow_Selected"
BackgroundColor="Transparent"
VerticalOptions="FillAndExpand"
RowHeight="60">
<ListView.ItemTemplate>
<DataTemplate x:DataType="view:AccountView">
<controls:AccountViewCell
Account="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
</StackLayout>
</StackLayout>
</ScrollView>
</ContentView>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -1,7 +1,5 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
@@ -11,8 +9,6 @@ namespace Bit.App.Pages
{
public partial class LoginPage : BaseContentPage
{
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly LoginPageViewModel _vm;
private readonly AppOptions _appOptions;
@@ -20,9 +16,6 @@ namespace Bit.App.Pages
public LoginPage(string email = null, AppOptions appOptions = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as LoginPageViewModel;
@@ -33,7 +26,6 @@ namespace Bit.App.Pages
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () =>
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
};
_vm.Email = email;
@@ -54,6 +46,11 @@ namespace Bit.App.Pages
{
ToolbarItems.Add(_getPasswordHint);
}
if (!_appOptions?.IosExtension ?? false)
{
ToolbarItems.Remove(_closeItem);
}
}
public Entry MasterPasswordEntry { get; set; }
@@ -61,6 +58,15 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
_mainContent.Content = _mainLayout;
if (await ShowAccountSwitcherAsync())
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
else
{
ToolbarItems.Remove(_accountAvatar);
}
await _vm.InitAsync();
if (!_inputFocused)
{
@@ -130,5 +136,23 @@ namespace Bit.App.Pages
var page = new UpdateTempPasswordPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async void AccountSwitch_Clicked(object sender, EventArgs e)
{
if (_accountListOverlay.IsVisible)
{
await ShowAccountListAsync(false, _accountListContainer, _accountListOverlay);
}
else
{
await RefreshAccountViewsAsync(_accountListView, false);
await ShowAccountListAsync(true, _accountListContainer, _accountListOverlay);
}
}
private async void AccountRow_Selected(object sender, SelectedItemChangedEventArgs e)
{
await AccountRowSelectedAsync(sender, e, _accountListContainer, _accountListOverlay, null, true);
}
}
}

View File

@@ -1,25 +1,21 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.View;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LoginPageViewModel : CaptchaProtectedViewModel
{
private const string Keys_RememberedEmail = "rememberedEmail";
private const string Keys_RememberEmail = "rememberEmail";
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private readonly IEnvironmentService _environmentService;
@@ -34,7 +30,6 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
@@ -67,6 +62,11 @@ namespace Bit.App.Pages
set => SetProperty(ref _masterPassword, value);
}
public ExtendedObservableCollection<AccountView> AccountViews
{
get => _stateService.AccountViews;
}
public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? "" : "";
@@ -85,9 +85,9 @@ namespace Bit.App.Pages
{
if (string.IsNullOrWhiteSpace(Email))
{
Email = await _storageService.GetAsync<string>(Keys_RememberedEmail);
Email = await _stateService.GetRememberedEmailAsync();
}
var rememberEmail = await _storageService.GetAsync<bool?>(Keys_RememberEmail);
var rememberEmail = await _stateService.GetRememberEmailAsync();
RememberEmail = rememberEmail.GetValueOrDefault(true);
}
@@ -131,11 +131,11 @@ namespace Bit.App.Pages
var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken);
if (RememberEmail)
{
await _storageService.SaveAsync(Keys_RememberedEmail, Email);
await _stateService.SetRememberedEmailAsync(Email);
}
else
{
await _storageService.RemoveAsync(Keys_RememberedEmail);
await _stateService.SetRememberedEmailAsync(null);
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
@@ -163,8 +163,6 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
LogInSuccessAction?.Invoke();
}
@@ -187,7 +185,7 @@ namespace Bit.App.Pages
ShowPassword = !ShowPassword;
var entry = (Page as LoginPage).MasterPasswordEntry;
entry.Focus();
entry.CursorPosition = MasterPassword.Length;
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
}
}
}

View File

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

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
@@ -16,16 +15,12 @@ namespace Bit.App.Pages
{
public class LoginSsoPageViewModel : BaseViewModel
{
private const string Keys_RememberedOrgIdentifier = "rememberedOrgIdentifier";
private const string Keys_RememberOrgIdentifier = "rememberOrgIdentifier";
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IApiService _apiService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
@@ -40,7 +35,6 @@ namespace Bit.App.Pages
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>("cryptoFunctionService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
@@ -66,9 +60,9 @@ namespace Bit.App.Pages
{
if (string.IsNullOrWhiteSpace(OrgIdentifier))
{
OrgIdentifier = await _storageService.GetAsync<string>(Keys_RememberedOrgIdentifier);
OrgIdentifier = await _stateService.GetRememberedOrgIdentifierAsync();
}
var rememberOrgIdentifier = await _storageService.GetAsync<bool?>(Keys_RememberOrgIdentifier);
var rememberOrgIdentifier = await _stateService.GetRememberOrgIdentifierAsync();
RememberOrgIdentifier = rememberOrgIdentifier.GetValueOrDefault(true);
}
@@ -139,7 +133,7 @@ namespace Bit.App.Pages
var code = GetResultCode(authResult, state);
if (!string.IsNullOrEmpty(code))
{
await LogIn(code, codeVerifier, redirectUri);
await LogIn(code, codeVerifier, redirectUri, OrgIdentifier);
}
else
{
@@ -164,19 +158,19 @@ namespace Bit.App.Pages
return code;
}
private async Task LogIn(string code, string codeVerifier, string redirectUri)
private async Task LogIn(string code, string codeVerifier, string redirectUri, string orgId)
{
try
{
var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri);
var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri, orgId);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (RememberOrgIdentifier)
{
await _storageService.SaveAsync(Keys_RememberedOrgIdentifier, OrgIdentifier);
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
}
else
{
await _storageService.RemoveAsync(Keys_RememberedOrgIdentifier);
await _stateService.SetRememberedOrgIdentifierAsync(null);
}
await _deviceActionService.HideLoadingAsync();
if (response.TwoFactor)
@@ -193,8 +187,6 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
SsoAuthSuccessAction?.Invoke();
}

View File

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

View File

@@ -203,7 +203,7 @@ namespace Bit.App.Pages
ShowPassword = !ShowPassword;
var entry = (Page as RegisterPage).MasterPasswordEntry;
entry.Focus();
entry.CursorPosition = MasterPassword.Length;
entry.CursorPosition = String.IsNullOrEmpty(MasterPassword) ? 0 : MasterPassword.Length;
}
public void ToggleConfirmPassword()
@@ -211,7 +211,7 @@ namespace Bit.App.Pages
ShowPassword = !ShowPassword;
var entry = (Page as RegisterPage).ConfirmMasterPasswordEntry;
entry.Focus();
entry.CursorPosition = ConfirmMasterPassword.Length;
entry.CursorPosition = String.IsNullOrEmpty(ConfirmMasterPassword) ? 0 : ConfirmMasterPassword.Length;
}
}
}

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.RemoveMasterPasswordPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:RemoveMasterPasswordPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:RemoveMasterPasswordPageViewModel />
</ContentPage.BindingContext>
<StackLayout Spacing="20"
Padding="10, 5">
<StackLayout Spacing="18"
Padding="30">
<Label x:Name="_warningLabel"
HorizontalTextAlignment="Center"/>
<Label x:Name="_warningLabel2"
HorizontalTextAlignment="Center"/>
</StackLayout>
<StackLayout Spacing="5">
<Button Text="{u:I18n Continue}"
StyleClass="btn-primary"
Clicked="Continue_Clicked" />
<Button Text="{u:I18n LeaveOrganization}"
Clicked="LeaveOrg_Clicked" />
</StackLayout>
</StackLayout>
</pages:BaseContentPage>

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using Bit.App.Resources;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class RemoveMasterPasswordPage : BaseContentPage
{
private readonly RemoveMasterPasswordPageViewModel _vm;
public Action NavigateAction { get; set; }
public RemoveMasterPasswordPage()
{
InitializeComponent();
_vm = BindingContext as RemoveMasterPasswordPageViewModel;
}
protected override async void OnAppearing()
{
await _vm.Init();
_warningLabel.Text = string.Format(AppResources.RemoveMasterPasswordWarning,
_vm.Organization.Name);
_warningLabel2.Text = AppResources.RemoveMasterPasswordWarning2;
}
private async void Continue_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())
{
await _vm.MigrateAccount();
await Navigation.PopModalAsync();
}
}
private async void LeaveOrg_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())
{
var confirm = await DisplayAlert(AppResources.LeaveOrganization,
string.Format(AppResources.LeaveOrganizationName, _vm.Organization.Name),
AppResources.Yes, AppResources.No);
if (confirm)
{
await _vm.LeaveOrganization();
await Navigation.PopModalAsync();
}
}
}
protected override async void OnDisappearing()
{
NavigateAction?.Invoke();
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
namespace Bit.App.Pages
{
public class RemoveMasterPasswordPageViewModel : BaseViewModel
{
private readonly IKeyConnectorService _keyConnectorService;
private readonly IDeviceActionService _deviceActionService;
private readonly IApiService _apiService;
private readonly ISyncService _syncService;
public Organization Organization;
public RemoveMasterPasswordPageViewModel()
{
PageTitle = AppResources.RemoveMasterPassword;
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
}
public async Task Init()
{
Organization = await _keyConnectorService.GetManagingOrganization();
}
public async Task MigrateAccount()
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _keyConnectorService.MigrateUser();
await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync();
}
public async Task LeaveOrganization()
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _apiService.PostLeaveOrganization(Organization.Id);
await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync();
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@@ -23,7 +22,6 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IStorageService _storageService;
private readonly IApiService _apiService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IEnvironmentService _environmentService;
@@ -43,7 +41,6 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_authService = ServiceContainer.Resolve<IAuthService>("authService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
@@ -204,7 +201,9 @@ namespace Bit.App.Pages
{
callbackUri = callbackUri,
data = JsonConvert.SerializeObject(providerData),
headerText = AppResources.Fido2Title,
btnText = AppResources.Fido2AuthenticateWebAuthn,
btnReturnText = AppResources.Fido2ReturnToApp,
});
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
@@ -301,8 +300,6 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
TwoFactorAuthSuccessAction?.Invoke();
}
}

View File

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

View File

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

View File

@@ -1,10 +1,11 @@
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
@@ -13,8 +14,9 @@ namespace Bit.App.Pages
{
public class BaseContentPage : ContentPage
{
private IStorageService _storageService;
private IStateService _stateService;
private IDeviceActionService _deviceActionService;
private IMessagingService _messagingService;
protected int ShowModalAnimationDelay = 400;
protected int ShowPageAnimationDelay = 100;
@@ -23,16 +25,17 @@ namespace Bit.App.Pages
{
if (Device.RuntimePlatform == Device.iOS)
{
On<iOS>().SetUseSafeArea(true);
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
}
}
public DateTime? LastPageAction { get; set; }
protected override void OnAppearing()
protected async override void OnAppearing()
{
base.OnAppearing();
SaveActivity();
await SaveActivity();
}
public bool DoOnce(Action action = null, int milliseconds = 1000)
@@ -105,22 +108,121 @@ namespace Bit.App.Pages
});
}
protected async Task<bool> ShowAccountSwitcherAsync()
{
return await _stateService.HasMultipleAccountsAsync();
}
protected async Task RefreshAccountViewsAsync(Xamarin.Forms.ListView accountListView, bool allowAddAccountRow)
{
await _stateService.RefreshAccountViewsAsync(allowAddAccountRow);
// Property change trigger on account listview is yielding inconsistent results, using a hammer instead
accountListView.ItemsSource = null;
accountListView.ItemsSource = _stateService.AccountViews;
}
protected async Task<AvatarImageSource> GetAvatarImageSourceAsync(bool useCurrentActiveAccount = true)
{
return new AvatarImageSource(useCurrentActiveAccount ? await _stateService.GetEmailAsync() : null);
}
protected async Task ShowAccountListAsync(bool isVisible, View listContainer, View overlay, View fab = null)
{
Device.BeginInvokeOnMainThread(async () =>
{
// Not all animations are awaited. This is intentional to allow multiple simultaneous animations.
if (isVisible)
{
// start listView in default (off-screen) position
await listContainer.TranslateTo(0, listContainer.Height * -1, 0);
// set overlay opacity to zero before making visible and start fade-in
overlay.Opacity = 0;
overlay.IsVisible = true;
overlay.FadeTo(1, 100);
if (Device.RuntimePlatform == Device.Android && fab != null)
{
// start fab fade-out
fab.FadeTo(0, 200);
}
// slide account list into view
await listContainer.TranslateTo(0, 0, 200, Easing.SinOut);
}
else
{
// start overlay fade-out
overlay.FadeTo(0, 200);
if (Device.RuntimePlatform == Device.Android && fab != null)
{
// start fab fade-in
fab.FadeTo(1, 200);
}
// slide account list out of view
await listContainer.TranslateTo(0, listContainer.Height * -1, 200, Easing.SinIn);
// remove overlay
overlay.IsVisible = false;
}
});
}
protected async Task AccountRowSelectedAsync(object sender, SelectedItemChangedEventArgs e, View listContainer,
View overlay, View fab = null, bool? allowActiveAccountSelection = false)
{
if (!DoOnce())
{
return;
}
if (!(e.SelectedItem is AccountViewCellViewModel item))
{
return;
}
((Xamarin.Forms.ListView)sender).SelectedItem = null;
await Task.Delay(100);
await ShowAccountListAsync(false, listContainer, overlay, fab);
if (item.AccountView.IsAccount)
{
if (item.AccountView.AuthStatus != AuthenticationStatus.Active)
{
await _stateService.SetActiveUserAsync(item.AccountView.UserId);
_messagingService.Send("switchedAccount");
}
else if (allowActiveAccountSelection ?? false)
{
_messagingService.Send("switchedAccount");
}
}
else
{
_messagingService.Send("addAccount");
}
}
private void SetServices()
{
if (_storageService == null)
if (_stateService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
}
if (_deviceActionService == null)
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
}
if (_messagingService == null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
}
}
private void SaveActivity()
private async Task SaveActivity()
{
SetServices();
_storageService.SaveAsync(Constants.LastActiveTimeKey, _deviceActionService.GetActiveTime());
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
}
}

View File

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

View File

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

View File

@@ -68,8 +68,7 @@
<controls:MonoLabel LineBreakMode="CharacterWrap"
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform"
TextType="Html"
StyleClass="list-title, list-title-platform, text-html"
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}" />
<Label LineBreakMode="TailTruncation"
Grid.Column="0"

View File

@@ -12,6 +12,7 @@ namespace Bit.App.Pages
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IClipboardService _clipboardService;
private bool _showNoData;
@@ -20,6 +21,7 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
PageTitle = AppResources.PasswordHistory;
History = new ExtendedObservableCollection<GeneratedPasswordHistory>();
@@ -51,7 +53,7 @@ namespace Bit.App.Pages
private async void CopyAsync(GeneratedPasswordHistory ph)
{
await _platformUtilsService.CopyToClipboardAsync(ph.Password);
await _clipboardService.CopyTextAsync(ph.Password);
_platformUtilsService.ShowToast("info", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}

View File

@@ -63,10 +63,9 @@
</Frame>
</Grid>
<controls:MonoLabel
StyleClass="text-lg, text-html"
Text="{Binding ColoredPassword, Mode=OneWay}"
TextType="Html"
Margin="0, 20"
StyleClass="text-lg"
HorizontalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
LineBreakMode="CharacterWrap" />

View File

@@ -13,6 +13,7 @@ namespace Bit.App.Pages
{
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IClipboardService _clipboardService;
private PasswordGenerationOptions _options;
private PasswordGeneratorPolicyOptions _enforcedPolicyOptions;
@@ -38,6 +39,8 @@ namespace Bit.App.Pages
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
PageTitle = AppResources.PasswordGenerator;
TypeOptions = new List<string> { AppResources.Password, AppResources.Passphrase };
}
@@ -305,7 +308,7 @@ namespace Bit.App.Pages
public async Task CopyAsync()
{
await _platformUtilsService.CopyToClipboardAsync(Password);
await _clipboardService.CopyTextAsync(Password);
_platformUtilsService.ShowToast("success", null,
string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}

View File

@@ -6,6 +6,8 @@
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:behaviors="clr-namespace:Bit.App.Behaviors"
xmlns:effects="clr-namespace:Bit.App.Effects"
x:DataType="pages:SendAddEditPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
@@ -201,7 +203,15 @@
Text="{Binding Send.Text.Text}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
Margin="{Binding EditorMargins}" />
Margin="{Binding EditorMargins}"
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
<Editor.Behaviors>
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
</Editor.Behaviors>
<Editor.Effects>
<effects:ScrollEnabledEffect />
</Editor.Effects>
</Editor>
<BoxView
StyleClass="box-row-separator"
IsVisible="{Binding ShowEditorSeparators}" />
@@ -443,7 +453,15 @@
Text="{Binding Send.Notes}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
Margin="{Binding EditorMargins}" />
Margin="{Binding EditorMargins}"
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
<Editor.Behaviors>
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
</Editor.Behaviors>
<Editor.Effects>
<effects:ScrollEnabledEffect />
</Editor.Effects>
</Editor>
<BoxView
StyleClass="box-row-separator"
IsVisible="{Binding ShowEditorSeparators}" />

View File

@@ -19,7 +19,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly ISendService _sendService;
private bool _sendEnabled;
private bool _canAccessPremium;
@@ -51,7 +51,7 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_sendService = ServiceContainer.Resolve<ISendService>("sendService");
TogglePasswordCommand = new Command(TogglePassword);
@@ -220,8 +220,8 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
_canAccessPremium = await _userService.CanAccessPremiumAsync();
_emailVerified = await _userService.GetEmailVerifiedAsync();
_canAccessPremium = await _stateService.CanAccessPremiumAsync();
_emailVerified = await _stateService.GetEmailVerifiedAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync();
SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail;

View File

@@ -161,7 +161,7 @@ namespace Bit.App.Pages
if (DoOnce())
{
var page = new SendsPage(_vm.Filter, _vm.Type != null);
await Navigation.PushModalAsync(new NavigationPage(page), false);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}

View File

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

View File

@@ -17,7 +17,6 @@
<ContentPage.Resources>
<ResourceDictionary>
<u:DateTimeConverter x:Key="dateTime" />
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Name="_closeItem" x:Key="closeItem" />
<StackLayout

View File

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

View File

@@ -25,21 +25,27 @@
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<Frame
IsVisible="{Binding DisablePrivateVaultPolicyEnabled}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<StackLayout
StyleClass="box"
Spacing="20">
<Frame
IsVisible="{Binding DisablePrivateVaultPolicyEnabled}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<Grid
RowSpacing="10"
RowDefinitions="70, Auto, Auto">
<StackLayout
StyleClass="box-row"
Grid.Row="0">
<Label
Text="{u:I18n FileFormat}"
StyleClass="box-label" />
@@ -51,23 +57,37 @@
StyleClass="box-value"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}" />
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackLayout
StyleClass="box-row"
Grid.Row="1"
IsVisible="{Binding UseOTPVerification}">
<Label
Text="{u:I18n MasterPassword}"
Text="{u:I18n SendVerificationCodeToEmail}"
StyleClass="box-label"
LineBreakMode="WordWrap"
Margin="0,0,0,10" />
<Button x:Name="_requestOTP"
Text="{u:I18n SendCode}"
Clicked="RequestOTP_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
Margin="0,0,0,10"/>
</StackLayout>
<Grid
StyleClass="box-row"
Grid.Row="2"
RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="*,Auto"
Padding="0">
<Label
Text="{Binding SecretName}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
Grid.ColumnSpan="2" />
<controls:MonoEntry
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
x:Name="_secret"
Text="{Binding Secret}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
@@ -81,22 +101,33 @@
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Row="1"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
<Label
Text="{u:I18n ConfirmYourIdentity}"
StyleClass="box-footer-label"
LineBreakMode="WordWrap"
Grid.Row="2"
Margin="0,10,0,0"
IsVisible="{Binding UseOTPVerification}" />
</Grid>
</Grid>
<StackLayout
StyleClass="box-row">
<Label
Text="{u:I18n ExportVaultMasterPasswordDescription}"
StyleClass="box-footer-label, box-footer-label-switch" />
<StackLayout Spacing="20">
<Button Text="{u:I18n ExportVault}"
Clicked="ExportVault_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"/>
</StackLayout>
StyleClass="box-footer-label, box-footer-label-switch"
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
<Button
Text="{u:I18n ExportVault}"
Clicked="ExportVault_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"/>
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -17,7 +17,7 @@ namespace Bit.App.Pages
_vm = BindingContext as ExportVaultPageViewModel;
_vm.Page = this;
_fileFormatPicker.ItemDisplayBinding = new Binding("Value");
MasterPasswordEntry = _masterPassword;
SecretEntry = _secret;
}
protected async override void OnAppearing()
@@ -39,7 +39,7 @@ namespace Bit.App.Pages
});
}
});
RequestFocus(_masterPassword);
RequestFocus(_secret);
}
protected async override void OnDisappearing()
@@ -48,7 +48,7 @@ namespace Bit.App.Pages
_broadcasterService.Unsubscribe(nameof(ExportVaultPage));
}
public Entry MasterPasswordEntry { get; set; }
public Entry SecretEntry { get; set; }
private async void Close_Clicked(object sender, System.EventArgs e)
{
@@ -66,6 +66,15 @@ namespace Bit.App.Pages
}
}
private async void RequestOTP_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await _vm.RequestOTP();
_requestOTP.IsEnabled = false;
}
}
void FileFormat_Changed(object sender, EventArgs e)
{
_vm?.UpdateWarning();

View File

@@ -4,11 +4,10 @@ using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
@@ -21,26 +20,33 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly II18nService _i18nService;
private readonly ICryptoService _cryptoService;
private readonly IExportService _exportService;
private readonly IPolicyService _policyService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IUserVerificationService _userVerificationService;
private readonly IApiService _apiService;
private int _fileFormatSelectedIndex;
private string _exportWarningMessage;
private bool _showPassword;
private string _masterPassword;
private string _secret;
private byte[] _exportResult;
private string _defaultFilename;
private bool _initialized = false;
private bool _useOTPVerification = false;
private string _secretName;
private string _instructionText;
public ExportVaultPageViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
PageTitle = AppResources.ExportVault;
TogglePasswordCommand = new Command(TogglePassword);
@@ -59,7 +65,19 @@ namespace Bit.App.Pages
_initialized = true;
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
UseOTPVerification = await _keyConnectorService.GetUsesKeyConnector();
if (UseOTPVerification)
{
InstructionText = _i18nService.T("ExportVaultOTPDescription");
SecretName = _i18nService.T("VerificationCode");
}
else
{
InstructionText = _i18nService.T("ExportVaultMasterPasswordDescription");
SecretName = _i18nService.T("MasterPassword");
}
UpdateWarning();
}
@@ -94,10 +112,28 @@ namespace Bit.App.Pages
additionalPropertyNames: new string[] {nameof(ShowPasswordIcon)});
}
public string MasterPassword
public bool UseOTPVerification
{
get => _masterPassword;
set => SetProperty(ref _masterPassword, value);
get => _useOTPVerification;
set => SetProperty(ref _useOTPVerification, value);
}
public string Secret
{
get => _secret;
set => SetProperty(ref _secret, value);
}
public string SecretName
{
get => _secretName;
set => SetProperty(ref _secretName, value);
}
public string InstructionText
{
get => _instructionText;
set => SetProperty(ref _instructionText, value);
}
public Command TogglePasswordCommand { get; }
@@ -107,27 +143,13 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
(Page as ExportVaultPage).MasterPasswordEntry.Focus();
(Page as ExportVaultPage).SecretEntry.Focus();
}
public Command ExportVaultCommand { get; }
public async Task ExportVaultAsync()
{
if (string.IsNullOrEmpty(_masterPassword))
{
await _platformUtilsService.ShowDialogAsync(_i18nService.T("InvalidMasterPassword"));
return;
}
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(_masterPassword, null);
MasterPassword = string.Empty;
if (!passwordValid)
{
await _platformUtilsService.ShowDialogAsync(_i18nService.T("InvalidMasterPassword"));
return;
}
bool userConfirmedExport = await _platformUtilsService.ShowDialogAsync(ExportWarningMessage,
_i18nService.T("ExportVaultConfirmationTitle"), _i18nService.T("ExportVault"), _i18nService.T("Cancel"));
@@ -136,6 +158,16 @@ namespace Bit.App.Pages
return;
}
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
? VerificationType.OTP
: VerificationType.MasterPassword;
if (!await _userVerificationService.VerifyUser(Secret, verificationType))
{
return;
}
Secret = string.Empty;
try
{
var data = await _exportService.GetExport(FileFormatOptions[FileFormatSelectedIndex].Key);
@@ -162,6 +194,27 @@ namespace Bit.App.Pages
}
}
public async Task RequestOTP()
{
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Sending);
await _apiService.PostAccountRequestOTP();
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.CodeSent);
}
catch (ApiException e)
{
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
public async void SaveFileSelected(string contentUri, string filename)
{
if (_deviceActionService.SaveFile(_exportResult, null, filename ?? _defaultFilename, contentUri))

View File

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

View File

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

View File

@@ -1,10 +1,11 @@
using System.ComponentModel;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Utilities;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Pages.Accounts;
using Bit.App.Resources;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -134,6 +135,10 @@ namespace Bit.App.Pages
{
await _vm.LogOutAsync();
}
else if (item.Name == AppResources.DeleteAccount)
{
await Navigation.PushModalAsync(new NavigationPage(new DeleteAccountPage()));
}
else if (item.Name == AppResources.LockNow)
{
await _vm.LockAsync();

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
@@ -17,15 +16,17 @@ namespace Bit.App.Pages
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly ICryptoService _cryptoService;
private readonly IUserService _userService;
private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService;
private readonly IEnvironmentService _environmentService;
private readonly IMessagingService _messagingService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IStorageService _storageService;
private readonly ISyncService _syncService;
private readonly IBiometricService _biometricService;
private readonly IPolicyService _policyService;
private readonly ILocalizeService _localizeService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IClipboardService _clipboardService;
private const int CustomVaultTimeoutValue = -100;
@@ -35,6 +36,8 @@ namespace Bit.App.Pages
private string _lastSyncDate;
private string _vaultTimeoutDisplayValue;
private string _vaultTimeoutActionDisplayValue;
private bool _showChangeMasterPassword;
private List<KeyValuePair<string, int?>> _vaultTimeouts =
new List<KeyValuePair<string, int?>>
{
@@ -57,21 +60,23 @@ namespace Bit.App.Pages
};
private Policy _vaultTimeoutPolicy;
private int _vaultTimeout;
private int? _vaultTimeout;
public SettingsPageViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
PageTitle = AppResources.Settings;
@@ -86,8 +91,9 @@ namespace Bit.App.Pages
if (lastSync != null)
{
lastSync = lastSync.Value.ToLocalTime();
_lastSyncDate = string.Format("{0} {1}", lastSync.Value.ToShortDateString(),
lastSync.Value.ToShortTimeString());
_lastSyncDate = string.Format("{0} {1}",
_localizeService.GetLocaleShortDate(lastSync.Value),
_localizeService.GetLocaleShortTime(lastSync.Value));
}
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
@@ -102,7 +108,7 @@ namespace Bit.App.Pages
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey) ?? "lock";
var action = await _stateService.GetVaultTimeoutActionAsync() ?? "lock";
_vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2;
@@ -113,6 +119,9 @@ namespace Bit.App.Pages
_vaultTimeoutDisplayValue = AppResources.Custom;
}
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
!await _keyConnectorService.GetUsesKeyConnector();
BuildList();
}
@@ -125,7 +134,7 @@ namespace Bit.App.Pages
AppResources.Close);
if (copy)
{
await _platformUtilsService.CopyToClipboardAsync(debugText);
await _clipboardService.CopyTextAsync(debugText);
}
}
@@ -139,7 +148,7 @@ namespace Bit.App.Pages
List<string> fingerprint;
try
{
fingerprint = await _cryptoService.GetFingerprintAsync(await _userService.GetUserIdAsync());
fingerprint = await _cryptoService.GetFingerprintAsync(await _stateService.GetActiveUserIdAsync());
}
catch (Exception e) when (e.Message == "No public key available.")
{
@@ -220,7 +229,7 @@ namespace Bit.App.Pages
await _vaultTimeoutService.LockAsync(true, true);
}
public async Task VaultTimeoutAsync(bool promptOptions = true, int newTimeout = 0)
public async Task VaultTimeoutAsync(bool promptOptions = true, int? newTimeout = 0)
{
var oldTimeout = _vaultTimeout;
@@ -237,7 +246,7 @@ namespace Bit.App.Pages
var cleanSelection = selection.Replace("✓ ", string.Empty);
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
_vaultTimeoutDisplayValue = selectionOption.Key;
newTimeout = selectionOption.Value.GetValueOrDefault();
newTimeout = selectionOption.Value;
}
if (_vaultTimeoutPolicy != null)
@@ -309,13 +318,17 @@ namespace Bit.App.Pages
AppResources.SetPINDescription, null, AppResources.Submit, AppResources.Cancel, true);
if (!string.IsNullOrWhiteSpace(pin))
{
var masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
AppResources.Yes, AppResources.No);
var masterPassOnRestart = false;
if (!await _keyConnectorService.GetUsesKeyConnector())
{
masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
AppResources.Yes, AppResources.No);
}
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
@@ -325,12 +338,12 @@ namespace Bit.App.Pages
if (masterPassOnRestart)
{
var encPin = await _cryptoService.EncryptAsync(pin);
await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString);
_vaultTimeoutService.PinProtectedKey = pinProtectedKey;
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
await _stateService.SetPinProtectedCachedAsync(pinProtectedKey);
}
else
{
await _storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString);
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
}
}
else
@@ -365,13 +378,13 @@ namespace Bit.App.Pages
if (_biometric)
{
await _biometricService.SetupBiometricAsync();
await _storageService.SaveAsync(Constants.BiometricUnlockKey, true);
await _stateService.SetBiometricUnlockAsync(true);
}
else
{
await _storageService.RemoveAsync(Constants.BiometricUnlockKey);
await _stateService.SetBiometricUnlockAsync(null);
}
_vaultTimeoutService.BiometricLocked = false;
_stateService.BiometricLocked = false;
await _cryptoService.ToggleKeyAsync();
BuildList();
}
@@ -418,25 +431,6 @@ namespace Bit.App.Pages
new SettingsPageListItem { Name = AppResources.LockNow },
new SettingsPageListItem { Name = AppResources.TwoStepLogin }
};
if (_vaultTimeoutDisplayValue == AppResources.Custom)
{
securityItems.Insert(1, new SettingsPageListItem
{
Name = AppResources.Custom,
Time = TimeSpan.FromMinutes(Math.Abs((double) _vaultTimeout)),
});
}
if (_vaultTimeoutPolicy != null)
{
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes").GetValueOrDefault();
securityItems.Insert(0, new SettingsPageListItem
{
Name = string.Format(AppResources.VaultTimeoutPolicyInEffect,
Math.Floor((float) maximumTimeout / 60),
maximumTimeout % 60),
UseFrame = true,
});
}
if (_supportsBiometric || _biometric)
{
var biometricName = AppResources.Biometrics;
@@ -452,12 +446,31 @@ namespace Bit.App.Pages
};
securityItems.Insert(2, item);
}
if (_vaultTimeoutDisplayValue == AppResources.Custom)
{
securityItems.Insert(1, new SettingsPageListItem
{
Name = AppResources.Custom,
Time = TimeSpan.FromMinutes(Math.Abs((double)_vaultTimeout.GetValueOrDefault())),
});
}
if (_vaultTimeoutPolicy != null)
{
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes").GetValueOrDefault();
securityItems.Insert(0, new SettingsPageListItem
{
Name = string.Format(AppResources.VaultTimeoutPolicyInEffect,
Math.Floor((float)maximumTimeout / 60),
maximumTimeout % 60),
UseFrame = true,
});
}
var accountItems = new List<SettingsPageListItem>
{
new SettingsPageListItem { Name = AppResources.FingerprintPhrase },
new SettingsPageListItem { Name = AppResources.LogOut }
};
if (IncludeLinksWithSubscriptionInfo())
if (_showChangeMasterPassword)
{
accountItems.Insert(0, new SettingsPageListItem { Name = AppResources.ChangeMasterPassword });
}
@@ -476,7 +489,8 @@ namespace Bit.App.Pages
new SettingsPageListItem { Name = AppResources.Options },
new SettingsPageListItem { Name = AppResources.About },
new SettingsPageListItem { Name = AppResources.HelpAndFeedback },
new SettingsPageListItem { Name = AppResources.RateTheApp }
new SettingsPageListItem { Name = AppResources.RateTheApp },
new SettingsPageListItem { Name = AppResources.DeleteAccount }
};
GroupedItems.ResetWithRange(new List<SettingsPageListGroup>
{

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
@@ -12,8 +11,9 @@ namespace Bit.App.Pages
{
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStorageService _storageService;
private readonly IStateService _stateService;
private readonly ISyncService _syncService;
private readonly ILocalizeService _localizeService;
private string _lastSync = "--";
private bool _inited;
@@ -23,8 +23,9 @@ namespace Bit.App.Pages
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
PageTitle = AppResources.Sync;
}
@@ -50,7 +51,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
await SetLastSyncAsync();
EnableSyncOnRefresh = await _storageService.GetAsync<bool>(Constants.SyncOnRefreshKey);
EnableSyncOnRefresh = await _stateService.GetSyncOnRefreshAsync();
_inited = true;
}
@@ -58,7 +59,7 @@ namespace Bit.App.Pages
{
if (_inited)
{
await _storageService.SaveAsync(Constants.SyncOnRefreshKey, _syncOnRefresh);
await _stateService.SetSyncOnRefreshAsync(_syncOnRefresh);
}
}
@@ -68,7 +69,9 @@ namespace Bit.App.Pages
if (last != null)
{
var localDate = last.Value.ToLocalTime();
LastSync = string.Format("{0} {1}", localDate.ToShortDateString(), localDate.ToShortTimeString());
LastSync = string.Format("{0} {1}",
_localizeService.GetLocaleShortDate(localDate),
_localizeService.GetLocaleShortTime(localDate));
}
else
{

View File

@@ -2,6 +2,7 @@
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -10,6 +11,7 @@ namespace Bit.App.Pages
public class TabsPage : TabbedPage
{
private readonly IMessagingService _messagingService;
private readonly IKeyConnectorService _keyConnectorService;
private NavigationPage _groupingsPage;
private NavigationPage _sendGroupingsPage;
@@ -18,6 +20,7 @@ namespace Bit.App.Pages
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage))
{
@@ -43,7 +46,7 @@ namespace Bit.App.Pages
var settingsPage = new NavigationPage(new SettingsPage(this))
{
Title = AppResources.Settings,
IconImageSource = "cog.png"
IconImageSource = "cog_settings.png"
};
Children.Add(settingsPage);
@@ -72,6 +75,15 @@ namespace Bit.App.Pages
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
if (await _keyConnectorService.UserNeedsMigration())
{
_messagingService.Send("convertAccountToKeyConnector");
}
}
public void ResetToVaultPage()
{
CurrentPage = _groupingsPage;

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