1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-21 20:03:16 +00:00

Compare commits

...

97 Commits

Author SHA1 Message Date
Matt Portune
c1461ab16b FIDO2 implementation using Google Play Services on Android 2021-08-30 13:38:03 -04:00
Matt Portune
307a5a5843 FIDO2 WebAuthn support for mobile (#1519)
* FIDO2 / WebAuthn support for mobile

* fixes
2021-08-30 12:44:12 -04:00
Joseph Flinn
d050215ebc Simplifying Crowdin sync workflow (#1517) 2021-08-27 10:08:40 -07:00
Matt Portune
67e26c778b Revert "bump MinimumOSVersion to 12.0 (#1508)" (#1515)
This reverts commit 745b54bf2a.
2021-08-25 17:03:33 -04:00
Matt Portune
efb10d155d Revert "enable LLVM in iOS builds (#1510)" (#1514)
This reverts commit 244a6a7f41.
2021-08-25 17:03:02 -04:00
Matt Portune
913c673773 Revert "enable LLVM in iOS extensions (#1511)" (#1513)
This reverts commit 380c3583fb.
2021-08-25 17:02:49 -04:00
Joseph Flinn
339decafe4 Reverting changes to Chinese and Portuguese translations (#1512) 2021-08-24 11:57:34 -07:00
Matt Portune
380c3583fb enable LLVM in iOS extensions (#1511) 2021-08-23 11:40:16 -04:00
Matt Portune
244a6a7f41 enable LLVM in iOS builds (#1510) 2021-08-23 10:16:46 -04:00
Matt Portune
745b54bf2a bump MinimumOSVersion to 12.0 (#1508) 2021-08-20 09:54:39 -04:00
Joseph Flinn
4f86bb2043 Reverting English changes from the Auto Crowdin Sync (#1507) 2021-08-19 10:47:15 -07:00
github-actions[bot]
ada8a73849 Autosync Crowdin Translations (#1498)
* Autosync Crowdin translations

* Autosync Crowdin translations

Co-authored-by: github-actions <>
2021-08-18 15:14:15 -07:00
Matt Gibson
1e3204f91d Version bump to 2.12.0 (#1505) 2021-08-17 14:02:00 -05:00
Joseph Flinn
b5c6a57fa0 Crowdin sync workflow (#1499)
* Initial addition of the crowdin sync workflow for testing

* adding logic to handle the case where there are no Crowdin updates
2021-08-13 13:28:07 -07:00
Joseph Flinn
6ca5b66aa7 stubbing out the crowdin sync workflow (#1497) 2021-08-13 07:31:36 -07:00
Matt Gibson
24a0396d0f Fix iphone captcha throws (#1495) 2021-08-12 08:23:02 -05:00
Chad Scharf
7f7b673b0a Fix for EMM system configuration for mobile (#1434) 2021-08-11 10:36:36 -04:00
Matt Gibson
f79ff3fd63 Encode auth email for unicode email support (#1491) 2021-08-10 11:48:51 -05:00
Matt Gibson
2f2fa8a25b Feature/use hcaptcha if bot (#1476)
* Add captcha to login models and methods

* Add captcha web auth to login

* Extract captcha to abstract base class

* Add Captcha to register

* Null out captcha token after each successful challenge

* Cancel > close
2021-08-04 14:47:23 -05:00
Matt Gibson
9042b1009e Revert "Revert "Redefine cipher "share" to "move to organization""" (#1459)
* Revert "Revert "Redefine cipher "share" to "move to organization" (#1433)" (#1440)"

This reverts commit bd4a275558.

* Cancel > close
2021-08-04 14:46:28 -05:00
Vince Grassia
dbc0f490c5 Add Release workflow stub (#1482) 2021-07-27 13:25:12 -04:00
Matt Portune
6d8f627772 disable send test (#1481) 2021-07-27 10:51:47 -04:00
Jonathan Browne
b4c016c9d4 Change Close to Cancel in editing prompts. (#1480) 2021-07-27 09:25:42 -04:00
howyay
ae763ebca8 Add support for Mull browser (#1435) 2021-07-26 09:20:47 -04:00
Matt Portune
880483ac79 bump app version to 2.11.3 for testflight (#1477) 2021-07-23 16:33:15 -04:00
Georges Varouchas
f44e6ab75f bugfix in AuthService.LogInSsoAsync (#1474) (#1475)
add missing parameter in call to LogInHelperAsync
2021-07-23 14:36:49 -04:00
Matt Portune
10a718b0c7 bump version to 2.11.2 (#1473) 2021-07-23 11:33:53 -04:00
Thomas Rittson
9ec4050e4d Move remaining primary buttons in login flow (#1470)
* Fix show/hide logic on Continue button

* Fix bug in 2FA method selection modal

* Move Log In button in SSO flow

* Fix linting
2021-07-22 07:56:39 +10:00
Matt Portune
93e2c0df7c keep app version at 2.11.1 for now (#1471) 2021-07-21 14:03:38 -04:00
Matt Portune
8d07397a59 Revert "lib updates and app version bump (#1468)" (#1469)
This reverts commit 15d44b873b.
2021-07-20 16:21:41 -04:00
Matt Portune
15d44b873b lib updates and app version bump (#1468)
* lib updates and app version bump

* added explicit androidx core dependency
2021-07-20 12:57:01 -04:00
Sang
6a979d0ff5 2FA screen: move continue to a button (#1427)
* Move continue to a button

* Resolve pr comments

* Move use another two step method button

* Resolve code suggestions

* Resolve for iPhone

* Apply suggestions from code review

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

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2021-07-20 14:50:45 +10:00
Matt Portune
a4db088eda bugfix for incorrect type and nullable for some org vars (#1465) 2021-07-16 13:57:15 -04:00
Matt Portune
96454b7cbf add toggle for removing links to pages containing subscription info (#1463) 2021-07-16 12:02:00 -04:00
Matt Portune
9c1df2179c bump version to 2.11.1 (#1462) 2021-07-14 14:46:18 -04:00
Matt Portune
52c9125404 bump version to 2.11.0 (#1461) 2021-07-13 12:52:14 -04:00
Matt Portune
172a857604 handle intent exceptions to prevent denial of service (#1458) 2021-07-12 12:31:39 -04:00
Matt Portune
d8e68a266c bugfixes for lock block when setting timeout to immediate (#1455) 2021-07-12 10:58:17 -04:00
Matt Portune
1f57ba6c50 workaround for off-screen draw bug in XF4.5+ part 2 (#1454) 2021-07-09 17:29:57 -04:00
Matt Portune
ff19578807 Fix for password unlock for autofill and share-to-send on Android (#1453) 2021-07-09 11:48:03 -04:00
Matt Portune
9298d57f22 fix for resuming autofill when back button was previously used to exit (#1451) 2021-07-06 11:10:17 -04:00
Martin Choutka
8cf5d5728e MaximumAccessCountInfo locale string update (#1441)
String MaximumAccessCountInfo doesn't have "Send" with a capital "s".
2021-07-02 12:20:40 -04:00
Matt Portune
bdf6d764ca update csv lib and skip link (#1449) 2021-07-02 11:21:36 -04:00
Matt Portune
05e8da4bcc Workaround for off-screen draw bug in XF4.5+ (#1447)
* workaround for off-screen draw bug in xf4.5+

* check cols even if orgId is present
2021-06-30 17:46:59 -04:00
Matt Portune
382e547f74 fix for export password handling (#1448) 2021-06-30 16:05:30 -04:00
Kyle Spearrin
4f9985d2b0 New Crowdin updates (#1444)
* New translations AppResources.resx (Romanian)

* New translations copy.resx (Bosnian)

* New translations copy.resx (Kannada)

* New translations copy.resx (Tamil)

* New translations copy.resx (Tamil)

* New translations copy.resx (Bosnian)

* New translations copy.resx (Kannada)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Swedish)

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

* New translations AppResources.resx (Slovenian)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Bengali)

* New translations AppResources.resx (Danish)

* 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 (German)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Tamil)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Sinhala)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Latvian)

* New translations AppResources.resx (Hindi)

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

* New translations AppResources.resx (Malayalam)

* New translations AppResources.resx (Bosnian)

* New translations AppResources.resx (Kannada)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (English, India)

* New translations copy.resx (Bosnian)

* New translations copy.resx (German)

* New translations copy.resx (Danish)

* New translations copy.resx (Czech)

* New translations copy.resx (Catalan)

* New translations copy.resx (Bulgarian)

* New translations copy.resx (Belarusian)

* New translations copy.resx (Afrikaans)

* New translations copy.resx (Spanish)

* New translations copy.resx (French)

* New translations copy.resx (Romanian)

* New translations copy.resx (English, India)

* New translations copy.resx (Norwegian Bokmal)

* New translations copy.resx (Kannada)

* New translations copy.resx (Sinhala)

* New translations copy.resx (Malayalam)

* New translations copy.resx (Finnish)

* New translations copy.resx (Persian)

* New translations copy.resx (Ukrainian)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Vietnamese)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Tamil)

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

* New translations copy.resx (Bengali)

* New translations copy.resx (Thai)

* New translations copy.resx (Croatian)

* New translations copy.resx (Estonian)

* New translations copy.resx (Latvian)

* New translations copy.resx (Hindi)

* New translations copy.resx (Greek)

* New translations copy.resx (Hebrew)

* New translations copy.resx (Swedish)

* New translations copy.resx (Latvian)

* New translations copy.resx (Persian)

* New translations copy.resx (Tamil)

* New translations copy.resx (Bengali)

* New translations copy.resx (Thai)

* New translations copy.resx (Croatian)

* New translations copy.resx (Estonian)

* New translations copy.resx (Hindi)

* New translations copy.resx (Portuguese, Brazilian)

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

* New translations copy.resx (Malayalam)

* New translations copy.resx (Bosnian)

* New translations copy.resx (Sinhala)

* New translations copy.resx (Kannada)

* New translations copy.resx (Norwegian Bokmal)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Vietnamese)

* New translations copy.resx (Hungarian)

* New translations copy.resx (Russian)

* New translations copy.resx (Italian)

* New translations copy.resx (Japanese)

* New translations copy.resx (Korean)

* New translations copy.resx (Dutch)

* New translations copy.resx (Polish)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Slovak)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Slovenian)

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

* New translations copy.resx (Swedish)

* New translations copy.resx (Turkish)

* New translations copy.resx (Ukrainian)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Turkish)

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

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Italian)

* New translations copy.resx (Slovenian)

* New translations copy.resx (Italian)

* New translations copy.resx (Danish)

* New translations copy.resx (German)

* New translations copy.resx (Greek)

* New translations copy.resx (Finnish)

* New translations copy.resx (Hebrew)

* New translations copy.resx (Hungarian)

* New translations copy.resx (Japanese)

* New translations copy.resx (Catalan)

* New translations copy.resx (Korean)

* New translations copy.resx (Dutch)

* New translations copy.resx (Polish)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Russian)

* New translations copy.resx (Slovak)

* New translations copy.resx (Czech)

* New translations copy.resx (Bulgarian)

* New translations copy.resx (Belarusian)

* New translations copy.resx (Romanian)

* New translations copy.resx (French)

* New translations copy.resx (Spanish)

* New translations copy.resx (Afrikaans)

* New translations copy.resx (English, India)
2021-06-29 15:41:27 -04:00
Matt Portune
ef97417cd7 disable send tests (#1443) 2021-06-29 15:29:10 -04:00
Chad Scharf
7bdf4d8b18 Fix encoding of & in resx XML (#1445) 2021-06-29 14:32:07 -04:00
Matt Portune
a6c95d06b5 fix for vault timeout locking issue on android (#1442) 2021-06-29 10:23:20 -04:00
Matt Gibson
bd4a275558 Revert "Redefine cipher "share" to "move to organization" (#1433)" (#1440)
This reverts commit 2003ac9d2c.
2021-06-24 14:33:19 -05:00
Oscar Hinton
a2b46ee7cb Add help link to password reprompt (#1439) 2021-06-24 17:26:47 +02:00
Matt Gibson
2003ac9d2c Redefine cipher "share" to "move to organization" (#1433) 2021-06-18 06:52:01 -05:00
Oscar Hinton
2a5667251e Add a 250ms sleep before prompting for password (#1431) 2021-06-15 20:29:11 +02:00
Thomas Rittson
79589b07fc Use 2 iterations for local password hashing (#1423)
* Add HashPurpose parameter to HashPasswordAsync

* Use 2 iterations for local password hashing

* Force logout if user has old keyHash stored

* Revert "Force logout if user has old keyHash stored"

This reverts commit 497d4928fa.

* Add backwards compatability with existing keyHash
2021-06-15 07:39:34 +10:00
Oscar Hinton
0aed13a2cf Fix selecting ciphers in search not working (#1426) 2021-06-11 15:20:42 +02:00
Oscar Hinton
df412e75d1 More Autofill fixes (#1425) 2021-06-11 15:20:21 +02:00
Oscar Hinton
2b8dbde923 Fixes for password reprompt (#1416) 2021-06-10 17:57:18 +02:00
Matt Portune
33791a03ac track failed unlock attempts in storage (#1421) 2021-06-09 10:03:05 -04:00
Matt Gibson
80a33e98a2 Use type to ensure transmitted data is encrypted (#1422) 2021-06-09 08:45:30 -05:00
Trey Greer
afed18908b Asset update (#1417)
* updated screenshots and copy for iOS + Android

* fixed data for copy file

* fixed data for copy file for Google

* fixed more file formatting items for resx

* added Google video
2021-06-01 10:16:52 -04:00
Thomas Rittson
fe58dea3e0 Add encKeyValidation string to encrypted exports (#1412) 2021-05-29 06:16:19 +10:00
Kyle Spearrin
569045fcd5 add auth-email header to auth request (#1414) 2021-05-28 14:06:42 -04:00
Matt Portune
fdda670311 replaced blank taskaffinity with singletask launchmode (#1410) 2021-05-26 14:18:01 -04:00
CookieJarApps
fbb7b05b9c Added support for SmartCookieWeb and SmartCookieWeb Preview (#1411) 2021-05-26 13:41:16 -04:00
Oscar Hinton
976eeab6d7 Password reprompt (#1365)
* Make card number hidden

* Add support for password reprompt

* Rename PasswordPrompt to Reprompt

* Protect autofill

* Use Enums.CipherRepromptType

* Fix iOS not building

* Protect iOS autofill

* Update to match jslib

* Fix failing build
2021-05-21 15:13:54 +02:00
Matt Portune
e61bcd2785 fix for activity exporting and workaround for task affinity (#1408) 2021-05-19 15:55:16 -04:00
Captain Trips
570edb4319 fixes bitwarden/mobile#967 (#1067) 2021-05-17 15:38:21 -04:00
Vince Grassia
8fe8c42765 Pin versions of actions in workflow (#1405) 2021-05-17 15:15:08 -04:00
Matt Gibson
0eebe6b156 Encode exports as UTF8 (#1404) 2021-05-17 11:40:20 -05:00
Matt Portune
946831b37e version bumps (#1399) 2021-05-13 15:21:24 -04:00
Matt Portune
1d4e742d66 Forms update with CollectionView conversion (#1374)
* Forms update with CollectionView conversion

* updates

* removed unnecessary import
2021-05-13 14:36:20 -04:00
Matt Portune
29979f6b04 expand package visibility for Android 11+ (#1384) 2021-05-13 14:16:53 -04:00
Matt Portune
2f6e1ff477 lib updates (#1381)
* lib updates

* included csv and biometric lib
2021-05-13 14:15:26 -04:00
Thomas Rittson
c1030c48fa Explain how to verify email for file Sends (#1395)
* Explain how to verify email for file Sends

* Revert "Explain how to verify email for file Sends"

This reverts commit b72314bc93.

* Explain how to verify email for file Sends
2021-05-11 17:52:18 -04:00
Kyle Spearrin
ef5d08cb75 New Crowdin updates (#1397)
* New translations AppResources.resx (Romanian)

* New translations copy.resx (Tamil)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Chinese Simplified)

* 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 copy.resx (Tamil)

* New translations AppResources.resx (Bengali)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Latvian)

* New translations AppResources.resx (Hindi)

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

* New translations AppResources.resx (Malayalam)

* New translations AppResources.resx (Sinhala)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Turkish)

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

* New translations AppResources.resx (French)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Spanish)

* New translations copy.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 (Finnish)

* New translations AppResources.resx (Slovenian)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Hungarian)

* 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 (English, India)
2021-05-11 14:03:24 -04:00
Matt Portune
faa6904ce3 update firebase messaging lib and implementation (#1383)
* update firebase messaging lib and implementation

* update clean-fdroid script for updated firebase
2021-05-03 13:36:09 -04:00
Matt Portune
c27da8e7c4 fix for disappearing share sheet (#1386) 2021-04-27 17:49:01 -04:00
Matt Gibson
3ef5ca9cc0 Limit file upload sizes to 100MB (#1385)
both iOS and Android are having trouble with the current method
of loading the entire file to memory, encrypting it, and sending
to azure in one go.

We will need to come up with a chunking scheme to support
larger files in the future
2021-04-27 15:14:54 -05:00
Vincent Salucci
2fbd3b4538 [Version] Bump to 2.10.0 (#1382) 2021-04-22 12:00:01 -05:00
laymanZ
b3b21ea6b1 add microsoft package to support beta/dev/canary (#1370)
* add microsoft package to support beta/dev/canary

* Add package name in the necessary file
2021-04-22 00:16:19 -04:00
Matt Gibson
a3b4ede8f3 Use CipherByteArray to signify encrypted byte[] (#1366)
* Use CipherByteArray to signify encrypted  byte[]

* Rename CipherString and CipherByteArray to EncString and EncByteArray
2021-04-21 15:27:14 -05:00
Matt Portune
10ea6a86e3 clear cache on logout (#1375) 2021-04-19 15:38:49 -04:00
Thomas Rittson
3b2b37b3b0 Use UserService to manage emailVerified (#1367) 2021-04-15 14:54:58 +10:00
Matt Gibson
75e27ffbe3 Move renew endpoint to fix overlapping endpoint issue (#1362) 2021-04-12 09:45:17 -05:00
Thomas Rittson
a2cff6da28 Require user to verify email to use file Send (#1360) 2021-04-08 06:42:00 +10:00
Daniel James Smith
fe80fd0ba1 Replaced appveyor build badge with one from Github Workflow (#1350)
* Deleted appveyor.yml

* Remove Include of appveyor.yml from sln-file

* Deleted ci-build-apks.ps1 referenced by appveyor.yml

* Replaced build badge in README.md
2021-04-05 07:28:37 -07:00
dldhk97
5c6b9fa471 Add support for Soul browser (#1348) 2021-04-01 14:01:21 -04:00
Matt Portune
d926565358 Share-to-Send for Android (#1343)
* Android implementation

* remove iOS attempt for now
2021-03-31 10:19:05 -04:00
Matt Gibson
ce0b8bc62d Attachment azure upload blobs (#1345)
* Update Size limits

* Add new Api paths for direct upload of Cipher Attachments

* Add Attachment upload to fileUploadService

* Save with direct upload and fallback to legacy uplaod

CipherID is required for direct upload to request an upload URL

* Inform on when to remove legacy code

* Test Attachment upload
2021-03-30 18:42:43 -05:00
Thomas Rittson
04aeddc5de Hide email address in Sends (#1340)
* Add HideEmail model properties and locale strings

* Fix UI strings

* Add HideEmail to SendService

* Add HideEmail option to UI

* Tidy up declarations

* Add Bitwarden Send translation warning
2021-03-29 12:01:42 -04:00
Matt Gibson
13ffbe911a Send azure upload (#1334)
* Add direct upload api endpoints

* Create azure upload service

* Update max file size

* Update send file upload test

* Move internationalization string to correct document

* Allow for one shot blob uploads

* Remove unused helper

* Use FileUploadService

Fallback to legacy method on old server implementations.
2021-03-29 09:45:04 -05:00
Jamal
ab04759b0e Add support for Styx browser. (#1336)
https://github.com/jamal2362/Styx
2021-03-23 15:09:32 -04:00
Stéphane Lenclud
798cfef391 Add support for Fulguris browser. (#1333) 2021-03-22 16:54:55 -04:00
Matt Portune
3b5cdfe03c bump to 2.9.2 (#1321) 2021-03-16 15:56:28 -04:00
Contribucious
63449a3832 [KnownUsernameField] Entries update (Amazon) (#1320) 2021-03-16 14:57:45 -04:00
Matt Portune
23011aa8ae version bump to 2.9.1 (#1316) 2021-03-13 13:02:56 -05:00
Matt Portune
654d71cbbc use hardcoded kdfiterations for send passwords (#1315) 2021-03-13 12:40:41 -05:00
480 changed files with 19926 additions and 4466 deletions

View File

@@ -30,16 +30,6 @@ $xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$firebaseReceiver1=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdInternalReceiver']", `
$nsAndroid);
$firebaseReceiver1.ParentNode.RemoveChild($firebaseReceiver1);
$firebaseReceiver2=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdReceiver']", `
$nsAndroid);
$firebaseReceiver2.ParentNode.RemoveChild($firebaseReceiver2);
$xml.Save($androidManifest);
Write-Output "########################################"
@@ -56,6 +46,10 @@ $firebaseNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
$daggerNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
$daggerNode.ParentNode.RemoveChild($daggerNode);
$safetyNetNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout repo
uses: actions/checkout@v2
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up cloc
run: |
@@ -31,22 +31,21 @@ jobs:
steps:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@v1
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
- name: Print environment
run: |
nuget help
nuget help | grep Version
msbuild -version
dotnet --info
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@v2
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Decrypt secrets
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
@@ -83,14 +82,14 @@ jobs:
- name: Upload Play Store .aab artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
- name: Upload Play Store .apk artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
@@ -115,7 +114,7 @@ jobs:
- name: Upload F-Droid .apk artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk
@@ -146,7 +145,7 @@ jobs:
steps:
- name: Set up Node
if: github.event_name == 'release'
uses: actions/setup-node@v1
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '10.x'
@@ -181,7 +180,7 @@ jobs:
- name: Checkout repo
if: github.event_name == 'release'
uses: actions/checkout@v2
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install Node dependencies
if: github.event_name == 'release'
@@ -215,18 +214,17 @@ jobs:
steps:
- name: Print environment
run: |
nuget help
nuget help | grep Version
msbuild -version
dotnet --info
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@v2
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Decrypt secrets
run: ./.github/scripts/ios/decrypt-secrets.ps1
@@ -271,7 +269,7 @@ jobs:
- name: Upload App Store .ipa artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: Bitwarden.ipa
path: ./bitwarden-export/Bitwarden.ipa

48
.github/workflows/crowdin-sync.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Crowdin Sync
on:
workflow_dispatch:
inputs: {}
#schedule:
# - cron: '0 0 * * *'
jobs:
crowdin-sync:
name: Autosync
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: Download translations
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea
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: false
upload_translations: false
download_translations: true
github_user_name: "github-actions"
github_user_email: "<>"
commit_message: "Autosync the updated translations"
localization_branch_name: crowdin-auto-sync
create_pull_request: true
pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated translations"

21
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Release
on:
workflow_dispatch:
jobs:
cloc:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up cloc
run: |
sudo apt-get update
sudo apt-get -y install cloc
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML

View File

@@ -1,4 +1,4 @@
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/mobile?branch=master&svg=true)](https://ci.appveyor.com/project/bitwarden/mobile)
[![Github Workflow build on master](https://github.com/bitwarden/mobile/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:master)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-mobile/localized.svg)](https://crowdin.com/project/bitwarden-mobile)
[![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)

View File

@@ -1,146 +0,0 @@
image:
- Visual Studio 2019
- Ubuntu1804
branches:
except:
- l10n_master
- gh-pages
configuration: Release
stack: node 10
init:
- sh: |
if [ "${DEBUG_SSH}" == "true" ]
then
curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
fi
- ps: |
if($isWindows -and $env:DEBUG_RDP -eq "true") {
iex ((new-object net.webclient).DownloadString(`
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
}
- ps: |
if($env:APPVEYOR_REPO_TAG -eq "true") {
$tagName = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
$env:RELEASE_NAME = "Version ${tagName}"
}
install:
- sh: |
curl -sflL 'https://raw.githubusercontent.com/appveyor/secure-file/master/install.sh' | bash -e -
./appveyor-tools/secure-file -decrypt ./store/fdroid/keystore.jks.enc -secret $FDROID_KEYSTORE_ENC_PASSWORD
- sh: npm install
- sh: |
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
- sh: |
if [ "${APPVEYOR_REPO_TAG}" == "true" -a "${GH_TOKEN}" != "" ]
then
git config --global credential.helper store
echo "https://${GH_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
git config --global user.email "ci@bitwarden.com"
git config --global user.name "Bitwarden CI"
fi
- cmd: choco install cloc --no-progress
- cmd: "cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML"
#- cmd: appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
#- cmd: appveyor DownloadFile https://aka.ms/vs/15/release/vs_community.exe
#- cmd: vs_community.exe update --wait --quiet --norestart --installPath "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community"
#- cmd: ps: .\src\Android\update-android.ps1
before_build:
- ps: |
if($isWindows) {
nuget restore
if($env:UPLOAD_KEYSTORE_DEC_SECRET -or$env:KEYSTORE_DEC_SECRET -or $env:GOOGLE_SERVICES_DEC_SECRET -or $env:PLAY_DEC_SECRET) {
nuget install secure-file -ExcludeVersion
}
if($env:GOOGLE_SERVICES_DEC_SECRET) {
secure-file\tools\secure-file -decrypt src\Android\google-services.json.enc `
-secret $env:GOOGLE_SERVICES_DEC_SECRET
}
}
build_script:
- sh: |
if [ "${APPVEYOR_REPO_TAG}" == "true" ]
then
mkdir dist
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
TEMP_DIR="$APPVEYOR_BUILD_FOLDER/store/temp/fdroid"
cd fdroid
echo "keypass=\"$FDROID_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
curl -Lo repo/com.x8bit.bitwarden-fdroid.apk \
https://github.com/bitwarden/mobile/releases/download/$APPVEYOR_REPO_TAG_NAME/com.x8bit.bitwarden-fdroid.apk
fdroid update
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $APPVEYOR_BUILD_FOLDER
fi
- ps: |
if($isWindows -and $env:KEYSTORE_DEC_SECRET) {
msbuild bitwarden-mobile.sln `
"/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" `
"/p:Configuration=Release"
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
.\src\Android\ci-build-apks.ps1
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
Push-AppveyorArtifact .\com.x8bit.bitwarden.aab
Push-AppveyorArtifact .\com.x8bit.bitwarden.apk
Push-AppveyorArtifact .\com.x8bit.bitwarden-fdroid.apk
}
on_success:
- sh: |
if [ "${APPVEYOR_REPO_TAG}" == "true" -a "${GH_TOKEN}" != "" ]
then
npm run deploy
fi
- ps: |
if($isWindows -and $env:PLAY_DEC_SECRET -and $env:APPVEYOR_REPO_BRANCH -eq 'master') {
secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret $env:PLAY_DEC_SECRET
cd store\google\Publisher\bin\Release\netcoreapp2.0
dotnet Publisher.dll `
$env:APPVEYOR_BUILD_FOLDER\store\google\Publisher\play_creds.json `
$env:APPVEYOR_BUILD_FOLDER\com.x8bit.bitwarden.aab `
alpha
cd $env:APPVEYOR_BUILD_FOLDER
}
on_finish:
- sh: |
if [ "${DEBUG_SSH}" == "true" ]
then
export APPVEYOR_SSH_BLOCK=true
curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
fi
- ps: |
if($isWindows -and $env:DEBUG_RDP -eq "true") {
$blockRdp = $true
iex ((new-object net.webclient).DownloadString(`
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
}
deploy:
tag: $(APPVEYOR_REPO_TAG_NAME)
release: $(RELEASE_NAME)
provider: GitHub
auth_token: $(GH_TOKEN)
artifact: /.*/
force_update: true
on:
branch: master
APPVEYOR_REPO_TAG: true

View File

@@ -23,7 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
appveyor.yml = appveyor.yml
.github\workflows\build.yml = .github\workflows\build.yml
CONTRIBUTING.md = CONTRIBUTING.md
crowdin.yml = crowdin.yml

View File

@@ -1,3 +1,5 @@
project_id_env: _CROWDIN_PROJECT_ID
api_token_env: CROWDIN_API_TOKEN
files:
- source: /src/App/Resources/AppResources.resx
translation: /src/App/Resources/AppResources.%two_letters_code%.resx

View File

@@ -46,14 +46,21 @@ namespace Bit.Droid.Accessibility
new Browser("com.chrome.beta", "url_bar"),
new Browser("com.chrome.canary", "url_bar"),
new Browser("com.chrome.dev", "url_bar"),
new Browser("com.cookiegames.smartcookie", "search"),
new Browser("com.cookiejarapps.android.smartcookieweb", "mozac_browser_toolbar_url_view"),
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
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"),
new Browser("com.jamal2367.styx", "search"),
new Browser("com.kiwibrowser.browser", "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"),
new Browser("com.microsoft.emmx.dev", "url_bar"),
new Browser("com.mmbox.browser", "search_box"),
new Browser("com.mmbox.xbrowser", "search_box"),
new Browser("com.mycompany.app.soulbrowser", "edit_text"),
new Browser("com.naver.whale", "url_bar"),
new Browser("com.opera.browser", "url_field"),
new Browser("com.opera.browser.beta", "url_field"),
@@ -77,6 +84,10 @@ namespace Bit.Droid.Accessibility
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
new Browser("mark.via", "am,an"),
new Browser("mark.via.gp", "as"),
new Browser("net.slions.fulguris.full.download", "search"),
new Browser("net.slions.fulguris.full.download.debug", "search"),
new Browser("net.slions.fulguris.full.playstore", "search"),
new Browser("net.slions.fulguris.full.playstore.debug", "search"),
new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
new Browser("org.bromite.bromite", "url_bar"),
@@ -98,6 +109,7 @@ namespace Bit.Droid.Accessibility
new Browser("org.torproject.torbrowser_alpha", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0a8)
new Browser("org.ungoogled.chromium.extensions.stable", "url_bar"),
new Browser("org.ungoogled.chromium.stable", "url_bar"),
new Browser("us.spotco.fennec_dos", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
// [Section B] Entries only present here
//
@@ -182,7 +194,9 @@ namespace Bit.Droid.Accessibility
new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.pl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.se", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
// Amazon Web Services

View File

@@ -28,7 +28,6 @@
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>3</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
<AndroidSupportedAbis />
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
</PropertyGroup>
@@ -75,23 +74,27 @@
<Version>2.1.0.4</Version>
</PackageReference>
<PackageReference Include="Portable.BouncyCastle">
<Version>1.8.6.7</Version>
<Version>1.8.10</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.3-beta01" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.5.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat.AppCompatResources" Version="1.3.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.0" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.6" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.8" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.7" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.4" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.5.3.2</Version>
<Version>1.7.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>71.1740.0</Version>
<Version>122.0.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.1.0" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.1.0" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.6" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.3.0.1" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.37.0" />
<PackageReference Include="Xamarin.GooglePlayServices.Fido" Version="118.1.0" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>71.1600.0</Version>
<Version>117.0.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -113,25 +116,24 @@
<Compile Include="Effects\FixedSizeEffect.cs" />
<Compile Include="Effects\SelectableLabelEffect.cs" />
<Compile Include="Effects\TabBarEffect.cs" />
<Compile Include="Push\FirebaseInstanceIdService.cs" />
<Compile Include="Fido2System\Fido2BuilderObject.cs" />
<Compile Include="Fido2System\Fido2Service.cs" />
<Compile Include="Push\FirebaseMessagingService.cs" />
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
<Compile Include="Receivers\RestrictionsChangedReceiver.cs" />
<Compile Include="Receivers\EventUploadReceiver.cs" />
<Compile Include="Receivers\LockAlarmReceiver.cs" />
<Compile Include="Receivers\PackageReplacedReceiver.cs" />
<Compile Include="Renderers\CipherViewCellRenderer.cs" />
<Compile Include="Renderers\ExtendedGridRenderer.cs" />
<Compile Include="Renderers\ExtendedDatePickerRenderer.cs" />
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
<Compile Include="Renderers\ExtendedStackLayoutRenderer.cs" />
<Compile Include="Renderers\ExtendedTimePickerRenderer.cs" />
<Compile Include="Renderers\ExtendedSliderRenderer.cs" />
<Compile Include="Renderers\CustomEditorRenderer.cs" />
<Compile Include="Renderers\CustomPickerRenderer.cs" />
<Compile Include="Renderers\CustomEntryRenderer.cs" />
<Compile Include="Renderers\CustomSearchBarRenderer.cs" />
<Compile Include="Renderers\ExtendedListViewRenderer.cs" />
<Compile Include="Renderers\HybridWebViewRenderer.cs" />
<Compile Include="Renderers\SendViewCellRenderer.cs" />
<Compile Include="Services\AndroidPushNotificationService.cs" />
<Compile Include="Services\AndroidLogService.cs" />
<Compile Include="MainApplication.cs" />
@@ -154,7 +156,6 @@
<AndroidAsset Include="Assets\RobotoMono_Regular.ttf" />
<AndroidAsset Include="Assets\MaterialIcons_Regular.ttf" />
<None Include="8bit.keystore.enc" />
<None Include="ci-build-apks.ps1" />
<GoogleServicesJson Include="google-services.json" />
<GoogleServicesJson Include="google-services.json.enc" />
<None Include="fdroid-keystore.jks.enc" />
@@ -174,6 +175,9 @@
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
<AndroidResource Include="Resources\drawable\id.xml" />
<AndroidResource Include="Resources\drawable\info.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg_dark.xml" />
<AndroidResource Include="Resources\drawable\list_item_bg_nord.xml" />
<AndroidResource Include="Resources\drawable\lock.xml" />
<AndroidResource Include="Resources\drawable\login.xml" />
<AndroidResource Include="Resources\drawable\logo.xml" />
@@ -186,7 +190,6 @@
<AndroidResource Include="Resources\drawable\shield.xml" />
<AndroidResource Include="Resources\drawable-v23\splash_screen.xml" />
<AndroidResource Include="Resources\drawable-v23\splash_screen_dark.xml" />
<AndroidResource Include="Resources\layout\SendViewCell.axml" />
<AndroidResource Include="Resources\layout\Tabbar.axml" />
<AndroidResource Include="Resources\layout\Toolbar.axml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher.xml" />
@@ -263,12 +266,6 @@
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\CipherViewCell.axml">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\xml\app_restrictions.xml">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>

View File

@@ -63,13 +63,20 @@ namespace Bit.Droid.Autofill
"com.chrome.beta",
"com.chrome.canary",
"com.chrome.dev",
"com.cookiegames.smartcookie",
"com.cookiejarapps.android.smartcookieweb",
"com.ecosia.android",
"com.google.android.apps.chrome",
"com.google.android.apps.chrome_dev",
"com.jamal2367.styx",
"com.kiwibrowser.browser",
"com.microsoft.emmx",
"com.microsoft.emmx.beta",
"com.microsoft.emmx.canary",
"com.microsoft.emmx.dev",
"com.mmbox.browser",
"com.mmbox.xbrowser",
"com.mycompany.app.soulbrowser",
"com.naver.whale",
"com.opera.browser",
"com.opera.browser.beta",
@@ -92,6 +99,10 @@ namespace Bit.Droid.Autofill
"io.github.forkmaintainers.iceraven",
"mark.via",
"mark.via.gp",
"net.slions.fulguris.full.download",
"net.slions.fulguris.full.download.debug",
"net.slions.fulguris.full.playstore",
"net.slions.fulguris.full.playstore.debug",
"org.adblockplus.browser",
"org.adblockplus.browser.beta",
"org.bromite.bromite",
@@ -111,6 +122,7 @@ namespace Bit.Droid.Autofill
"org.torproject.torbrowser_alpha",
"org.ungoogled.chromium.extensions.stable",
"org.ungoogled.chromium.stable",
"us.spotco.fennec_dos",
};
// The URLs are blacklisted from autofilling
@@ -131,13 +143,14 @@ namespace Bit.Droid.Autofill
{
var allCiphers = ciphers.Item1.ToList();
allCiphers.AddRange(ciphers.Item2.ToList());
return allCiphers.Select(c => new FilledItem(c)).ToList();
var nonPromptCiphers = allCiphers.Where(cipher => cipher.Reprompt == CipherRepromptType.None);
return nonPromptCiphers.Select(c => new FilledItem(c)).ToList();
}
}
else if (parser.FieldCollection.FillableForCard)
{
var ciphers = await cipherService.GetAllDecryptedAsync();
return ciphers.Where(c => c.Type == CipherType.Card).Select(c => new FilledItem(c)).ToList();
return ciphers.Where(c => c.Type == CipherType.Card && c.Reprompt == CipherRepromptType.None).Select(c => new FilledItem(c)).ToList();
}
return new List<FilledItem>();
}

View File

@@ -57,6 +57,7 @@ namespace Bit.Droid.Autofill
}
List<FilledItem> items = null;
await _vaultTimeoutService.CheckVaultTimeoutAsync();
var locked = await _vaultTimeoutService.IsLockedAsync();
if (!locked)
{

View File

@@ -0,0 +1,106 @@
#if !FDROID
using System.Collections.Generic;
using Android.Gms.Fido.Common;
using Android.Gms.Fido.Fido2.Api.Common;
using Bit.Core.Models.Data;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
using Java.Lang;
using Newtonsoft.Json.Linq;
namespace Bit.Droid.Fido2System
{
class Fido2BuilderObject
{
public static PublicKeyCredentialRequestOptions ParsePublicKeyCredentialRequestOptions(
Fido2AuthenticationChallengeResponse data)
{
if (data == null)
{
return null;
}
var builder = new PublicKeyCredentialRequestOptions.Builder();
if (!string.IsNullOrEmpty(data.Challenge))
{
builder.SetChallenge(CoreHelpers.Base64UrlDecode(data.Challenge));
}
if (data.AllowCredentials != null && data.AllowCredentials.Count > 0)
{
builder.SetAllowList(ParseCredentialDescriptors(data.AllowCredentials));
}
if (!string.IsNullOrEmpty(data.RpId))
{
builder.SetRpId(data.RpId);
}
if (data.Timeout > 0)
{
builder.SetTimeoutSeconds((Double)(data.Timeout / 1000));
}
if (data.Extensions != null)
{
builder.SetAuthenticationExtensions(ParseExtensions((JObject)data.Extensions));
}
return builder.Build();
}
private static List<PublicKeyCredentialDescriptor> ParseCredentialDescriptors(
List<Fido2CredentialDescriptor> listData)
{
if (listData == null || listData.Count == 0)
{
return new List<PublicKeyCredentialDescriptor>();
}
var credentials = new List<PublicKeyCredentialDescriptor>();
foreach (var data in listData)
{
string id = null;
string type = null;
var transports = new List<Transport>();
if (!string.IsNullOrEmpty(data.Id))
{
id = data.Id;
}
if (!string.IsNullOrEmpty(data.Type))
{
type = data.Type;
}
if (data.Transports != null && data.Transports.Count > 0)
{
foreach (var transport in data.Transports)
{
transports.Add(Transport.FromString(transport));
}
}
credentials.Add(new PublicKeyCredentialDescriptor(type, CoreHelpers.Base64UrlDecode(id), transports));
}
return credentials;
}
private static AuthenticationExtensions ParseExtensions(JObject extensions)
{
var builder = new AuthenticationExtensions.Builder();
if (extensions.ContainsKey("appid"))
{
var appId = new FidoAppIdExtension((string)extensions.GetValue("appid"));
builder.SetFido2Extension(appId);
}
if (extensions.ContainsKey("uvm"))
{
var uvm = new UserVerificationMethodExtension((bool)extensions.GetValue("uvm"));
builder.SetUserVerificationMethodExtension(uvm);
}
return builder.Build();
}
}
}
#endif

View File

@@ -0,0 +1,249 @@
#if !FDROID
using Android.App;
using Android.Content;
using Android.Gms.Fido;
using Android.Gms.Fido.Fido2;
using Android.Gms.Fido.Fido2.Api.Common;
using Android.Gms.Tasks;
using Android.Util;
using AndroidX.AppCompat.App;
using Bit.App.Services;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
using Java.Lang;
using Newtonsoft.Json;
using Xamarin.Forms;
using Enum = System.Enum;
namespace Bit.Droid.Fido2System
{
public class Fido2Service
{
public static readonly string _tag_log = "Fido2Service";
public static Fido2Service INSTANCE = new Fido2Service();
private readonly MobileI18nService _i18nService;
private readonly IPlatformUtilsService _platformUtilsService;
private AppCompatActivity _activity;
private Fido2ApiClient _fido2ApiClient;
private Fido2CodesTypes _fido2CodesType;
public Fido2Service()
{
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
}
public void Start(AppCompatActivity activity)
{
_activity = activity;
_fido2ApiClient = Fido.GetFido2ApiClient(_activity);
}
public void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (resultCode == Result.Ok && Enum.IsDefined(typeof(Fido2CodesTypes), requestCode))
{
switch ((Fido2CodesTypes)requestCode)
{
case Fido2CodesTypes.RequestSignInUser:
var errorExtra = data?.GetByteArrayExtra(Fido.Fido2KeyErrorExtra);
if (errorExtra != null)
{
HandleErrorCode(errorExtra);
}
else
{
if (data != null)
{
SignInUserResponse(data);
}
}
break;
// TODO: Key registration, should we ever choose to implement client-side
/*case Fido2CodesTypes.RequestRegisterNewKey:
errorExtra = data?.GetByteArrayExtra(Fido.Fido2KeyErrorExtra);
if (errorExtra != null)
{
HandleErrorCode(errorExtra);
}
else
{
if (data != null)
{
// begin registration flow
}
}
break;*/
}
}
else if (resultCode == Result.Canceled && Enum.IsDefined(typeof(Fido2CodesTypes), requestCode))
{
Log.Info(_tag_log, "cancelled");
_platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2AbortError"),
_i18nService.T("Fido2Title"));
}
}
public void OnSuccess(Object result)
{
if (result != null && Enum.IsDefined(typeof(Fido2CodesTypes), _fido2CodesType))
{
try
{
_activity.StartIntentSenderForResult(((PendingIntent)result).IntentSender, (int)_fido2CodesType,
null, 0, 0, 0);
}
catch (System.Exception e)
{
Log.Error(_tag_log, e.Message);
_platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"),
_i18nService.T("Fido2Title"));
}
}
}
public void OnFailure(Exception e)
{
Log.Error(_tag_log, e.Message ?? "OnFailure: No error message returned");
_platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"),
_i18nService.T("Fido2Title"));
}
public void OnComplete(Task task)
{
Log.Debug(_tag_log, "OnComplete");
}
public async System.Threading.Tasks.Task SignInUserRequestAsync(string dataJson)
{
try
{
var dataObject = JsonConvert.DeserializeObject<Fido2AuthenticationChallengeResponse>(dataJson);
_fido2CodesType = Fido2CodesTypes.RequestSignInUser;
var options = Fido2BuilderObject.ParsePublicKeyCredentialRequestOptions(dataObject);
var task = _fido2ApiClient.GetSignPendingIntent(options);
task.AddOnSuccessListener((IOnSuccessListener)_activity)
.AddOnFailureListener((IOnFailureListener)_activity)
.AddOnCompleteListener((IOnCompleteListener)_activity);
}
catch (System.Exception e)
{
Log.Error(_tag_log, e.StackTrace);
await _platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"),
_i18nService.T("Fido2Title"));
}
finally
{
Log.Info(_tag_log, "SignInUserRequest() -> finally()");
}
}
private void SignInUserResponse(Intent data)
{
try
{
var response =
AuthenticatorAssertionResponse.DeserializeFromBytes(
data.GetByteArrayExtra(Fido.Fido2KeyResponseExtra));
var responseJson = JsonConvert.SerializeObject(new Fido2AuthenticationChallengeRequest
{
Id = CoreHelpers.Base64UrlEncode(response.GetKeyHandle()),
RawId = CoreHelpers.Base64UrlEncode(response.GetKeyHandle()),
Type = "public-key",
Response = new Fido2AssertionResponse
{
AuthenticatorData = CoreHelpers.Base64UrlEncode(response.GetAuthenticatorData()),
ClientDataJson = CoreHelpers.Base64UrlEncode(response.GetClientDataJSON()),
Signature = CoreHelpers.Base64UrlEncode(response.GetSignature()),
UserHandle = (response.GetUserHandle() != null
? CoreHelpers.Base64UrlEncode(response.GetUserHandle()) : null),
},
Extensions = null
}
);
Device.BeginInvokeOnMainThread(() => ((MainActivity)_activity).Fido2Submission(responseJson));
}
catch (System.Exception e)
{
Log.Error(_tag_log, e.Message);
_platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"),
_i18nService.T("Fido2Title"));
}
finally
{
Log.Info(_tag_log, "SignInUserResponse() -> finally()");
}
}
public void HandleErrorCode(byte[] errorExtra)
{
var error = AuthenticatorErrorResponse.DeserializeFromBytes(errorExtra);
if (error.ErrorMessage.Length > 0)
{
Log.Info(_tag_log, error.ErrorMessage);
}
string message = "";
if (error.ErrorCode == ErrorCode.AbortErr)
{
message = "Fido2AbortError";
}
else if (error.ErrorCode == ErrorCode.TimeoutErr)
{
message = "Fido2TimeoutError";
}
else if (error.ErrorCode == ErrorCode.AttestationNotPrivateErr)
{
message = "Fido2PrivacyError";
}
else if (error.ErrorCode == ErrorCode.ConstraintErr)
{
message = "Fido2SomethingWentWrong";
}
else if (error.ErrorCode == ErrorCode.DataErr)
{
message = "Fido2ServerDataFail";
}
else if (error.ErrorCode == ErrorCode.EncodingErr)
{
message = "Fido2SomethingWentWrong";
}
else if (error.ErrorCode == ErrorCode.InvalidStateErr)
{
message = "Fido2SomethingWentWrong";
}
else if (error.ErrorCode == ErrorCode.NetworkErr)
{
message = "Fido2NetworkFail";
}
else if (error.ErrorCode == ErrorCode.NotAllowedErr)
{
message = "Fido2NoPermission";
}
else if (error.ErrorCode == ErrorCode.NotSupportedErr)
{
message = "Fido2NotSupportedError";
}
else if (error.ErrorCode == ErrorCode.SecurityErr)
{
message = "Fido2SecurityError";
}
else if (error.ErrorCode == ErrorCode.UnknownErr)
{
message = "Fido2SomethingWentWrong";
}
else
{
message = "Fido2SomethingWentWrong";
}
_platformUtilsService.ShowDialogAsync(_i18nService.T(message), _i18nService.T("Fido2Title"));
}
}
}
#endif

View File

@@ -9,6 +9,7 @@ using Bit.Core.Utilities;
using Bit.Core.Abstractions;
using System.IO;
using System;
using System.Collections.Generic;
using Android.Content;
using Bit.Droid.Utilities;
using Bit.Droid.Receivers;
@@ -17,7 +18,11 @@ using Bit.Core.Enums;
using Android.Nfc;
using Bit.App.Utilities;
using System.Threading.Tasks;
using Android.Util;
using AndroidX.Core.Content;
#if !FDROID
using Bit.Droid.Fido2System;
#endif
using ZXing.Net.Mobile.Android;
namespace Bit.Droid
@@ -27,11 +32,25 @@ namespace Bit.Droid
Icon = "@mipmap/ic_launcher",
Theme = "@style/LaunchTheme",
MainLauncher = true,
LaunchMode = LaunchMode.SingleTask,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden |
ConfigChanges.Navigation)]
[IntentFilter(
new[] { Intent.ActionSend },
Categories = new[] { Intent.CategoryDefault },
DataMimeTypes = new[]
{
@"application/*",
@"image/*",
@"video/*",
@"text/*"
})]
[Register("com.x8bit.bitwarden.MainActivity")]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity,
Android.Gms.Tasks.IOnSuccessListener,
Android.Gms.Tasks.IOnCompleteListener,
Android.Gms.Tasks.IOnFailureListener
{
private IDeviceActionService _deviceActionService;
private IMessagingService _messagingService;
@@ -40,22 +59,19 @@ namespace Bit.Droid
private IAppIdService _appIdService;
private IStorageService _storageService;
private IEventService _eventService;
private PendingIntent _vaultTimeoutAlarmPendingIntent;
private PendingIntent _clearClipboardPendingIntent;
private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
private Java.Util.Regex.Pattern _otpPattern =
Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
private string _fidoDataJson;
protected override void OnCreate(Bundle savedInstanceState)
{
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
PendingIntentFlags.UpdateCurrent);
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
_vaultTimeoutAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
PendingIntentFlags.UpdateCurrent);
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
PendingIntentFlags.UpdateCurrent);
@@ -84,6 +100,7 @@ namespace Bit.Droid
#if !FDROID
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
var appCenterTask = appCenterHelper.InitAsync();
Fido2Service.INSTANCE.Start(this);
#endif
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
@@ -93,20 +110,7 @@ namespace Bit.Droid
_broadcasterService.Subscribe(_activityKey, (message) =>
{
if (message.Command == "scheduleVaultTimeoutTimer")
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
var vaultTimeoutMinutes = (int)message.Data;
var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + vaultTimeoutMs + 10;
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _vaultTimeoutAlarmPendingIntent);
}
else if (message.Command == "cancelVaultTimeoutTimer")
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Cancel(_vaultTimeoutAlarmPendingIntent);
}
else if (message.Command == "startEventTimer")
if (message.Command == "startEventTimer")
{
StartEventAlarm();
}
@@ -118,6 +122,14 @@ namespace Bit.Droid
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish());
}
else if (message.Command == "listenFido2")
{
ListenFido2((Dictionary<string, object>)message.Data);
}
else if (message.Command == "listenFido2TryAgain")
{
ListenFido2();
}
else if (message.Command == "listenYubiKeyOTP")
{
ListenYubiKey((bool)message.Data);
@@ -155,31 +167,48 @@ namespace Bit.Droid
}
catch { }
}
var setRestrictions = AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this);
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
.GetAwaiter()
.GetResult();
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
if (intent.GetBooleanExtra("generatorTile", false))
try
{
_messagingService.Send("popAllAndGoToTabGenerator");
if (_appOptions != null)
if (intent.GetBooleanExtra("generatorTile", false))
{
_appOptions.GeneratorTile = true;
_messagingService.Send("popAllAndGoToTabGenerator");
if (_appOptions != null)
{
_appOptions.GeneratorTile = true;
}
}
else if (intent.GetBooleanExtra("myVaultTile", false))
{
_messagingService.Send("popAllAndGoToTabMyVault");
if (_appOptions != null)
{
_appOptions.MyVaultTile = true;
}
}
else if (intent.Action == Intent.ActionSend && intent.Type != null)
{
if (_appOptions != null)
{
_appOptions.CreateSend = GetCreateSendRequest(intent);
}
_messagingService.Send("popAllAndGoToTabSend");
}
else
{
ParseYubiKey(intent.DataString);
}
}
if (intent.GetBooleanExtra("myVaultTile", false))
catch (Exception e)
{
_messagingService.Send("popAllAndGoToTabMyVault");
if (_appOptions != null)
{
_appOptions.MyVaultTile = true;
}
}
else
{
ParseYubiKey(intent.DataString);
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
}
}
@@ -249,6 +278,34 @@ namespace Bit.Droid
return;
}
}
else if (resultCode == Result.Ok &&
Enum.IsDefined(typeof(Fido2CodesTypes), requestCode))
{
#if !FDROID
Fido2Service.INSTANCE.OnActivityResult(requestCode, resultCode, data);
#endif
}
}
public void OnSuccess(Java.Lang.Object result)
{
#if !FDROID
Fido2Service.INSTANCE.OnSuccess(result);
#endif
}
public void OnComplete(Android.Gms.Tasks.Task task)
{
#if !FDROID
Fido2Service.INSTANCE.OnComplete(task);
#endif
}
public void OnFailure(Java.Lang.Exception e)
{
#if !FDROID
Fido2Service.INSTANCE.OnFailure(e);
#endif
}
protected override void OnDestroy()
@@ -257,6 +314,41 @@ namespace Bit.Droid
_broadcasterService.Unsubscribe(_activityKey);
}
private void ListenFido2(Dictionary<string, object> data = null)
{
if (!_deviceActionService.SupportsFido2())
{
return;
}
#if !FDROID
RunOnUiThread(async () =>
{
try
{
if (data != null)
{
_fidoDataJson = Newtonsoft.Json.JsonConvert.SerializeObject(data);
await Fido2Service.INSTANCE.SignInUserRequestAsync(_fidoDataJson);
}
else
{
await Fido2Service.INSTANCE.SignInUserRequestAsync(_fidoDataJson);
}
}
catch (Exception e)
{
Log.Error(Fido2Service._tag_log, e.Message);
}
});
#endif
}
public void Fido2Submission(string token)
{
_messagingService.Send("gotFido2Token", token);
}
private void ListenYubiKey(bool listen)
{
if (!_deviceActionService.SupportsNfc())
@@ -298,7 +390,8 @@ namespace Bit.Droid
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false)
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
CreateSend = GetCreateSendRequest(Intent)
};
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
if (fillType > 0)
@@ -320,6 +413,42 @@ namespace Bit.Droid
return options;
}
private Tuple<SendType, string, byte[], string> GetCreateSendRequest(Intent intent)
{
if (intent.Action == Intent.ActionSend && intent.Type != null)
{
if ((intent.Flags & ActivityFlags.LaunchedFromHistory) == ActivityFlags.LaunchedFromHistory)
{
// don't re-deliver intent if resuming from app switcher
return null;
}
var type = intent.Type;
if (type.Contains("text/"))
{
var subject = intent.GetStringExtra(Intent.ExtraSubject);
var text = intent.GetStringExtra(Intent.ExtraText);
return new Tuple<SendType, string, byte[], string>(SendType.Text, subject, null, text);
}
else
{
var data = intent.ClipData?.GetItemAt(0);
var uri = data?.Uri;
var filename = AndroidHelpers.GetFileName(ApplicationContext, uri);
try
{
using (var stream = ContentResolver.OpenInputStream(uri))
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return new Tuple<SendType, string, byte[], string>(SendType.File, filename, memoryStream.ToArray(), null);
}
}
catch (Java.IO.FileNotFoundException) { }
}
}
return null;
}
private void ParseYubiKey(string data)
{
if (data == null)

View File

@@ -99,6 +99,9 @@ namespace Bit.Droid
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
@@ -110,6 +113,9 @@ namespace Bit.Droid
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
// Push
#if FDROID

View File

@@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="2.9.0"
android:versionName="2.12.0"
android:installLocation="internalOnly"
package="com.x8bit.bitwarden">
@@ -40,20 +40,6 @@
android:resource="@xml/filepaths" />
</provider>
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
android:exported="false" />
<receiver
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.x8bit.bitwarden" />
</intent-filter>
</receiver>
<meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
@@ -68,12 +54,7 @@
<!-- Package visibility (for Android 11+) -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https"/>
<action android:name="*"/>
</intent>
</queries>

View File

@@ -1,25 +0,0 @@
#if !FDROID
using Android.App;
using Android.Content;
using Bit.App.Abstractions;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Firebase.Iid;
namespace Bit.Droid.Push
{
[Service]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class FirebaseInstanceIdService : Firebase.Iid.FirebaseInstanceIdService
{
public async override void OnTokenRefresh()
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await storageService.SaveAsync(Constants.PushRegisteredTokenKey, FirebaseInstanceId.Instance.Token);
await pushNotificationService.RegisterAsync();
}
}
}
#endif

View File

@@ -1,7 +1,7 @@
#if !FDROID
using Android.App;
using Android.Content;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Firebase.Messaging;
using Newtonsoft.Json;
@@ -10,10 +10,19 @@ using Xamarin.Forms;
namespace Bit.Droid.Push
{
[Service]
[Service(Exported=false)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService
{
public async override void OnNewToken(string token)
{
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token);
await pushNotificationService.RegisterAsync();
}
public async override void OnMessageReceived(RemoteMessage message)
{
if (message?.Data == null)

View File

@@ -1,16 +0,0 @@
using Android.Content;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
namespace Bit.Droid.Receivers
{
[BroadcastReceiver(Name = "com.x8bit.bitwarden.LockAlarmReceiver", Exported = false)]
public class LockAlarmReceiver : BroadcastReceiver
{
public async override void OnReceive(Context context, Intent intent)
{
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
await vaultTimeoutService.CheckVaultTimeoutAsync();
}
}
}

View File

@@ -1,242 +0,0 @@
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using FFImageLoading;
using FFImageLoading.Views;
using FFImageLoading.Work;
using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CipherViewCell), typeof(CipherViewCellRenderer))]
namespace Bit.Droid.Renderers
{
public class CipherViewCellRenderer : ViewCellRenderer
{
private static Typeface _faTypeface;
private static Typeface _miTypeface;
private static Android.Graphics.Color _textColor;
private static Android.Graphics.Color _mutedColor;
private static Android.Graphics.Color _disabledIconColor;
private static bool _usingLightTheme;
private AndroidCipherCell _cell;
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView,
ViewGroup parent, Context context)
{
// TODO expand beyond light/dark detection once we support custom theme switching without app restart
var themeChanged = _usingLightTheme != ThemeManager.UsingLightTheme;
if (_faTypeface == null)
{
_faTypeface = Typeface.CreateFromAsset(context.Assets, "FontAwesome.ttf");
}
if (_miTypeface == null)
{
_miTypeface = Typeface.CreateFromAsset(context.Assets, "MaterialIcons_Regular.ttf");
}
if (_textColor == default(Android.Graphics.Color) || themeChanged)
{
_textColor = ThemeManager.GetResourceColor("TextColor").ToAndroid();
}
if (_mutedColor == default(Android.Graphics.Color) || themeChanged)
{
_mutedColor = ThemeManager.GetResourceColor("MutedColor").ToAndroid();
}
if (_disabledIconColor == default(Android.Graphics.Color) || themeChanged)
{
_disabledIconColor = ThemeManager.GetResourceColor("DisabledIconColor").ToAndroid();
}
_usingLightTheme = ThemeManager.UsingLightTheme;
var cipherCell = item as CipherViewCell;
_cell = convertView as AndroidCipherCell;
if (_cell == null)
{
_cell = new AndroidCipherCell(context, cipherCell, _faTypeface, _miTypeface);
}
else
{
_cell.CipherViewCell.PropertyChanged -= CellPropertyChanged;
}
cipherCell.PropertyChanged += CellPropertyChanged;
_cell.UpdateCell(cipherCell);
_cell.UpdateColors(_textColor, _mutedColor, _disabledIconColor);
return _cell;
}
public void CellPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var cipherCell = sender as CipherViewCell;
_cell.CipherViewCell = cipherCell;
if (e.PropertyName == CipherViewCell.CipherProperty.PropertyName)
{
_cell.UpdateCell(cipherCell);
}
else if (e.PropertyName == CipherViewCell.WebsiteIconsEnabledProperty.PropertyName)
{
_cell.UpdateIconImage(cipherCell);
}
}
}
public class AndroidCipherCell : LinearLayout, INativeElementView
{
private readonly Typeface _faTypeface;
private readonly Typeface _miTypeface;
private IScheduledWork _currentTask;
public AndroidCipherCell(Context context, CipherViewCell cipherView, Typeface faTypeface, Typeface miTypeface)
: base(context)
{
CipherViewCell = cipherView;
_faTypeface = faTypeface;
_miTypeface = miTypeface;
var view = (context as Activity).LayoutInflater.Inflate(Resource.Layout.CipherViewCell, null);
IconImage = view.FindViewById<IconImageView>(Resource.Id.CipherCellIconImage);
Icon = view.FindViewById<TextView>(Resource.Id.CipherCellIcon);
Name = view.FindViewById<TextView>(Resource.Id.CipherCellName);
SubTitle = view.FindViewById<TextView>(Resource.Id.CipherCellSubTitle);
SharedIcon = view.FindViewById<TextView>(Resource.Id.CipherCellSharedIcon);
AttachmentsIcon = view.FindViewById<TextView>(Resource.Id.CipherCellAttachmentsIcon);
MoreButton = view.FindViewById<Android.Widget.Button>(Resource.Id.CipherCellButton);
MoreButton.Click += MoreButton_Click;
Icon.Typeface = _faTypeface;
SharedIcon.Typeface = _faTypeface;
AttachmentsIcon.Typeface = _faTypeface;
MoreButton.Typeface = _miTypeface;
var small = (float)Device.GetNamedSize(NamedSize.Small, typeof(Label));
Icon.SetTextSize(ComplexUnitType.Pt, 10);
Name.SetTextSize(ComplexUnitType.Sp, (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label)));
SubTitle.SetTextSize(ComplexUnitType.Sp, small);
SharedIcon.SetTextSize(ComplexUnitType.Sp, small);
AttachmentsIcon.SetTextSize(ComplexUnitType.Sp, small);
MoreButton.SetTextSize(ComplexUnitType.Sp, 25);
AddView(view);
}
public CipherViewCell CipherViewCell { get; set; }
public Element Element => CipherViewCell;
public IconImageView IconImage { get; set; }
public TextView Icon { get; set; }
public TextView Name { get; set; }
public TextView SubTitle { get; set; }
public TextView SharedIcon { get; set; }
public TextView AttachmentsIcon { get; set; }
public Android.Widget.Button MoreButton { get; set; }
public void UpdateCell(CipherViewCell cipherCell)
{
UpdateIconImage(cipherCell);
var cipher = cipherCell.Cipher;
Name.Text = cipher.Name;
if (!string.IsNullOrWhiteSpace(cipher.SubTitle))
{
SubTitle.Text = cipher.SubTitle;
SubTitle.Visibility = ViewStates.Visible;
}
else
{
SubTitle.Visibility = ViewStates.Invisible;
}
SharedIcon.Visibility = cipher.Shared ? ViewStates.Visible : ViewStates.Gone;
AttachmentsIcon.Visibility = cipher.HasAttachments ? ViewStates.Visible : ViewStates.Gone;
}
public void UpdateIconImage(CipherViewCell cipherCell)
{
if (_currentTask != null && !_currentTask.IsCancelled && !_currentTask.IsCompleted)
{
_currentTask.Cancel();
}
var cipher = cipherCell.Cipher;
var iconImage = cipherCell.GetIconImage(cipher);
if (iconImage.Item2 != null)
{
IconImage.SetImageResource(Resource.Drawable.login);
IconImage.Visibility = ViewStates.Visible;
Icon.Visibility = ViewStates.Gone;
_currentTask = ImageService.Instance.LoadUrl(iconImage.Item2).DownSample(64).Into(IconImage);
IconImage.Key = iconImage.Item2;
}
else
{
IconImage.Visibility = ViewStates.Gone;
Icon.Visibility = ViewStates.Visible;
Icon.Text = iconImage.Item1;
}
}
public void UpdateColors(Android.Graphics.Color textColor, Android.Graphics.Color mutedColor,
Android.Graphics.Color iconDisabledColor)
{
Name.SetTextColor(textColor);
SubTitle.SetTextColor(mutedColor);
Icon.SetTextColor(mutedColor);
SharedIcon.SetTextColor(mutedColor);
AttachmentsIcon.SetTextColor(mutedColor);
MoreButton.SetTextColor(iconDisabledColor);
}
private void MoreButton_Click(object sender, EventArgs e)
{
if (CipherViewCell.ButtonCommand?.CanExecute(CipherViewCell.Cipher) ?? false)
{
CipherViewCell.ButtonCommand.Execute(CipherViewCell.Cipher);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
MoreButton.Click -= MoreButton_Click;
}
base.Dispose(disposing);
}
}
[Android.Runtime.Preserve(AllMembers = true)]
[Register("bit.droid.renderers.IconImageView")]
public class IconImageView : ImageViewAsync
{
public IconImageView(Context context) : base(context)
{ }
public IconImageView(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{ }
public IconImageView(Context context, IAttributeSet attrs)
: base(context, attrs)
{ }
public string Key { get; set; }
protected override void JavaFinalize()
{
SetImageDrawable(null);
SetImageBitmap(null);
ImageService.Instance.InvalidateCacheEntryAsync(Key, FFImageLoading.Cache.CacheType.Memory);
base.JavaFinalize();
}
}
}

View File

@@ -0,0 +1,43 @@
using Android.Content;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ExtendedGrid), typeof(ExtendedGridRenderer))]
namespace Bit.Droid.Renderers
{
public class ExtendedGridRenderer : ViewRenderer
{
private static int? _bgResId;
public ExtendedGridRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
{
base.OnElementChanged(elementChangedEvent);
if (elementChangedEvent.NewElement != null)
{
SetBackgroundResource(GetBgResId());
}
}
private int GetBgResId()
{
if (_bgResId == null)
{
if (ThemeManager.GetTheme(true) == "nord")
{
_bgResId = Resource.Drawable.list_item_bg_nord;
}
else
{
_bgResId ??= ThemeManager.UsingLightTheme ? Resource.Drawable.list_item_bg :
Resource.Drawable.list_item_bg_dark;
}
}
return _bgResId.Value;
}
}
}

View File

@@ -1,29 +0,0 @@
using Android.Content;
using Android.Views;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ExtendedListView), typeof(ExtendedListViewRenderer))]
namespace Bit.Droid.Renderers
{
public class ExtendedListViewRenderer : ListViewRenderer
{
public ExtendedListViewRenderer(Context context)
: base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement != null && e.NewElement is ExtendedListView listView)
{
// Pad for FAB
Control.SetPadding(0, 0, 0, 170);
Control.SetClipToPadding(false);
Control.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
}
}
}
}

View File

@@ -0,0 +1,43 @@
using Android.Content;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ExtendedStackLayout), typeof(ExtendedStackLayoutRenderer))]
namespace Bit.Droid.Renderers
{
public class ExtendedStackLayoutRenderer : ViewRenderer
{
private static int? _bgResId;
public ExtendedStackLayoutRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
{
base.OnElementChanged(elementChangedEvent);
if (elementChangedEvent.NewElement != null)
{
SetBackgroundResource(GetBgResId());
}
}
private int GetBgResId()
{
if (_bgResId == null)
{
if (ThemeManager.GetTheme(true) == "nord")
{
_bgResId = Resource.Drawable.list_item_bg_nord;
}
else
{
_bgResId ??= ThemeManager.UsingLightTheme ? Resource.Drawable.list_item_bg :
Resource.Drawable.list_item_bg_dark;
}
}
return _bgResId.Value;
}
}
}

View File

@@ -1,210 +0,0 @@
using System;
using System.ComponentModel;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Util;
using Android.Views;
using Android.Widget;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using FFImageLoading.Work;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Button = Android.Widget.Button;
using Color = Android.Graphics.Color;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(SendViewCell), typeof(SendViewCellRenderer))]
namespace Bit.Droid.Renderers
{
public class SendViewCellRenderer : ViewCellRenderer
{
private static Typeface _faTypeface;
private static Typeface _miTypeface;
private static Color _textColor;
private static Color _mutedColor;
private static Color _disabledIconColor;
private static bool _usingLightTheme;
private AndroidSendCell _cell;
protected override View GetCellCore(Cell item, View convertView,
ViewGroup parent, Context context)
{
// TODO expand beyond light/dark detection once we support custom theme switching without app restart
var themeChanged = _usingLightTheme != ThemeManager.UsingLightTheme;
if (_faTypeface == null)
{
_faTypeface = Typeface.CreateFromAsset(context.Assets, "FontAwesome.ttf");
}
if (_miTypeface == null)
{
_miTypeface = Typeface.CreateFromAsset(context.Assets, "MaterialIcons_Regular.ttf");
}
if (_textColor == default(Color) || themeChanged)
{
_textColor = ThemeManager.GetResourceColor("TextColor").ToAndroid();
}
if (_mutedColor == default(Color) || themeChanged)
{
_mutedColor = ThemeManager.GetResourceColor("MutedColor").ToAndroid();
}
if (_disabledIconColor == default(Color) || themeChanged)
{
_disabledIconColor = ThemeManager.GetResourceColor("DisabledIconColor").ToAndroid();
}
_usingLightTheme = ThemeManager.UsingLightTheme;
var sendCell = item as SendViewCell;
_cell = convertView as AndroidSendCell;
if (_cell == null)
{
_cell = new AndroidSendCell(context, sendCell, _faTypeface, _miTypeface);
}
else
{
_cell.SendViewCell.PropertyChanged -= CellPropertyChanged;
}
sendCell.PropertyChanged += CellPropertyChanged;
_cell.UpdateCell(sendCell);
_cell.UpdateColors(_textColor, _mutedColor, _disabledIconColor);
return _cell;
}
public void CellPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var sendCell = sender as SendViewCell;
_cell.SendViewCell = sendCell;
if (e.PropertyName == SendViewCell.SendProperty.PropertyName)
{
_cell.UpdateCell(sendCell);
}
}
}
public class AndroidSendCell : LinearLayout, INativeElementView
{
private readonly Typeface _faTypeface;
private readonly Typeface _miTypeface;
private IScheduledWork _currentTask;
public AndroidSendCell(Context context, SendViewCell sendView, Typeface faTypeface, Typeface miTypeface)
: base(context)
{
SendViewCell = sendView;
_faTypeface = faTypeface;
_miTypeface = miTypeface;
var view = (context as Activity).LayoutInflater.Inflate(Resource.Layout.SendViewCell, null);
Icon = view.FindViewById<TextView>(Resource.Id.SendCellIcon);
Name = view.FindViewById<TextView>(Resource.Id.SendCellName);
SubTitle = view.FindViewById<TextView>(Resource.Id.SendCellSubTitle);
DisabledIcon = view.FindViewById<TextView>(Resource.Id.SendCellDisabledIcon);
HasPasswordIcon = view.FindViewById<TextView>(Resource.Id.SendCellHasPasswordIcon);
MaxAccessCountReachedIcon = view.FindViewById<TextView>(Resource.Id.SendCellMaxAccessCountReachedIcon);
ExpiredIcon = view.FindViewById<TextView>(Resource.Id.SendCellExpiredIcon);
PendingDeleteIcon = view.FindViewById<TextView>(Resource.Id.SendCellPendingDeleteIcon);
MoreButton = view.FindViewById<Button>(Resource.Id.SendCellButton);
MoreButton.Click += MoreButton_Click;
Icon.Typeface = _faTypeface;
DisabledIcon.Typeface = _faTypeface;
HasPasswordIcon.Typeface = _faTypeface;
MaxAccessCountReachedIcon.Typeface = _faTypeface;
ExpiredIcon.Typeface = _faTypeface;
PendingDeleteIcon.Typeface = _faTypeface;
MoreButton.Typeface = _miTypeface;
var small = (float)Device.GetNamedSize(NamedSize.Small, typeof(Label));
Icon.SetTextSize(ComplexUnitType.Pt, 10);
Name.SetTextSize(ComplexUnitType.Sp, (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label)));
SubTitle.SetTextSize(ComplexUnitType.Sp, small);
DisabledIcon.SetTextSize(ComplexUnitType.Sp, small);
HasPasswordIcon.SetTextSize(ComplexUnitType.Sp, small);
MaxAccessCountReachedIcon.SetTextSize(ComplexUnitType.Sp, small);
ExpiredIcon.SetTextSize(ComplexUnitType.Sp, small);
PendingDeleteIcon.SetTextSize(ComplexUnitType.Sp, small);
MoreButton.SetTextSize(ComplexUnitType.Sp, 25);
if (!SendViewCell.ShowOptions)
{
MoreButton.Visibility = ViewStates.Gone;
}
AddView(view);
}
public SendViewCell SendViewCell { get; set; }
public Element Element => SendViewCell;
public TextView Icon { get; set; }
public TextView Name { get; set; }
public TextView SubTitle { get; set; }
public TextView DisabledIcon { get; set; }
public TextView HasPasswordIcon { get; set; }
public TextView MaxAccessCountReachedIcon { get; set; }
public TextView ExpiredIcon { get; set; }
public TextView PendingDeleteIcon { get; set; }
public Button MoreButton { get; set; }
public void UpdateCell(SendViewCell sendCell)
{
UpdateIconImage(sendCell);
var send = sendCell.Send;
Name.Text = send.Name;
SubTitle.Text = send.DisplayDate;
DisabledIcon.Visibility = send.Disabled ? ViewStates.Visible : ViewStates.Gone;
HasPasswordIcon.Visibility = send.HasPassword ? ViewStates.Visible : ViewStates.Gone;
MaxAccessCountReachedIcon.Visibility = send.MaxAccessCountReached ? ViewStates.Visible : ViewStates.Gone;
ExpiredIcon.Visibility = send.Expired ? ViewStates.Visible : ViewStates.Gone;
PendingDeleteIcon.Visibility = send.PendingDelete ? ViewStates.Visible : ViewStates.Gone;
}
public void UpdateIconImage(SendViewCell sendCell)
{
if (_currentTask != null && !_currentTask.IsCancelled && !_currentTask.IsCompleted)
{
_currentTask.Cancel();
}
var send = sendCell.Send;
var iconImage = sendCell.GetIconImage(send);
Icon.Text = iconImage;
}
public void UpdateColors(Color textColor, Color mutedColor,
Color iconDisabledColor)
{
Name.SetTextColor(textColor);
SubTitle.SetTextColor(mutedColor);
Icon.SetTextColor(mutedColor);
DisabledIcon.SetTextColor(mutedColor);
HasPasswordIcon.SetTextColor(mutedColor);
MaxAccessCountReachedIcon.SetTextColor(mutedColor);
ExpiredIcon.SetTextColor(mutedColor);
PendingDeleteIcon.SetTextColor(mutedColor);
MoreButton.SetTextColor(iconDisabledColor);
}
private void MoreButton_Click(object sender, EventArgs e)
{
if (SendViewCell.ButtonCommand?.CanExecute(SendViewCell.Send) ?? false)
{
SendViewCell.ButtonCommand.Execute(SendViewCell.Send);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
MoreButton.Click -= MoreButton_Click;
}
base.Dispose(disposing);
}
}
}

View File

@@ -0,0 +1,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/itemPressed">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white" />
</ripple>

View File

@@ -0,0 +1,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/dark_primary">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white" />
</ripple>

View File

@@ -0,0 +1,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/nord_primary">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white" />
</ripple>

View File

@@ -1,86 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="44dp"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="2.2dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="39.8dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:gravity="center">
<bit.droid.renderers.IconImageView
android:id="@+id/CipherCellIconImage"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_gravity="center"
android:gravity="center" />
<TextView
android:id="@+id/CipherCellIcon"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_gravity="center"
android:gravity="center"
android:text="[X]" />
</LinearLayout>
<LinearLayout
android:id="@+id/CipherCellContent"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="center"
android:paddingVertical="7.65dp">
<LinearLayout
android:id="@+id/CipherCellContentTop"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/CipherCellName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:text="Name" />
<TextView
android:id="@+id/CipherCellSharedIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="&#xf1e0;" />
<TextView
android:id="@+id/CipherCellAttachmentsIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="&#xf0c6;" />
</LinearLayout>
<TextView
android:id="@+id/CipherCellSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:text="SubTitle" />
</LinearLayout>
<Button
android:id="@+id/CipherCellButton"
android:layout_width="37dp"
android:layout_height="match_parent"
android:text="&#xe5d4;"
android:gravity="center"
android:padding="0dp"
android:background="@android:color/transparent" />
</LinearLayout>
</RelativeLayout>

View File

@@ -1,104 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="44dp"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="2.2dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="39.8dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:gravity="center">
<TextView
android:id="@+id/SendCellIcon"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_gravity="center"
android:gravity="center"
android:text="[X]" />
</LinearLayout>
<LinearLayout
android:id="@+id/SendCellContent"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="center"
android:paddingVertical="7.65dp">
<LinearLayout
android:id="@+id/SendCellContentTop"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/SendCellName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:text="Name" />
<TextView
android:id="@+id/SendCellDisabledIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="&#xf071;" />
<TextView
android:id="@+id/SendCellHasPasswordIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="&#xf084;" />
<TextView
android:id="@+id/SendCellMaxAccessCountReachedIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="&#xf05e;" />
<TextView
android:id="@+id/SendCellExpiredIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="&#xf017;" />
<TextView
android:id="@+id/SendCellPendingDeleteIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="5dp"
android:singleLine="true"
android:text="&#xf1f8;" />
</LinearLayout>
<TextView
android:id="@+id/SendCellSubTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:text="SubTitle" />
</LinearLayout>
<Button
android:id="@+id/SendCellButton"
android:layout_width="37dp"
android:layout_height="match_parent"
android:text="&#xe5d4;"
android:gravity="center"
android:padding="0dp"
android:background="@android:color/transparent" />
</LinearLayout>
</RelativeLayout>

View File

@@ -6,6 +6,7 @@
<color name="primary">#175DDC</color>
<color name="notificationBar">#1452BC</color>
<color name="border">#dddddd</color>
<color name="itemPressed">#bbbbbb</color>
<!-- Dark theme -->
<color name="dark_primary">#52bdfb</color>

View File

@@ -56,6 +56,12 @@
<compatibility-package
android:name="com.chrome.dev"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.cookiegames.smartcookie"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.cookiejarapps.android.smartcookieweb"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.ecosia.android"
android:maxLongVersionCode="10000000000"/>
@@ -65,18 +71,33 @@
<compatibility-package
android:name="com.google.android.apps.chrome_dev"
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.microsoft.emmx"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.microsoft.emmx.beta"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.microsoft.emmx.canary"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.microsoft.emmx.dev"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.mmbox.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.mmbox.xbrowser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.mycompany.app.soulbrowser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.naver.whale"
android:maxLongVersionCode="10000000000"/>
@@ -143,6 +164,18 @@
<compatibility-package
android:name="mark.via.gp"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.slions.fulguris.full.download"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.slions.fulguris.full.download.debug"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.slions.fulguris.full.playstore"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.slions.fulguris.full.playstore.debug"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="org.adblockplus.browser"
android:maxLongVersionCode="10000000000"/>
@@ -200,4 +233,7 @@
<compatibility-package
android:name="org.ungoogled.chromium.stable"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="us.spotco.fennec_dos"
android:maxLongVersionCode="10000000000"/>
</autofill-service>

View File

@@ -307,7 +307,7 @@ namespace Bit.Droid.Services
public Task<string> DisplayPromptAync(string title = null, string description = null,
string text = null, string okButtonText = null, string cancelButtonText = null,
bool numericKeyboard = false, bool autofocus = true)
bool numericKeyboard = false, bool autofocus = true, bool password = false)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
@@ -333,6 +333,10 @@ namespace Bit.Droid.Services
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
}
if (password)
{
input.InputType = InputTypes.TextVariationPassword | InputTypes.ClassText;
}
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
(ImeAction)ImeFlags.NoExtractUi;
@@ -760,6 +764,28 @@ namespace Bit.Droid.Services
// ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime()
return SystemClock.ElapsedRealtime();
}
public void CloseMainApp()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return;
}
activity.Finish();
_messagingService.Send("finishMainActivity");
}
public bool SupportsFido2()
{
#if !FDROID
if ((int)Build.VERSION.SdkInt >= 21)
{
return true;
}
#endif
return false;
}
private bool DeleteDir(Java.IO.File dir)
{

View File

@@ -3,7 +3,9 @@ using Android.Content.PM;
namespace Bit.Droid
{
[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
[Activity(
NoHistory = true,
LaunchMode = LaunchMode.SingleTop)]
[IntentFilter(new[] { Android.Content.Intent.ActionView },
Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
DataScheme = "bitwarden")]

View File

@@ -1,166 +0,0 @@
$rootPath = $env:APPVEYOR_BUILD_FOLDER;
$androidPath = $($rootPath + "\src\Android\Android.csproj");
$appPath = $($rootPath + "\src\App\App.csproj");
echo "########################################"
echo "##### Increment Version"
echo "########################################"
$androidManifest = $($rootPath + "\src\Android\Properties\AndroidManifest.xml");
$xml=New-Object XML;
$xml.Load($androidManifest);
$node=$xml.SelectNodes("/manifest");
$node.SetAttribute("android:versionCode", $env:APPVEYOR_BUILD_NUMBER);
$xml.Save($androidManifest);
echo "########################################"
echo "##### Decrypt Keystore"
echo "########################################"
$encKeystorePath = $($rootPath + "\src\Android\8bit.keystore.enc");
$encFdroidKeystorePath = $($rootPath + "\src\Android\fdroid-keystore.jks.enc");
$encUploadKeystorePath = $($rootPath + "\src\Android\upload-keystore.jks.enc");
$secureFilePath = $($rootPath + "\secure-file\tools\secure-file.exe");
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encKeystorePath) -secret $($env:keystore_dec_secret)"
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encFdroidKeystorePath) -secret $($env:fdroid_apk_keystore_dec_secret)"
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encUploadKeystorePath) -secret $($env:upload_keystore_dec_secret)"
echo "########################################"
echo "##### Sign Google Play Bundle Release Configuration"
echo "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:upload_keystore_password)" `
"/p:AndroidSigningKeyStore=upload-keystore.jks" "/p:AndroidSigningStorePass=$($env:upload_keystore_password)" `
"/p:AndroidPackageFormat=aab" "/v:quiet"
echo "########################################"
echo "##### Copy Google Play Bundle to project root"
echo "########################################"
$signedAabPath = $($rootPath + "\src\Android\bin\Release\com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($rootPath + "\com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath
echo "########################################"
echo "##### Sign APK Release Configuration"
echo "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:keystore_password)" `
"/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=$($env:keystore_password)" "/v:quiet"
echo "########################################"
echo "##### Copy Release APK to project root"
echo "########################################"
$signedApkPath = $($rootPath + "\src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "\com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath
echo "########################################"
echo "##### Clean Android and App"
echo "########################################"
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
echo "########################################"
echo "##### Backup project files"
echo "########################################"
Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $androidPath $($androidPath + ".original");
Copy-Item $appPath $($appPath + ".original");
echo "########################################"
echo "##### Cleanup Android Manifest"
echo "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$firebaseReceiver1=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdInternalReceiver']", `
$nsAndroid);
$firebaseReceiver1.ParentNode.RemoveChild($firebaseReceiver1);
$firebaseReceiver2=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdReceiver']", `
$nsAndroid);
$firebaseReceiver2.ParentNode.RemoveChild($firebaseReceiver2);
$xml.Save($androidManifest);
echo "########################################"
echo "##### Uninstall from Android.csproj"
echo "########################################"
$xml=New-Object XML;
$xml.Load($androidPath);
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
$firebaseNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
$safetyNetNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
$xml.Save($androidPath);
echo "########################################"
echo "##### Uninstall from App.csproj"
echo "########################################"
$xml=New-Object XML;
$xml.Load($appPath);
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
$xml.Save($appPath);
echo "########################################"
echo "##### Restore NuGet"
echo "########################################"
Invoke-Expression "& nuget restore"
echo "########################################"
echo "##### Build and Sign FDroid Configuration"
echo "########################################"
msbuild "$($androidPath)" "/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" `
"/p:Configuration=FDroid"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:fdroid_apk_keystore_password)" `
"/p:AndroidSigningKeyStore=fdroid-keystore.jks" "/p:AndroidSigningStorePass=$($env:fdroid_apk_keystore_password)" `
"/v:quiet"
echo "########################################"
echo "##### Copy FDroid apk to project root"
echo "########################################"
$signedApkPath = $($rootPath + "\src\Android\bin\FDroid\com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "\com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath
echo "########################################"
echo "##### Done"
echo "########################################"

View File

@@ -19,7 +19,7 @@ namespace Bit.App.Abstractions
Task SelectFileAsync();
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
bool autofocus = true);
bool autofocus = true, bool password = false);
void RateApp();
bool SupportsFaceBiometric();
Task<bool> SupportsFaceBiometricAsync();
@@ -44,5 +44,7 @@ namespace Bit.App.Abstractions
void OpenAutofillSettings();
bool UsingDarkTheme();
long GetActiveTime();
void CloseMainApp();
bool SupportsFido2();
}
}

View File

@@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace Bit.App.Abstractions
{
public interface IPasswordRepromptService
{
string[] ProtectedFields { get; }
Task<bool> ShowPasswordPromptAsync();
}
}

View File

@@ -13,11 +13,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="3.2.1" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.2" />
<PackageReference Include="Xamarin.Essentials" Version="1.5.3.2" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="4.3.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="4.5.0.725" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2083" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
</ItemGroup>
@@ -411,4 +411,4 @@
</EmbeddedResource>
</ItemGroup>
</Project>
</Project>

View File

@@ -131,7 +131,8 @@ namespace Bit.App
await SetMainPageAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault")
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend")
{
Device.BeginInvokeOnMainThread(async () =>
{
@@ -146,11 +147,15 @@ namespace Bit.App
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else
else if (message.Command == "popAllAndGoToTabGenerator")
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
}
});
}
@@ -173,6 +178,10 @@ namespace Bit.App
SyncIfNeeded();
}
}
if (Device.RuntimePlatform == Device.Android)
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
}
_messagingService.Send("startEventTimer");
}
@@ -211,7 +220,6 @@ namespace Bit.App
private async void ResumedAsync()
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
_messagingService.Send("cancelVaultTimeoutTimer");
_messagingService.Send("startEventTimer");
await ClearCacheIfNeededAsync();
Prime();
@@ -244,7 +252,8 @@ namespace Bit.App
_collectionService.ClearAsync(userId),
_passwordGenerationService.ClearAsync(),
_vaultTimeoutService.ClearAsync(),
_stateService.PurgeAsync());
_stateService.PurgeAsync(),
_deviceActionService.ClearCacheAsync());
_vaultTimeoutService.BiometricLocked = true;
_searchService.ClearIndex();
_authService.LogOut(() =>
@@ -274,6 +283,10 @@ namespace Bit.App
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (Options.CreateSend != null)
{
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
}
else
{
Current.MainPage = new TabsPage(Options);
@@ -303,11 +316,7 @@ namespace Bit.App
vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
}
vaultTimeout = vaultTimeout.GetValueOrDefault(-1);
if (vaultTimeout > 0)
{
_messagingService.Send("scheduleVaultTimeoutTimer", vaultTimeout.Value);
}
else if (vaultTimeout == 0)
if (vaultTimeout == 0)
{
var action = await _storageService.GetAsync<string>(Constants.VaultTimeoutActionKey);
if (action == "logOut")

View File

@@ -1,114 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.CipherViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms">
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.CipherViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="controls:CipherViewCellViewModel">
<Grid
x:Name="_grid"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="controls:CipherViewCellViewModel">
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
<u:IconImageConverter x:Key="iconImageConverter"/>
<u:InverseBoolConverter x:Key="inverseBool" />
</Grid.Resources>
<Grid.BindingContext>
<controls:CipherViewCellViewModel />
</Grid.BindingContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<controls:FaLabel
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage
Grid.Column="0"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
LoadingPlaceholder="login.png"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="22"
HeightRequest="22"
IsVisible="{Binding ShowIconImage}"
Source="{Binding Cipher, Converter={StaticResource iconImageConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform"
Text="{Binding Cipher.Name}" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="3"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}" />
<controls:FaLabel
x:Name="_icon"
Grid.Column="1"
Grid.Row="0"
Grid.Column="0"
HorizontalOptions="Center"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
AutomationProperties.IsInAccessibleTree="False" />
<ff:CachedImage
x:Name="_image"
Grid.Row="0"
Grid.Column="0"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="22"
HeightRequest="22"
IsVisible="False"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform"
Text="{Binding Cipher.Name, Mode=OneWay}" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="3"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle, Mode=OneWay}" />
<controls:FaLabel
Grid.Column="1"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf1e0;"
IsVisible="{Binding Cipher.Shared, Mode=OneWay}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Shared}" />
<controls:FaLabel
Grid.Column="2"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf0c6;"
IsVisible="{Binding Cipher.HasAttachments, Mode=OneWay}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Attachments}" />
</Grid>
<controls:MiButton
Grid.Row="0"
Grid.Column="2"
Text="&#xe5d3;"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked"
VerticalOptions="CenterAndExpand"
HorizontalOptions="EndAndExpand"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf1b2;"
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
AutomationProperties.Name="{u:I18n Shared}" />
<controls:FaLabel
Grid.Column="2"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf0c6;"
IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Attachments}" />
</Grid>
</ViewCell>
<controls:MiButton
Grid.Row="0"
Grid.Column="2"
Text="&#xe5d3;"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked"
VerticalOptions="CenterAndExpand"
HorizontalOptions="EndAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</controls:ExtendedGrid>

View File

@@ -1,45 +1,26 @@
using Bit.App.Pages;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using System;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class CipherViewCell : ViewCell
public partial class CipherViewCell : ExtendedGrid
{
public static readonly BindableProperty CipherProperty = BindableProperty.Create(
nameof(Cipher), typeof(CipherView), typeof(CipherViewCell), default(CipherView), BindingMode.OneWay);
public static readonly BindableProperty WebsiteIconsEnabledProperty = BindableProperty.Create(
nameof(WebsiteIconsEnabled), typeof(bool), typeof(CipherViewCell), true, BindingMode.OneWay);
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(CipherViewCell));
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
nameof(ButtonCommand), typeof(Command<CipherView>), typeof(CipherViewCell));
private readonly IEnvironmentService _environmentService;
private CipherViewCellViewModel _viewModel;
private bool _usingNativeCell;
public CipherViewCell()
{
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
if (Device.RuntimePlatform == Device.iOS)
{
InitializeComponent();
_viewModel = _grid.BindingContext as CipherViewCellViewModel;
}
else
{
_usingNativeCell = true;
}
InitializeComponent();
}
public bool WebsiteIconsEnabled
public bool? WebsiteIconsEnabled
{
get => (bool)GetValue(WebsiteIconsEnabledProperty);
set => SetValue(WebsiteIconsEnabledProperty, value);
@@ -60,130 +41,31 @@ namespace Bit.App.Controls
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (_usingNativeCell)
{
return;
}
if (propertyName == CipherProperty.PropertyName)
{
_viewModel.Cipher = Cipher;
if (Cipher == null)
{
return;
}
BindingContext = new CipherViewCellViewModel(Cipher, WebsiteIconsEnabled ?? false);
}
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (_usingNativeCell)
else if (propertyName == WebsiteIconsEnabledProperty.PropertyName)
{
return;
if (Cipher == null)
{
return;
}
((CipherViewCellViewModel)BindingContext).WebsiteIconsEnabled = WebsiteIconsEnabled ?? false;
}
_image.Source = null;
CipherView cipher = null;
if (BindingContext is GroupingsPageListItem groupingsPageListItem)
{
cipher = groupingsPageListItem.Cipher;
}
else if (BindingContext is CipherView cv)
{
cipher = cv;
}
if (cipher != null)
{
var iconImage = GetIconImage(cipher);
if (iconImage.Item2 != null)
{
_image.IsVisible = true;
_icon.IsVisible = false;
_image.Source = iconImage.Item2;
_image.LoadingPlaceholder = "login.png";
}
else
{
_image.IsVisible = false;
_icon.IsVisible = true;
_icon.Text = iconImage.Item1;
}
}
}
public Tuple<string, string> GetIconImage(CipherView cipher)
{
string icon = null;
string image = null;
switch (cipher.Type)
{
case CipherType.Login:
var loginIconImage = GetLoginIconImage(cipher);
icon = loginIconImage.Item1;
image = loginIconImage.Item2;
break;
case CipherType.SecureNote:
icon = "";
break;
case CipherType.Card:
icon = "";
break;
case CipherType.Identity:
icon = "";
break;
default:
break;
}
return new Tuple<string, string>(icon, image);
}
private Tuple<string, string> GetLoginIconImage(CipherView cipher)
{
string icon = "";
string image = null;
if (cipher.Login.Uri != null)
{
var hostnameUri = cipher.Login.Uri;
var isWebsite = false;
if (hostnameUri.StartsWith(Constants.AndroidAppProtocol))
{
icon = "";
}
else if (hostnameUri.StartsWith(Constants.iOSAppProtocol))
{
icon = "";
}
else if (WebsiteIconsEnabled && !hostnameUri.Contains("://") && hostnameUri.Contains("."))
{
hostnameUri = string.Concat("http://", hostnameUri);
isWebsite = true;
}
else if (WebsiteIconsEnabled)
{
isWebsite = hostnameUri.StartsWith("http") && hostnameUri.Contains(".");
}
if (WebsiteIconsEnabled && isWebsite)
{
var hostname = CoreHelpers.GetHostname(hostnameUri);
var iconsUrl = _environmentService.IconsUrl;
if (string.IsNullOrWhiteSpace(iconsUrl))
{
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
{
iconsUrl = string.Format("{0}/icons", _environmentService.BaseUrl);
}
else
{
iconsUrl = "https://icons.bitwarden.net";
}
}
image = string.Format("{0}/{1}/icon.png", iconsUrl, hostname);
}
}
return new Tuple<string, string>(icon, image);
}
private void MoreButton_Clicked(object sender, EventArgs e)
{
ButtonCommand?.Execute(Cipher);
var cipher = ((sender as MiButton)?.BindingContext as CipherViewCellViewModel)?.Cipher;
if (cipher != null)
{
ButtonCommand?.Execute(cipher);
}
}
}
}

View File

@@ -6,11 +6,30 @@ namespace Bit.App.Controls
public class CipherViewCellViewModel : ExtendedViewModel
{
private CipherView _cipher;
private bool _websiteIconsEnabled;
public CipherViewCellViewModel(CipherView cipherView, bool websiteIconsEnabled)
{
Cipher = cipherView;
WebsiteIconsEnabled = websiteIconsEnabled;
}
public CipherView Cipher
{
get => _cipher;
set => SetProperty(ref _cipher, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public bool ShowIconImage
{
get => WebsiteIconsEnabled && !string.IsNullOrWhiteSpace(Cipher.Login?.Uri) &&
Cipher.Login.Uri.StartsWith("http");
}
}
}

View File

@@ -0,0 +1,8 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedCollectionView : CollectionView
{
}
}

View File

@@ -0,0 +1,8 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedGrid : Grid
{
}
}

View File

@@ -1,12 +0,0 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedListView : ListView
{
public ExtendedListView() { }
public ExtendedListView(ListViewCachingStrategy cachingStrategy)
: base(cachingStrategy) { }
}
}

View File

@@ -0,0 +1,8 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedStackLayout : StackLayout
{
}
}

View File

@@ -1,137 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.SendViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities">
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.SendViewCell"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="controls:SendViewCellViewModel">
<Grid
x:Name="_grid"
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="controls:SendViewCellViewModel">
<Grid.Resources>
<u:SendIconGlyphConverter x:Key="sendIconGlyphConverter"/>
</Grid.Resources>
<Grid.BindingContext>
<controls:SendViewCellViewModel />
</Grid.BindingContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<controls:FaLabel
Grid.Row="0"
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding Send, Converter={StaticResource sendIconGlyphConverter}}"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<controls:FaLabel
x:Name="_icon"
Grid.Row="0"
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
AutomationProperties.IsInAccessibleTree="False" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform"
Text="{Binding Send.Name, Mode=OneWay}" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="6"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Send.DisplayDate, Mode=OneWay}" />
<controls:FaLabel
Grid.Column="1"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf071;"
IsVisible="{Binding Send.Disabled, Mode=OneWay}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Disabled}" />
<controls:FaLabel
Grid.Column="2"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf084;"
IsVisible="{Binding Send.HasPassword, Mode=OneWay}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Password}" />
<controls:FaLabel
Grid.Column="3"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf05e;"
IsVisible="{Binding Send.MaxAccessCountReached, Mode=OneWay}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n MaxAccessCountReached}" />
<controls:FaLabel
Grid.Column="4"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf017;"
IsVisible="{Binding Send.Expired, Mode=OneWay}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Expired}" />
<controls:FaLabel
Grid.Column="5"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf1f8;"
IsVisible="{Binding Send.PendingDelete, Mode=OneWay}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n PendingDelete}" />
</Grid>
<controls:MiButton
Grid.Row="0"
Grid.Column="2"
Text="&#xe5d3;"
IsVisible="{Binding ShowOptions, Mode=OneWay}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked"
VerticalOptions="CenterAndExpand"
HorizontalOptions="EndAndExpand"
StyleClass="list-title, list-title-platform"
Text="{Binding Send.Name}" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="6"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Send.DisplayDate}" />
<controls:FaLabel
Grid.Column="1"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf071;"
IsVisible="{Binding Send.Disabled, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
AutomationProperties.Name="{u:I18n Disabled}" />
<controls:FaLabel
Grid.Column="2"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf084;"
IsVisible="{Binding Send.HasPassword, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Password}" />
<controls:FaLabel
Grid.Column="3"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf05e;"
IsVisible="{Binding Send.MaxAccessCountReached, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n MaxAccessCountReached}" />
<controls:FaLabel
Grid.Column="4"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf017;"
IsVisible="{Binding Send.Expired, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Expired}" />
<controls:FaLabel
Grid.Column="5"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf1f8;"
IsVisible="{Binding Send.PendingDelete, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n PendingDelete}" />
</Grid>
</ViewCell>
<controls:MiButton
Grid.Row="0"
Grid.Column="2"
Text="&#xe5d3;"
IsVisible="{Binding ShowOptions, Mode=OneWay}"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="MoreButton_Clicked"
VerticalOptions="CenterAndExpand"
HorizontalOptions="EndAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</controls:ExtendedGrid>

View File

@@ -1,14 +1,10 @@
using System;
using Bit.App.Pages;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class SendViewCell : ViewCell
public partial class SendViewCell : ExtendedGrid
{
public static readonly BindableProperty SendProperty = BindableProperty.Create(
nameof(Send), typeof(SendView), typeof(SendViewCell), default(SendView), BindingMode.OneWay);
@@ -17,25 +13,11 @@ namespace Bit.App.Controls
nameof(ButtonCommand), typeof(Command<SendView>), typeof(SendViewCell));
public static readonly BindableProperty ShowOptionsProperty = BindableProperty.Create(
nameof(ShowOptions), typeof(bool), typeof(SendViewCell));
private readonly IEnvironmentService _environmentService;
private SendViewCellViewModel _viewModel;
private bool _usingNativeCell;
nameof(ShowOptions), typeof(bool), typeof(SendViewCell), true, BindingMode.OneWay);
public SendViewCell()
{
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
if (Device.RuntimePlatform == Device.iOS)
{
InitializeComponent();
_viewModel = _grid.BindingContext as SendViewCellViewModel;
}
else
{
_usingNativeCell = true;
}
InitializeComponent();
}
public SendView Send
@@ -52,72 +34,30 @@ namespace Bit.App.Controls
public bool ShowOptions
{
get => GetValue(ShowOptionsProperty) is bool && (bool)GetValue(ShowOptionsProperty);
get => (bool)GetValue(ShowOptionsProperty);
set => SetValue(ShowOptionsProperty, value);
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (_usingNativeCell)
{
return;
}
if (propertyName == SendProperty.PropertyName)
{
_viewModel.Send = Send;
if (Send == null)
{
return;
}
BindingContext = new SendViewCellViewModel(Send, ShowOptions);
}
else if (propertyName == ShowOptionsProperty.PropertyName)
{
_viewModel.ShowOptions = ShowOptions;
}
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (_usingNativeCell)
{
return;
}
SendView send = null;
if (BindingContext is SendGroupingsPageListItem sendGroupingsPageListItem)
{
send = sendGroupingsPageListItem.Send;
}
else if (BindingContext is SendView sv)
{
send = sv;
}
if (send != null)
{
var iconImage = GetIconImage(send);
_icon.IsVisible = true;
_icon.Text = iconImage;
}
}
public string GetIconImage(SendView send)
{
string icon = null;
switch (send.Type)
{
case SendType.Text:
icon = "\uf0f6"; // fa-file-text-o
break;
case SendType.File:
icon = "\uf016"; // fa-file-o
break;
default:
break;
}
return icon;
}
private void MoreButton_Clicked(object sender, EventArgs e)
{
ButtonCommand?.Execute(Send);
var send = ((sender as MiButton)?.BindingContext as SendViewCellViewModel)?.Send;
if (send != null)
{
ButtonCommand?.Execute(send);
}
}
}
}

View File

@@ -8,6 +8,12 @@ namespace Bit.App.Controls
private SendView _send;
private bool _showOptions;
public SendViewCellViewModel(SendView sendView, bool showOptions)
{
Send = sendView;
ShowOptions = showOptions;
}
public SendView Send
{
get => _send;

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System;
using Bit.Core.Enums;
namespace Bit.App.Models
{
@@ -19,6 +20,7 @@ namespace Bit.App.Models
public string SaveCardExpYear { get; set; }
public string SaveCardCode { get; set; }
public bool IosExtension { get; set; }
public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
public void SetAllFrom(AppOptions o)
{
@@ -41,6 +43,7 @@ namespace Bit.App.Models
SaveCardExpYear = o.SaveCardExpYear;
SaveCardCode = o.SaveCardCode;
IosExtension = o.IosExtension;
CreateSend = o.CreateSend;
}
}
}

View File

@@ -13,7 +13,7 @@
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Save}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>

View File

@@ -13,7 +13,7 @@
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>

View File

@@ -8,6 +8,7 @@ using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Models.Request;
using Xamarin.Forms;
@@ -37,7 +38,6 @@ namespace Bit.App.Pages
private string _biometricButtonText;
private string _loggedInAsText;
private string _lockedVerifyText;
private int _invalidPinAttempts = 0;
private Tuple<bool, bool> _pinSet;
public LockPageViewModel()
@@ -203,11 +203,12 @@ namespace Bit.App.Pages
_vaultTimeoutService.PinProtectedKey);
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin;
if (!failed)
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
}
@@ -217,6 +218,7 @@ namespace Bit.App.Pages
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
failed = false;
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
}
@@ -226,8 +228,8 @@ namespace Bit.App.Pages
}
if (failed)
{
_invalidPinAttempts++;
if (_invalidPinAttempts >= 5)
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
_messagingService.Send("logout");
return;
@@ -239,32 +241,31 @@ namespace Bit.App.Pages
else
{
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
var passwordValid = false;
if (keyHash != null)
if (storedKeyHash != null)
{
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash != null)
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
}
else
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash;
try
{
passwordValid = storedKeyHash == keyHash;
await _apiService.PostAccountVerifyPasswordAsync(request);
passwordValid = true;
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
await _cryptoService.SetKeyHashAsync(localKeyHash);
}
else
catch (Exception e)
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash;
try
{
await _apiService.PostAccountVerifyPasswordAsync(request);
passwordValid = true;
await _cryptoService.SetKeyHashAsync(keyHash);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
}
await _deviceActionService.HideLoadingAsync();
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
}
await _deviceActionService.HideLoadingAsync();
}
if (passwordValid)
{
@@ -272,12 +273,13 @@ namespace Bit.App.Pages
{
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
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);
}
MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
// Re-enable biometrics
@@ -288,6 +290,12 @@ namespace Bit.App.Pages
}
else
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
_messagingService.Send("logout");
return;
}
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
AppResources.AnErrorHasOccurred);
}

View File

@@ -16,6 +16,8 @@ namespace Bit.App.Pages
private readonly LoginPageViewModel _vm;
private readonly AppOptions _appOptions;
private bool _inputFocused;
public LoginPage(string email = null, AppOptions appOptions = null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
@@ -58,13 +60,10 @@ namespace Bit.App.Pages
{
base.OnAppearing();
await _vm.InitAsync();
if (string.IsNullOrWhiteSpace(_vm.Email))
if (!_inputFocused)
{
RequestFocus(_email);
}
else
{
RequestFocus(_masterPassword);
RequestFocus(string.IsNullOrWhiteSpace(_vm.Email) ? _email : _masterPassword);
_inputFocused = true;
}
}

View File

@@ -6,11 +6,17 @@ using Bit.Core.Exceptions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Xamarin.Forms;
using Newtonsoft.Json;
using System.Text;
using Xamarin.Essentials;
using System.Text.RegularExpressions;
using Bit.Core.Services;
namespace Bit.App.Pages
{
public class LoginPageViewModel : BaseViewModel
public class LoginPageViewModel : CaptchaProtectedViewModel
{
private const string Keys_RememberedEmail = "rememberedEmail";
private const string Keys_RememberEmail = "rememberEmail";
@@ -21,6 +27,8 @@ namespace Bit.App.Pages
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private readonly IEnvironmentService _environmentService;
private readonly II18nService _i18nService;
private bool _showPassword;
private string _email;
@@ -34,6 +42,8 @@ namespace Bit.App.Pages
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword);
@@ -69,7 +79,12 @@ namespace Bit.App.Pages
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action CloseAction { get; set; }
protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService;
protected override IDeviceActionService deviceActionService => _deviceActionService;
protected override IPlatformUtilsService platformUtilsService => _platformUtilsService;
public async Task InitAsync()
{
if (string.IsNullOrWhiteSpace(Email))
@@ -80,7 +95,7 @@ namespace Bit.App.Pages
RememberEmail = rememberEmail.GetValueOrDefault(true);
}
public async Task LogInAsync()
public async Task LogInAsync(bool showLoading = true)
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
@@ -114,9 +129,12 @@ namespace Bit.App.Pages
ShowPassword = false;
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
var response = await _authService.LogInAsync(Email, MasterPassword);
MasterPassword = string.Empty;
if (showLoading)
{
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
}
var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken);
if (RememberEmail)
{
await _storageService.SaveAsync(Keys_RememberedEmail, Email);
@@ -125,7 +143,22 @@ namespace Bit.App.Pages
{
await _storageService.RemoveAsync(Keys_RememberedEmail);
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (response.CaptchaNeeded)
{
if (await HandleCaptchaAsync(response.CaptchaSiteKey))
{
await LogInAsync(false);
_captchaToken = null;
}
return;
}
MasterPassword = string.Empty;
_captchaToken = null;
await _deviceActionService.HideLoadingAsync();
if (response.TwoFactor)
{
StartTwoFactorAction?.Invoke();
@@ -140,6 +173,8 @@ namespace Bit.App.Pages
}
catch (ApiException e)
{
_captchaToken = null;
MasterPassword = string.Empty;
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{

View File

@@ -13,8 +13,7 @@
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n LogIn}" Clicked="LogIn_Clicked" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ScrollView>
@@ -36,6 +35,10 @@
ReturnCommand="{Binding LogInCommand}" />
</StackLayout>
</StackLayout>
<StackLayout Padding="10, 0">
<Button Text="{u:I18n LogIn}"
Clicked="LogIn_Clicked"></Button>
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -5,6 +5,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
@@ -122,43 +123,28 @@ namespace Bit.App.Pages
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
WebAuthenticatorResult authResult = null;
bool cancelled = false;
try
{
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
new Uri(redirectUri));
}
catch (TaskCanceledException taskCanceledException)
catch (TaskCanceledException)
{
// user canceled
await _deviceActionService.HideLoadingAsync();
return;
}
var code = GetResultCode(authResult, state);
if (!string.IsNullOrEmpty(code))
{
await LogIn(code, codeVerifier, redirectUri);
}
else
{
await _deviceActionService.HideLoadingAsync();
cancelled = true;
}
catch (Exception e)
{
// WebAuthenticator throws NSErrorException if iOS flow is cancelled - by setting cancelled to true
// here we maintain the appearance of a clean cancellation (we don't want to do this across the board
// because we still want to present legitimate errors). If/when this is fixed, we can remove this
// particular catch block (catching taskCanceledException above must remain)
// https://github.com/xamarin/Essentials/issues/1240
if (Device.RuntimePlatform == Device.iOS)
{
await _deviceActionService.HideLoadingAsync();
cancelled = true;
}
}
if (!cancelled)
{
var code = GetResultCode(authResult, state);
if (!string.IsNullOrEmpty(code))
{
await LogIn(code, codeVerifier, redirectUri);
}
else
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
AppResources.AnErrorHasOccurred);
}
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
AppResources.AnErrorHasOccurred);
}
}
@@ -182,6 +168,7 @@ namespace Bit.App.Pages
try
{
var response = await _authService.LogInSsoAsync(code, codeVerifier, redirectUri);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (RememberOrgIdentifier)
{
await _storageService.SaveAsync(Keys_RememberedOrgIdentifier, OrgIdentifier);

View File

@@ -20,7 +20,7 @@
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Submit}" Clicked="Submit_Clicked" />
</ContentPage.ToolbarItems>

View File

@@ -11,6 +11,8 @@ namespace Bit.App.Pages
private readonly IMessagingService _messagingService;
private readonly RegisterPageViewModel _vm;
private bool _inputFocused;
public RegisterPage(HomePage homePage)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@@ -45,7 +47,11 @@ namespace Bit.App.Pages
protected override void OnAppearing()
{
base.OnAppearing();
RequestFocus(_email);
if (!_inputFocused)
{
RequestFocus(_email);
_inputFocused = true;
}
}
private async void Submit_Clicked(object sender, EventArgs e)

View File

@@ -12,9 +12,11 @@ using Xamarin.Forms;
namespace Bit.App.Pages
{
public class RegisterPageViewModel : BaseViewModel
public class RegisterPageViewModel : CaptchaProtectedViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly II18nService _i18nService;
private readonly IEnvironmentService _environmentService;
private readonly IApiService _apiService;
private readonly ICryptoService _cryptoService;
private readonly IPlatformUtilsService _platformUtilsService;
@@ -27,6 +29,8 @@ namespace Bit.App.Pages
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
PageTitle = AppResources.CreateAccount;
TogglePasswordCommand = new Command(TogglePassword);
@@ -76,7 +80,12 @@ namespace Bit.App.Pages
public Action RegistrationSuccess { get; set; }
public Action CloseAction { get; set; }
public async Task SubmitAsync()
protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService;
protected override IDeviceActionService deviceActionService => _deviceActionService;
protected override IPlatformUtilsService platformUtilsService => _platformUtilsService;
public async Task SubmitAsync(bool showLoading = true)
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
@@ -123,6 +132,11 @@ namespace Bit.App.Pages
}
// TODO: Password strength check?
if (showLoading)
{
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
}
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
Email = Email.Trim().ToLower();
@@ -145,13 +159,13 @@ namespace Bit.App.Pages
{
PublicKey = keys.Item1,
EncryptedPrivateKey = keys.Item2.EncryptedString
}
},
CaptchaResponse = _captchaToken,
};
// TODO: org invite?
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
await _apiService.PostRegisterAsync(request);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.AccountCreated,
@@ -163,6 +177,15 @@ namespace Bit.App.Pages
}
catch (ApiException e)
{
if (e?.Error != null && e.Error.CaptchaRequired)
{
if (await HandleCaptchaAsync(e.Error.CaptchaSiteKey))
{
await SubmitAsync(false);
_captchaToken = null;
}
return;
}
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{

View File

@@ -138,9 +138,10 @@ namespace Bit.App.Pages
var kdfIterations = 100000;
var email = await _userService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
Tuple<SymmetricCryptoKey, CipherString> encKey;
Tuple<SymmetricCryptoKey, EncString> encKey;
var existingEncKey = await _cryptoService.GetEncKeyAsync();
if (existingEncKey == null)
{
@@ -174,7 +175,7 @@ namespace Bit.App.Pages
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
await _userService.GetEmailAsync(), kdf, kdfIterations);
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(masterPasswordHash);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
await _deviceActionService.HideLoadingAsync();

View File

@@ -16,14 +16,21 @@
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Name="_cancelItem" />
<ToolbarItem Text="{u:I18n Continue}" Clicked="Continue_Clicked" Order="Primary"
x:Name="_continueItem" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:IsNullConverter x:Key="isNull" />
<ToolbarItem Icon="more_vert.png" Clicked="More_Clicked" Order="Primary"
x:Name="_moreItem" x:Key="moreItem"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
<ToolbarItem Text="{u:I18n UseAnotherTwoStepMethod}"
Clicked="Methods_Clicked"
Order="Secondary"
x:Name="_useAnotherTwoStepMethod"
x:Key="useAnotherTwoStepMethod" />
</ResourceDictionary>
</ContentPage.Resources>
@@ -45,7 +52,8 @@
Keyboard="Numeric"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
ReturnCommand="{Binding SubmitCommand}"
TextChanged="Token_TextChanged"/>
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label
@@ -94,6 +102,30 @@
</StackLayout>
</StackLayout>
</StackLayout>
<StackLayout Spacing="20" Padding="0" IsVisible="{Binding Fido2Method, Mode=OneWay}">
<Label
Text="{u:I18n Fido2Instruction}"
Margin="10, 20, 10, 0"
HorizontalTextAlignment="Center" />
<Image
Source="yubikey.png"
Margin="10, 0"
WidthRequest="266"
HeightRequest="160"
HorizontalOptions="Center" />
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n RememberMe}"
StyleClass="box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Remember}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
</StackLayout>
</StackLayout>
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding DuoMethod, Mode=OneWay}"
VerticalOptions="FillAndExpand">
<controls:HybridWebView
@@ -123,6 +155,12 @@
Margin="10, 20, 10, 10"
HorizontalTextAlignment="Center" />
</StackLayout>
<Button Text="{u:I18n Continue}"
IsEnabled="{Binding EnableContinue}"
IsVisible="{Binding ShowContinue}"
Clicked="Continue_Clicked"
Margin="10, 0"
x:Name="_continue"></Button>
<Button Text="{u:I18n SendVerificationCodeAgain}"
IsVisible="{Binding EmailMethod}"
Clicked="ResendEmail_Clicked"
@@ -131,9 +169,6 @@
IsVisible="{Binding ShowTryAgain}"
Clicked="TryAgain_Clicked"
Margin="10, 0"></Button>
<Button Text="{u:I18n UseAnotherTwoStepMethod}"
Clicked="Methods_Clicked"
Margin="10, 0"></Button>
</StackLayout>
</ScrollView>

View File

@@ -1,5 +1,6 @@
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
@@ -45,26 +46,17 @@ namespace Bit.App.Pages
{
ToolbarItems.Remove(_cancelItem);
}
if (Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_moreItem);
} else
{
ToolbarItems.Add(_useAnotherTwoStepMethod);
}
}
public HybridWebView DuoWebView { get; set; }
public void AddContinueButton()
{
if (!ToolbarItems.Contains(_continueItem))
{
ToolbarItems.Add(_continueItem);
}
}
public void RemoveContinueButton()
{
if (ToolbarItems.Contains(_continueItem))
{
ToolbarItems.Remove(_continueItem);
}
}
protected async override void OnAppearing()
{
base.OnAppearing();
@@ -83,6 +75,18 @@ namespace Bit.App.Pages
});
}
}
else if (message.Command == "gotFido2Token")
{
var token = (string)message.Data;
if (!string.IsNullOrWhiteSpace(token))
{
Device.BeginInvokeOnMainThread(async () =>
{
_vm.Token = token;
await _vm.SubmitAsync();
});
}
}
else if (message.Command == "resumeYubiKey")
{
if (_vm.YubikeyMethod)
@@ -145,6 +149,21 @@ namespace Bit.App.Pages
}
}
private async void More_Clicked(object sender, EventArgs e)
{
if (!DoOnce())
{
return;
}
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel, null, AppResources.UseAnotherTwoStepMethod);
if (selection == AppResources.UseAnotherTwoStepMethod)
{
await _vm.AnotherMethodAsync();
}
}
private async void ResendEmail_Clicked(object sender, EventArgs e)
{
if (DoOnce())
@@ -161,11 +180,22 @@ namespace Bit.App.Pages
}
}
private void TryAgain_Clicked(object sender, EventArgs e)
private async void TryAgain_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
if (_vm.YubikeyMethod)
if (_vm.Fido2Method)
{
if (Device.RuntimePlatform == Device.Android)
{
_messagingService.Send("listenFido2TryAgain", true);
}
else
{
await _vm.Fido2AuthenticateAsync();
}
}
else if (_vm.YubikeyMethod)
{
_messagingService.Send("listenYubiKeyOTP", true);
}
@@ -195,5 +225,10 @@ namespace Bit.App.Pages
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
}
private void Token_TextChanged(object sender, TextChangedEventArgs e)
{
_vm.EnableContinue = !string.IsNullOrWhiteSpace(e.NewTextValue);
}
}
}

View File

@@ -7,9 +7,15 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.App.Utilities;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -27,11 +33,12 @@ namespace Bit.App.Pages
private readonly IBroadcasterService _broadcasterService;
private readonly IStateService _stateService;
private bool _u2fSupported = false;
private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction;
private string _webVaultUrl = "https://vault.bitwarden.com";
private bool _authingWithSso = false;
private bool _enableContinue = false;
private bool _showContinue = true;
public TwoFactorPageViewModel()
{
@@ -63,6 +70,8 @@ namespace Bit.App.Pages
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
SelectedProviderType == TwoFactorProviderType.OrganizationDuo;
public bool Fido2Method => SelectedProviderType == TwoFactorProviderType.Fido2WebAuthn;
public bool YubikeyMethod => SelectedProviderType == TwoFactorProviderType.YubiKey;
public bool AuthenticatorMethod => SelectedProviderType == TwoFactorProviderType.Authenticator;
@@ -71,7 +80,19 @@ namespace Bit.App.Pages
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
public bool ShowTryAgain => YubikeyMethod && Device.RuntimePlatform == Device.iOS;
public bool ShowTryAgain => (YubikeyMethod && Device.RuntimePlatform == Device.iOS) || Fido2Method;
public bool ShowContinue
{
get => _showContinue;
set => SetProperty(ref _showContinue, value);
}
public bool EnableContinue
{
get => _enableContinue;
set => SetProperty(ref _enableContinue, value);
}
public string YubikeyInstruction => Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
AppResources.YubiKeyInstruction;
@@ -83,6 +104,7 @@ namespace Bit.App.Pages
{
nameof(EmailMethod),
nameof(DuoMethod),
nameof(Fido2Method),
nameof(YubikeyMethod),
nameof(AuthenticatorMethod),
nameof(TotpMethod),
@@ -114,10 +136,7 @@ namespace Bit.App.Pages
_webVaultUrl = _environmentService.WebVaultUrl;
}
// TODO: init U2F
_u2fSupported = false;
SelectedProviderType = _authService.GetDefaultTwoFactorProvider(_u2fSupported);
SelectedProviderType = _authService.GetDefaultTwoFactorProvider(_platformUtilsService.SupportsFido2());
Load();
}
@@ -133,8 +152,15 @@ namespace Bit.App.Pages
var providerData = _authService.TwoFactorProvidersData[SelectedProviderType.Value];
switch (SelectedProviderType.Value)
{
case TwoFactorProviderType.U2f:
// TODO
case TwoFactorProviderType.Fido2WebAuthn:
if (Device.RuntimePlatform == Device.Android)
{
_messagingService.Send("listenFido2", providerData);
}
else
{
Fido2AuthenticateAsync(providerData);
}
break;
case TwoFactorProviderType.YubiKey:
_messagingService.Send("listenYubiKeyOTP", true);
@@ -169,17 +195,73 @@ namespace Bit.App.Pages
{
_messagingService.Send("listenYubiKeyOTP", false);
}
if (SelectedProviderType == null || DuoMethod)
ShowContinue = !(SelectedProviderType == null || DuoMethod || Fido2Method);
}
public async Task Fido2AuthenticateAsync(Dictionary<string, object> providerData = null)
{
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
if (providerData == null)
{
page.RemoveContinueButton();
providerData = _authService.TwoFactorProvidersData[TwoFactorProviderType.Fido2WebAuthn];
}
var callbackUri = "bitwarden://webauthn-callback";
var data = AppHelpers.EncodeDataParameter(new
{
callbackUri = callbackUri,
data = JsonConvert.SerializeObject(providerData),
btnText = AppResources.Fido2AuthenticateWebAuthn,
});
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=2";
WebAuthenticatorResult authResult = null;
try
{
var options = new WebAuthenticatorOptions
{
Url = new Uri(url),
CallbackUrl = new Uri(callbackUri),
PrefersEphemeralWebBrowserSession = true,
};
authResult = await WebAuthenticator.AuthenticateAsync(options);
}
catch (TaskCanceledException)
{
// user canceled
await _deviceActionService.HideLoadingAsync();
return;
}
string response = null;
if (authResult != null && authResult.Properties.TryGetValue("data", out var resultData))
{
response = Uri.UnescapeDataString(resultData);
}
if (!string.IsNullOrWhiteSpace(response))
{
Token = response;
await SubmitAsync(false);
}
else
{
page.AddContinueButton();
await _deviceActionService.HideLoadingAsync();
if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError))
{
await _platformUtilsService.ShowDialogAsync(resultError, AppResources.AnErrorHasOccurred);
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2SomethingWentWrong,
AppResources.AnErrorHasOccurred);
}
}
}
public async Task SubmitAsync()
public async Task SubmitAsync(bool showLoading = true)
{
if (SelectedProviderType == null)
{
@@ -206,7 +288,10 @@ namespace Bit.App.Pages
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
if (showLoading)
{
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
}
var result = await _authService.LogInTwoFactorAsync(SelectedProviderType.Value, Token, Remember);
var task = Task.Run(() => _syncService.FullSyncAsync(true));
await _deviceActionService.HideLoadingAsync();
@@ -245,7 +330,7 @@ namespace Bit.App.Pages
{
_platformUtilsService.LaunchUri("https://help.bitwarden.com/article/lost-two-step-device/");
}
else if (method != AppResources.Cancel)
else if (method != AppResources.Cancel && method != null)
{
var selected = supportedProviders.FirstOrDefault(p => p.Name == method)?.Type;
if (selected == SelectedProviderType)

View File

@@ -0,0 +1,64 @@
using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Xamarin.Essentials;
namespace Bit.App.Pages
{
public abstract class CaptchaProtectedViewModel : BaseViewModel
{
protected abstract II18nService i18nService { get; }
protected abstract IEnvironmentService environmentService { get; }
protected abstract IDeviceActionService deviceActionService { get; }
protected abstract IPlatformUtilsService platformUtilsService { get; }
protected string _captchaToken = null;
protected async Task<bool> HandleCaptchaAsync(string CaptchaSiteKey)
{
var callbackUri = "bitwarden://captcha-callback";
var data = AppHelpers.EncodeDataParameter(new
{
siteKey = CaptchaSiteKey,
locale = i18nService.Culture.TwoLetterISOLanguageName,
callbackUri = callbackUri,
captchaRequiredText = AppResources.CaptchaRequired,
});
var url = environmentService.GetWebVaultUrl() + "/captcha-mobile-connector.html?" + "data=" + data +
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=1";
WebAuthenticatorResult authResult = null;
bool cancelled = false;
try
{
var options = new WebAuthenticatorOptions
{
Url = new Uri(url),
CallbackUrl = new Uri(callbackUri),
PrefersEphemeralWebBrowserSession = true,
};
authResult = await WebAuthenticator.AuthenticateAsync(options);
}
catch (TaskCanceledException)
{
await deviceActionService.HideLoadingAsync();
cancelled = true;
}
if (cancelled == false && authResult != null &&
authResult.Properties.TryGetValue("token", out _captchaToken))
{
return true;
}
else
{
await platformUtilsService.ShowDialogAsync(AppResources.CaptchaFailed,
AppResources.CaptchaRequired);
return false;
}
}
}
}

View File

@@ -43,57 +43,53 @@
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"></Label>
<ListView x:Name="_listView"
<controls:ExtendedCollectionView
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding History}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
CachingStrategy="RecycleElement"
StyleClass="list, list-platform">
<ListView.ItemTemplate>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="domain:GeneratedPasswordHistory">
<ViewCell>
<Grid
StyleClass="list-row, list-row-platform"
Padding="10"
RowSpacing="0"
ColumnSpacing="10">
<Grid
StyleClass="list-row, list-row-platform"
Padding="10"
RowSpacing="0"
ColumnSpacing="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<controls:MonoLabel LineBreakMode="CharacterWrap"
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform"
TextType="Html"
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}" />
<Label LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}" />
<controls:FaButton
StyleClass="list-row-button, list-row-button-platform"
Text="&#xf24d;"
Command="{Binding BindingContext.CopyCommand, Source={x:Reference _page}}"
CommandParameter="{Binding .}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyPassword}" />
</Grid>
</ViewCell>
<controls:MonoLabel LineBreakMode="CharacterWrap"
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform"
TextType="Html"
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}" />
<Label LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}" />
<controls:FaButton
StyleClass="list-row-button, list-row-button-platform"
Text="&#xf0ea;"
Command="{Binding BindingContext.CopyCommand, Source={x:Reference _page}}"
CommandParameter="{Binding .}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyPassword}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</CollectionView.ItemTemplate>
</controls:ExtendedCollectionView>
</StackLayout>
</pages:BaseContentPage>

View File

@@ -15,7 +15,7 @@
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Name="_closeItem" x:Key="closeItem" />
<ToolbarItem Text="{u:I18n Select}"
Clicked="Select_Clicked"

View File

@@ -23,7 +23,7 @@
<u:InverseBoolConverter x:Key="inverseBool" />
<u:IsNullConverter x:Key="null" />
<u:IsNotNullConverter x:Key="notNull" />
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Key="closeItem" x:Name="_closeItem" />
<ToolbarItem Icon="more_vert.png" Clicked="More_Clicked" Order="Primary" x:Name="_moreItem"
x:Key="moreItem"
@@ -89,6 +89,18 @@
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<Frame
IsVisible="{Binding SendOptionsPolicyInEffect}"
Padding="10"
Margin="0, 12, 0, 0"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="Accent">
<Label
Text="{u:I18n SendOptionsPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n Name}"
@@ -105,7 +117,7 @@
</StackLayout>
<StackLayout
StyleClass="box-row"
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}">
IsVisible="{Binding ShowTypeButtons}">
<Label
Text="{u:I18n Type}"
StyleClass="box-label" />
@@ -125,8 +137,6 @@
AutomationProperties.Name="{u:I18n File}"
Grid.Column="0">
<VisualStateManager.VisualStateGroups>
<!-- Rider users, if the x:Name values below are red, it's a known issue: -->
<!-- https://youtrack.jetbrains.com/issue/RSRP-479388 -->
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
@@ -151,8 +161,6 @@
AutomationProperties.Name="{u:I18n Text}"
Grid.Column="1">
<VisualStateManager.VisualStateGroups>
<!-- Rider users, if the x:Name values below are red, it's a known issue: -->
<!-- https://youtrack.jetbrains.com/issue/RSRP-479388 -->
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
@@ -210,6 +218,7 @@
HorizontalTextAlignment="Center" />
<Button
Text="{u:I18n ChooseFile}"
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-button-row"
Clicked="ChooseFile_Clicked" />
@@ -222,7 +231,7 @@
</StackLayout>
<Label
Text="{u:I18n TypeFileInfo}"
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
IsVisible="{Binding ShowTypeButtons}"
StyleClass="box-footer-label"
Margin="0,5,0,0" />
</StackLayout>
@@ -281,24 +290,23 @@
x:Name="_btnOptions"
StyleClass="box-row-button"
TextColor="{StaticResource PrimaryColor}"
Margin="0"
Clicked="ToggleOptions_Clicked" />
Margin="0" />
<controls:FaButton
x:Name="_btnOptionsUp"
Text="&#xf077;"
StyleClass="box-row-button"
TextColor="{StaticResource PrimaryColor}"
Clicked="ToggleOptions_Clicked"
IsVisible="{Binding ShowOptions}" />
IsVisible="False" />
<controls:FaButton
x:Name="_btnOptionsDown"
Text="&#xf078;"
StyleClass="box-row-button"
TextColor="{StaticResource PrimaryColor}"
Clicked="ToggleOptions_Clicked"
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}" />
IsVisible="False" />
</StackLayout>
<StackLayout IsVisible="{Binding ShowOptions}">
<StackLayout IsVisible="True">
<StackLayout
StyleClass="box-row"
Margin="0,10,0,0">
@@ -490,6 +498,20 @@
StyleClass="box-footer-label"
Margin="0,5,0,0" />
</StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch"
Margin="0,5,0,0">
<Label
Text="{u:I18n HideEmail}"
StyleClass="box-label-regular"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Send.HideEmail}"
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
HorizontalOptions="End"
Margin="10,0,0,0" />
</StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch"
Margin="0,5,0,0">
@@ -513,4 +535,4 @@
</ResourceDictionary>
</ContentPage.Resources>
</pages:BaseContentPage>
</pages:BaseContentPage>

View File

@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
@@ -14,19 +17,24 @@ namespace Bit.App.Pages
public partial class SendAddEditPage : BaseContentPage
{
private readonly IBroadcasterService _broadcasterService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private AppOptions _appOptions;
private SendAddEditPageViewModel _vm;
public SendAddEditPage(
AppOptions appOptions = null,
string sendId = null,
SendType? type = null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as SendAddEditPageViewModel;
_vm.Page = this;
_vm.SendId = sendId;
_vm.Type = type;
_vm.Type = appOptions?.CreateSend?.Item1 ?? type;
SetActivityIndicator();
if (Device.RuntimePlatform == Device.Android)
{
@@ -74,6 +82,14 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
}
if (await _vaultTimeoutService.IsLockedAsync())
{
return;
}
await _vm.InitAsync();
_broadcasterService.Subscribe(nameof(SendAddEditPage), message =>
{
@@ -95,6 +111,7 @@ namespace Bit.App.Pages
await Navigation.PopModalAsync();
return;
}
await HandleCreateRequest();
if (!_vm.EditMode && string.IsNullOrWhiteSpace(_vm.Send?.Name))
{
RequestFocus(_nameEntry);
@@ -103,6 +120,15 @@ namespace Bit.App.Pages
});
}
protected override bool OnBackButtonPressed()
{
if (_vm.IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_appOptions.CreateSend = null;
}
return base.OnBackButtonPressed();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
@@ -271,5 +297,33 @@ namespace Bit.App.Pages
ToolbarItems.Remove(_shareLink);
}
}
private async Task HandleCreateRequest()
{
if (_appOptions?.CreateSend == null)
{
return;
}
_vm.IsAddFromShare = true;
var name = _appOptions.CreateSend.Item2;
_vm.Send.Name = name;
var type = _appOptions.CreateSend.Item1;
if (type == SendType.File)
{
_vm.FileData = _appOptions.CreateSend.Item3;
_vm.FileName = name;
FileType_Clicked(null, null);
}
else
{
var text = _appOptions.CreateSend.Item4;
_vm.Send.Text.Text = text;
TextType_Clicked(null, null);
}
_appOptions.CreateSend = null;
}
}
}

View File

@@ -23,6 +23,7 @@ namespace Bit.App.Pages
private readonly ISendService _sendService;
private bool _sendEnabled;
private bool _canAccessPremium;
private bool _emailVerified;
private SendView _send;
private string _fileName;
private bool _showOptions;
@@ -42,6 +43,8 @@ namespace Bit.App.Pages
nameof(IsText),
nameof(IsFile),
};
private bool _disableHideEmail;
private bool _sendOptionsPolicyInEffect;
public SendAddEditPageViewModel()
{
@@ -91,6 +94,8 @@ namespace Bit.App.Pages
public byte[] FileData { get; set; }
public string NewPassword { get; set; }
public bool ShareOnSave { get; set; }
public bool DisableHideEmailControl { get; set; }
public bool IsAddFromShare { get; set; }
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
@@ -194,6 +199,17 @@ namespace Bit.App.Pages
nameof(ShowPasswordIcon)
});
}
public bool DisableHideEmail
{
get => _disableHideEmail;
set => SetProperty(ref _disableHideEmail, value);
}
public bool SendOptionsPolicyInEffect
{
get => _sendOptionsPolicyInEffect;
set => SetProperty(ref _sendOptionsPolicyInEffect, value);
}
public bool ShowTypeButtons => !EditMode && !IsAddFromShare;
public bool EditMode => !string.IsNullOrWhiteSpace(SendId);
public bool IsText => Send?.Type == SendType.Text;
public bool IsFile => Send?.Type == SendType.File;
@@ -205,7 +221,10 @@ namespace Bit.App.Pages
{
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
_canAccessPremium = await _userService.CanAccessPremiumAsync();
_emailVerified = await _userService.GetEmailVerifiedAsync();
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync();
SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail;
}
public async Task<bool> LoadAsync()
@@ -228,7 +247,7 @@ namespace Bit.App.Pages
}
else
{
var defaultType = _canAccessPremium ? SendType.File : SendType.Text;
var defaultType = _canAccessPremium && _emailVerified ? SendType.File : SendType.Text;
Send = new SendView
{
Type = Type.GetValueOrDefault(defaultType),
@@ -243,6 +262,10 @@ namespace Bit.App.Pages
_isOverridingPickers = false;
}
DisableHideEmailControl = !SendEnabled ||
(!EditMode && DisableHideEmail) ||
(EditMode && DisableHideEmail && !Send.HideEmail);
return true;
}
@@ -315,7 +338,12 @@ namespace Bit.App.Pages
{
if (!_canAccessPremium)
{
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
await _platformUtilsService.ShowDialogAsync(AppResources.SendFilePremiumRequired);
return false;
}
if (!_emailVerified)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
return false;
}
if (!EditMode)
@@ -354,10 +382,6 @@ namespace Bit.App.Pages
var sendId = await _sendService.SaveWithServerAsync(send, encryptedFileData);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
await Page.Navigation.PopModalAsync();
if (Device.RuntimePlatform == Device.Android && IsFile)
{
// Workaround for https://github.com/xamarin/Xamarin.Forms/issues/5418
@@ -366,6 +390,21 @@ namespace Bit.App.Pages
_messagingService.Send("sendUpdated");
}
if (!ShareOnSave)
{
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
}
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
}
else
{
await Page.Navigation.PopModalAsync();
}
if (ShareOnSave)
{
var savedSend = await _sendService.GetAsync(sendId);
@@ -375,7 +414,7 @@ namespace Bit.App.Pages
await AppHelpers.ShareSendUrlAsync(savedSendView);
}
}
return true;
}
catch (ApiException e)
@@ -412,11 +451,37 @@ namespace Bit.App.Pages
public async Task TypeChangedAsync(SendType type)
{
if (!SendEnabled)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendDisabledWarning);
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
}
else
{
await Page.Navigation.PopModalAsync();
}
return;
}
if (Send != null)
{
if (!EditMode && type == SendType.File && !_canAccessPremium)
if (!EditMode && type == SendType.File && (!_canAccessPremium || !_emailVerified))
{
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
if (!_canAccessPremium)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendFilePremiumRequired);
}
else if (!_emailVerified)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
}
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
return;
}
type = SendType.Text;
}
Send.Type = type;

View File

@@ -44,34 +44,32 @@
<controls:SendViewCell
Send="{Binding Send}"
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
ShowOptions="{Binding ShowOptions}" />
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}" />
</DataTemplate>
<DataTemplate x:Key="sendGroupTemplate"
x:DataType="pages:SendGroupingsPageListItem">
<ViewCell>
<StackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<controls:FaLabel Text="{Binding Icon, Mode=OneWay}"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform">
<controls:FaLabel.Effects>
<effects:FixedSizeEffect />
</controls:FaLabel.Effects>
</controls:FaLabel>
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title" />
<Label Text="{Binding ItemCount, Mode=OneWay}"
HorizontalOptions="End"
VerticalOptions="CenterAndExpand"
HorizontalTextAlignment="End"
StyleClass="list-sub" />
</StackLayout>
</ViewCell>
<controls:ExtendedStackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<controls:FaLabel Text="{Binding Icon, Mode=OneWay}"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform">
<controls:FaLabel.Effects>
<effects:FixedSizeEffect />
</controls:FaLabel.Effects>
</controls:FaLabel>
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title" />
<Label Text="{Binding ItemCount, Mode=OneWay}"
HorizontalOptions="End"
VerticalOptions="CenterAndExpand"
HorizontalTextAlignment="End"
StyleClass="list-sub" />
</controls:ExtendedStackLayout>
</DataTemplate>
<pages:SendGroupingsPageListItemSelector x:Key="sendListItemDataTemplateSelector"
@@ -103,32 +101,25 @@
Text="{Binding NoDataText}"
HorizontalTextAlignment="Center" />
<Button
Text="{u:I18n AddAnItem}"
Clicked="AddButton_Clicked"
IsVisible="{Binding ShowAddSendButton}" />
Text="{u:I18n AddASend}"
Clicked="AddButton_Clicked" />
</StackLayout>
<controls:ExtendedListView
x:Name="_listView"
<RefreshView
IsVisible="{Binding ShowList}"
ItemsSource="{Binding GroupedSends}"
VerticalOptions="FillAndExpand"
HasUnevenRows="True"
RowHeight="-1"
RefreshCommand="{Binding RefreshCommand}"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding Refreshing}"
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
IsGroupingEnabled="True"
ItemSelected="RowSelected"
StyleClass="list, list-platform">
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
Command="{Binding RefreshCommand}">
<controls:ExtendedCollectionView
ItemsSource="{Binding GroupedSends}"
VerticalOptions="FillAndExpand"
ItemTemplate="{StaticResource sendListItemDataTemplateSelector}"
IsGrouped="True"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<ListView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:SendGroupingsPageListGroup">
<ViewCell>
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:SendGroupingsPageListGroup">
<StackLayout
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
StyleClass="list-row-header-container, list-row-header-container-platform">
@@ -146,10 +137,10 @@
<BoxView
StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</controls:ExtendedListView>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
</controls:ExtendedCollectionView>
</RefreshView>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>

View File

@@ -19,14 +19,14 @@ namespace Bit.App.Pages
private readonly SendGroupingsPageViewModel _vm;
private readonly string _pageName;
private PreviousPageInfo _previousPage;
private AppOptions _appOptions;
public SendGroupingsPage(bool mainPage, SendType? type = null, string pageTitle = null,
PreviousPageInfo previousPage = null)
AppOptions appOptions = null)
{
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
_pageName = string.Concat(nameof(SendGroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent();
ListView = _listView;
SetActivityIndicator(_mainContent);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
@@ -35,7 +35,7 @@ namespace Bit.App.Pages
_vm.Page = this;
_vm.MainPage = mainPage;
_vm.Type = type;
_previousPage = previousPage;
_appOptions = appOptions;
if (pageTitle != null)
{
_vm.PageTitle = pageTitle;
@@ -44,7 +44,10 @@ namespace Bit.App.Pages
if (Device.RuntimePlatform == Device.iOS)
{
_absLayout.Children.Remove(_fab);
ToolbarItems.Add(_aboutIconItem);
if (type == null)
{
ToolbarItems.Add(_aboutIconItem);
}
ToolbarItems.Add(_addItem);
}
else
@@ -55,8 +58,6 @@ namespace Bit.App.Pages
}
}
public ExtendedListView ListView { get; set; }
protected override async void OnAppearing()
{
base.OnAppearing();
@@ -109,8 +110,8 @@ namespace Bit.App.Pages
}
}
await ShowPreviousPageAsync();
AdjustToolbar();
await CheckAddRequest();
}, _mainContent);
}
@@ -122,14 +123,26 @@ namespace Bit.App.Pages
_vm.DisableRefreshing();
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
private async Task CheckAddRequest()
{
((ListView)sender).SelectedItem = null;
if (_appOptions?.CreateSend != null)
{
if (DoOnce())
{
var page = new SendAddEditPage(_appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (!(e.SelectedItem is SendGroupingsPageListItem item))
if (!(e.CurrentSelection?.FirstOrDefault() is SendGroupingsPageListItem item))
{
return;
}
@@ -172,28 +185,11 @@ namespace Bit.App.Pages
{
if (DoOnce())
{
var page = new SendAddEditPage(null, _vm.Type);
var page = new SendAddEditPage(null, null, _vm.Type);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async Task ShowPreviousPageAsync()
{
if (_previousPage == null)
{
return;
}
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.SendId))
{
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.SendId)));
}
else if (_previousPage.Page == "edit" && !string.IsNullOrWhiteSpace(_previousPage.SendId))
{
await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_previousPage.SendId)));
}
_previousPage = null;
}
private void AdjustToolbar()
{
_addItem.IsEnabled = _vm.SendEnabled;

View File

@@ -7,10 +7,10 @@ namespace Bit.App.Pages
public SendGroupingsPageListGroup(string name, int count, bool doUpper = true, bool first = false)
: this(new List<SendGroupingsPageListItem>(), name, count, doUpper, first) { }
public SendGroupingsPageListGroup(List<SendGroupingsPageListItem> groupItems, string name, int count,
public SendGroupingsPageListGroup(List<SendGroupingsPageListItem> sendGroupItems, string name, int count,
bool doUpper = true, bool first = false)
{
AddRange(groupItems);
AddRange(sendGroupItems);
if (string.IsNullOrWhiteSpace(name))
{
Name = "-";

View File

@@ -23,7 +23,6 @@ namespace Bit.App.Pages
private bool _doingLoad;
private bool _loading;
private bool _loaded;
private bool _showAddSendButton;
private bool _showNoData;
private bool _showList;
private bool _syncRefreshing;
@@ -91,11 +90,6 @@ namespace Bit.App.Pages
get => _loaded;
set => SetProperty(ref _loaded, value);
}
public bool ShowAddSendButton
{
get => _showAddSendButton;
set => SetProperty(ref _showAddSendButton, value);
}
public bool ShowNoData
{
get => _showNoData;
@@ -208,7 +202,7 @@ namespace Bit.App.Pages
public async Task SelectSendAsync(SendView send)
{
var page = new SendAddEditPage(send.Id);
var page = new SendAddEditPage(null, send.Id);
await Page.Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@@ -60,23 +60,22 @@
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center" />
<ListView x:Name="_listView"
<controls:ExtendedCollectionView
IsVisible="{Binding ShowList}"
ItemsSource="{Binding Sends}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
CachingStrategy="RecycleElement"
ItemSelected="RowSelected"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<ListView.ItemTemplate>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:SendView">
<controls:SendViewCell
Send="{Binding .}"
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</CollectionView.ItemTemplate>
</controls:ExtendedCollectionView>
</StackLayout>
</pages:BaseContentPage>

View File

@@ -1,4 +1,6 @@
using System;
using System;
using System.Linq;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Models.View;
using Xamarin.Forms;
@@ -87,15 +89,15 @@ namespace Bit.App.Pages
Navigation.PopModalAsync(false);
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ListView)sender).SelectedItem = null;
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (e.SelectedItem is SendView send)
if (e.CurrentSelection?.FirstOrDefault() is SendView send)
{
await _vm.SelectSendAsync(send);
}

View File

@@ -113,7 +113,7 @@ namespace Bit.App.Pages
public async Task SelectSendAsync(SendView send)
{
var page = new SendAddEditPage(send.Id);
var page = new SendAddEditPage(null, send.Id);
await Page.Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@@ -21,7 +21,7 @@
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" />
</ContentPage.ToolbarItems>
<ScrollView>

View File

@@ -103,11 +103,9 @@ namespace Bit.App.Pages
return;
}
var keyHash = await _cryptoService.HashPasswordAsync(_masterPassword, null);
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(_masterPassword, null);
MasterPassword = string.Empty;
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
if (storedKeyHash == null || keyHash == null || storedKeyHash != keyHash)
if (!passwordValid)
{
await _platformUtilsService.ShowDialogAsync(_i18nService.T("InvalidMasterPassword"));
return;
@@ -128,7 +126,7 @@ namespace Bit.App.Pages
fileFormat = fileFormat == "encrypted_json" ? "json" : fileFormat;
_defaultFilename = _exportService.GetFileName(null, fileFormat);
_exportResult = Encoding.ASCII.GetBytes(data);
_exportResult = Encoding.UTF8.GetBytes(data);
if (!_deviceActionService.SaveFile(_exportResult, null, _defaultFilename, null))
{

View File

@@ -13,7 +13,7 @@
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" />
<ToolbarItem Text="{u:I18n Delete}"
Clicked="Delete_Clicked"

View File

@@ -32,27 +32,25 @@
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"></Label>
<ListView x:Name="_listView"
<controls:ExtendedCollectionView
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding Folders}"
VerticalOptions="FillAndExpand"
CachingStrategy="RecycleElement"
ItemSelected="RowSelected"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<ListView.ItemTemplate>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:FolderView">
<ViewCell>
<StackLayout
StyleClass="list-row, list-row-platform"
Padding="10">
<Label LineBreakMode="TailTruncation"
StyleClass="list-title, list-title-platform"
Text="{Binding Name, Mode=OneWay}" />
</StackLayout>
</ViewCell>
<controls:ExtendedStackLayout
StyleClass="list-row, list-row-platform"
Padding="10">
<Label LineBreakMode="TailTruncation"
StyleClass="list-title, list-title-platform"
Text="{Binding Name, Mode=OneWay}" />
</controls:ExtendedStackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</CollectionView.ItemTemplate>
</controls:ExtendedCollectionView>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>

View File

@@ -1,5 +1,7 @@
using Bit.Core.Models.View;
using System;
using System.Linq;
using Bit.App.Controls;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -32,14 +34,14 @@ namespace Bit.App.Pages
}, _mainContent);
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ListView)sender).SelectedItem = null;
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (!(e.SelectedItem is FolderView folder))
if (!(e.CurrentSelection?.FirstOrDefault() is FolderView folder))
{
return;
}

View File

@@ -19,23 +19,21 @@
<DataTemplate
x:Key="regularTemplate"
x:DataType="pages:SettingsPageListItem">
<ViewCell>
<StackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
HorizontalOptions="StartAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title"/>
<Label Text="{Binding SubLabel, Mode=OneWay}"
IsVisible="{Binding SubLabel, Converter={StaticResource stringHasValue}}"
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand"
TextColor="{Binding SubLabelColor}"
StyleClass="list-sub" />
</StackLayout>
</ViewCell>
<controls:ExtendedStackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
HorizontalOptions="StartAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title"/>
<Label Text="{Binding SubLabel, Mode=OneWay}"
IsVisible="{Binding SubLabel, Converter={StaticResource stringHasValue}}"
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand"
TextColor="{Binding SubLabelColor}"
StyleClass="list-sub" />
</controls:ExtendedStackLayout>
</DataTemplate>
<pages:SettingsPageListItemSelector
@@ -44,35 +42,32 @@
</ResourceDictionary>
</ContentPage.Resources>
<ListView
<controls:ExtendedCollectionView
ItemsSource="{Binding GroupedItems}"
VerticalOptions="FillAndExpand"
HasUnevenRows="True"
RowHeight="-1"
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
IsGroupingEnabled="True"
ItemSelected="RowSelected"
IsGrouped="True"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<ListView.GroupHeaderTemplate>
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:SettingsPageListGroup">
<ViewCell>
<StackLayout
Padding="0" Spacing="0" VerticalOptions="FillAndExpand"
StyleClass="list-row-header-container, list-row-header-container-platform">
<BoxView
StyleClass="list-section-separator-top, list-section-separator-top-platform"
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
<StackLayout StyleClass="list-row-header, list-row-header-platform">
<Label
Text="{Binding Name}"
StyleClass="list-header, list-header-platform" />
</StackLayout>
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
<StackLayout
Padding="0" Spacing="0" VerticalOptions="FillAndExpand"
StyleClass="list-row-header-container, list-row-header-container-platform">
<BoxView
StyleClass="list-section-separator-top, list-section-separator-top-platform"
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
<StackLayout StyleClass="list-row-header, list-row-header-platform">
<Label
Text="{Binding Name}"
StyleClass="list-header, list-header-platform" />
</StackLayout>
</ViewCell>
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
</StackLayout>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</ListView>
</CollectionView.GroupHeaderTemplate>
</controls:ExtendedCollectionView>
</pages:BaseContentPage>

View File

@@ -1,7 +1,9 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Utilities;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Controls;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -41,14 +43,14 @@ namespace Bit.App.Pages
return base.OnBackButtonPressed();
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ListView)sender).SelectedItem = null;
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (!(e.SelectedItem is SettingsPageListItem item))
if (!(e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item))
{
return;
}
@@ -101,7 +103,7 @@ namespace Bit.App.Pages
{
await Navigation.PushModalAsync(new NavigationPage(new ExportVaultPage()));
}
else if (item.Name == AppResources.ShareVault)
else if (item.Name == AppResources.LearnOrg)
{
await _vm.ShareAsync();
}

View File

@@ -150,8 +150,8 @@ namespace Bit.App.Pages
public async Task ShareAsync()
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.ShareVaultConfirmation,
AppResources.ShareVault, AppResources.Yes, AppResources.Cancel);
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LearnOrgConfirmation,
AppResources.LearnOrg, AppResources.Yes, AppResources.Cancel);
if (confirmed)
{
_platformUtilsService.LaunchUri("https://help.bitwarden.com/article/what-is-an-organization/");
@@ -379,17 +379,23 @@ namespace Bit.App.Pages
}
var accountItems = new List<SettingsPageListItem>
{
new SettingsPageListItem { Name = AppResources.ChangeMasterPassword },
new SettingsPageListItem { Name = AppResources.FingerprintPhrase },
new SettingsPageListItem { Name = AppResources.LogOut }
};
if (IncludeLinksWithSubscriptionInfo())
{
accountItems.Insert(0, new SettingsPageListItem { Name = AppResources.ChangeMasterPassword });
}
var toolsItems = new List<SettingsPageListItem>
{
new SettingsPageListItem { Name = AppResources.ImportItems },
new SettingsPageListItem { Name = AppResources.ExportVault },
new SettingsPageListItem { Name = AppResources.ShareVault },
new SettingsPageListItem { Name = AppResources.WebVault }
new SettingsPageListItem { Name = AppResources.ExportVault }
};
if (IncludeLinksWithSubscriptionInfo())
{
toolsItems.Add(new SettingsPageListItem { Name = AppResources.LearnOrg });
toolsItems.Add(new SettingsPageListItem { Name = AppResources.WebVault });
}
var otherItems = new List<SettingsPageListItem>
{
new SettingsPageListItem { Name = AppResources.Options },
@@ -408,6 +414,15 @@ namespace Bit.App.Pages
});
}
private bool IncludeLinksWithSubscriptionInfo()
{
if (Device.RuntimePlatform == Device.iOS)
{
return false;
}
return true;
}
private string GetVaultTimeoutActionFromKey(string key)
{
return _vaultTimeoutActions.FirstOrDefault(o => o.Key == key).Value;

View File

@@ -20,7 +20,7 @@ namespace Bit.App.Pages
};
Children.Add(_groupingsPage);
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true))
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
{
Title = AppResources.Send,
IconImageSource = "paper_plane.png",
@@ -60,6 +60,10 @@ namespace Bit.App.Pages
{
appOptions.MyVaultTile = false;
}
else if (appOptions?.CreateSend != null)
{
ResetToSendPage();
}
}
public void ResetToVaultPage()
@@ -71,6 +75,11 @@ namespace Bit.App.Pages
{
CurrentPage = _generatorPage;
}
public void ResetToSendPage()
{
CurrentPage = _sendGroupingsPage;
}
protected async override void OnCurrentPageChanged()
{

View File

@@ -23,14 +23,14 @@
<u:InverseBoolConverter x:Key="inverseBool" />
<u:StringHasValueConverter x:Key="stringHasValue" />
<u:IsNotNullConverter x:Key="notNull" />
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Key="closeItem" x:Name="_closeItem" />
<ToolbarItem Text="{u:I18n Collections}"
x:Key="collectionsItem"
x:Name="_collectionsItem"
Clicked="Collections_Clicked"
Order="Secondary" />
<ToolbarItem Text="{u:I18n Share}"
<ToolbarItem Text="{u:I18n MoveToOrganization}"
x:Key="shareItem"
x:Name="_shareItem"
Clicked="Share_Clicked"
@@ -219,15 +219,40 @@
Text="{Binding Cipher.Card.CardholderName}"
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
<Grid StyleClass="box-row, box-row-input">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n Number}"
StyleClass="box-label" />
<Entry
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_cardNumberEntry"
Text="{Binding Cipher.Card.Number}"
StyleClass="box-value" />
</StackLayout>
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
Keyboard="Numeric"
IsPassword="{Binding ShowCardNumber, Converter={StaticResource inverseBool}}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowCardNumberIcon}"
Command="{Binding ToggleCardNumberCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout StyleClass="box-row, box-row-input">
<Label
Text="{u:I18n Brand}"
@@ -530,6 +555,24 @@
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n PasswordPrompt}"
StyleClass="box-label-regular" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf29c;"
Command="{Binding PasswordPromptHelpCommand}"
TextColor="{StaticResource MutedColor}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding PasswordPrompt}"
Toggled="PasswordPrompt_Toggled"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
<StackLayout StyleClass="box">
@@ -667,7 +710,10 @@
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Collections}" IsVisible="{Binding HasCollections}">
<controls:RepeaterView
x:Name="_collectionsRepeaterView"
ItemsSource="{Binding Collections}"
IsVisible="{Binding HasCollections}">
<controls:RepeaterView.ItemTemplate>
<DataTemplate x:DataType="pages:CollectionViewModel">
<StackLayout Spacing="0" Padding="0">

View File

@@ -20,6 +20,7 @@ namespace Bit.App.Pages
private readonly AppOptions _appOptions;
private readonly IStorageService _storageService;
private readonly IDeviceActionService _deviceActionService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private AddEditPageViewModel _vm;
private bool _fromAutofill;
@@ -38,6 +39,7 @@ namespace Bit.App.Pages
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_appOptions = appOptions;
_fromAutofill = fromAutofill;
FromAutofillFramework = _appOptions?.FromAutofillFramework ?? false;
@@ -47,6 +49,7 @@ namespace Bit.App.Pages
_vm.CipherId = cipherId;
_vm.FolderId = folderId == "none" ? null : folderId;
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
_vm.CollectionsRepeaterView = _collectionsRepeaterView;
_vm.Type = type;
_vm.DefaultName = name ?? appOptions?.SaveName;
_vm.DefaultUri = uri ?? appOptions?.Uri;
@@ -144,6 +147,14 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
}
if (await _vaultTimeoutService.IsLockedAsync())
{
return;
}
await LoadOnAppearedAsync(_scrollView, true, async () =>
{
var success = await _vm.LoadAsync(_appOptions);
@@ -158,6 +169,7 @@ namespace Bit.App.Pages
{
RequestFocus(_nameEntry);
}
_scrollView.Scrolled += (sender, args) => _vm.HandleScroll();
});
}
@@ -272,7 +284,7 @@ namespace Bit.App.Pages
var options = new List<string> { AppResources.Attachments };
if (_vm.EditMode)
{
options.Add(_vm.Cipher.OrganizationId == null ? AppResources.Share : AppResources.Collections);
options.Add(_vm.Cipher.OrganizationId == null ? AppResources.MoveToOrganization : AppResources.Collections);
}
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
(_vm.EditMode && !_vm.CloneMode) ? AppResources.Delete : null, options.ToArray());
@@ -293,7 +305,7 @@ namespace Bit.App.Pages
var page = new CollectionsPage(_vm.CipherId);
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
else if (selection == AppResources.Share)
else if (selection == AppResources.MoveToOrganization)
{
var page = new SharePage(_vm.CipherId);
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
@@ -376,5 +388,10 @@ namespace Bit.App.Pages
}
}
}
private void PasswordPrompt_Toggled(object sender, ToggledEventArgs e)
{
_vm.Cipher.Reprompt = e.Value ? CipherRepromptType.Password : CipherRepromptType.None;
}
}
}

View File

@@ -1,4 +1,5 @@
using Bit.App.Abstractions;
using System;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
@@ -9,6 +10,7 @@ using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Controls;
using Xamarin.Forms;
using View = Xamarin.Forms.View;
@@ -29,6 +31,7 @@ namespace Bit.App.Pages
private CipherView _cipher;
private bool _showNotesSeparator;
private bool _showPassword;
private bool _showCardNumber;
private bool _showCardCode;
private int _typeSelectedIndex;
private int _cardBrandSelectedIndex;
@@ -38,6 +41,7 @@ namespace Bit.App.Pages
private int _ownershipSelectedIndex;
private bool _hasCollections;
private string _previousCipherId;
private DateTime _lastHandledScrollTime;
private List<Core.Models.View.CollectionView> _writeableCollections;
private string[] _additionalCipherProperties = new string[]
{
@@ -82,10 +86,12 @@ namespace Bit.App.Pages
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode);
CheckPasswordCommand = new Command(CheckPasswordAsync);
UriOptionsCommand = new Command<LoginUriView>(UriOptions);
FieldOptionsCommand = new Command<AddEditPageFieldViewModel>(FieldOptions);
PasswordPromptHelpCommand = new Command(PasswordPromptHelp);
Uris = new ExtendedObservableCollection<LoginUriView>();
Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>();
Collections = new ExtendedObservableCollection<CollectionViewModel>();
@@ -141,10 +147,12 @@ namespace Bit.App.Pages
public Command GeneratePasswordCommand { get; set; }
public Command TogglePasswordCommand { get; set; }
public Command ToggleCardNumberCommand { get; set; }
public Command ToggleCardCodeCommand { get; set; }
public Command CheckPasswordCommand { get; set; }
public Command UriOptionsCommand { get; set; }
public Command FieldOptionsCommand { get; set; }
public Command PasswordPromptHelpCommand { get; set; }
public string CipherId { get; set; }
public string OrganizationId { get; set; }
public string FolderId { get; set; }
@@ -161,6 +169,7 @@ namespace Bit.App.Pages
public ExtendedObservableCollection<LoginUriView> Uris { get; set; }
public ExtendedObservableCollection<AddEditPageFieldViewModel> Fields { get; set; }
public ExtendedObservableCollection<CollectionViewModel> Collections { get; set; }
public RepeaterView CollectionsRepeaterView { get; set; }
public int TypeSelectedIndex
{
get => _typeSelectedIndex;
@@ -246,6 +255,15 @@ namespace Bit.App.Pages
nameof(ShowPasswordIcon)
});
}
public bool ShowCardNumber
{
get => _showCardNumber;
set => SetProperty(ref _showCardNumber, value,
additionalPropertyNames: new string[]
{
nameof(ShowCardNumberIcon)
});
}
public bool ShowCardCode
{
get => _showCardCode;
@@ -277,10 +295,12 @@ namespace Bit.App.Pages
public bool ShowUris => IsLogin && Cipher.Login.HasUris;
public bool ShowAttachments => Cipher.HasAttachments;
public string ShowPasswordIcon => ShowPassword ? "" : "";
public string ShowCardNumberIcon => ShowCardNumber ? "" : "";
public string ShowCardCodeIcon => ShowCardCode ? "" : "";
public int PasswordFieldColSpan => Cipher.ViewPassword ? 1 : 4;
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
public bool AllowPersonal { get; set; }
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
public void Init()
{
@@ -691,6 +711,16 @@ namespace Bit.App.Pages
}
}
public void ToggleCardNumber()
{
ShowCardNumber = !ShowCardNumber;
if (EditMode && ShowCardNumber)
{
var task = _eventService.CollectAsync(
Core.Enums.EventType.Cipher_ClientToggledCardNumberVisible, CipherId);
}
}
public void ToggleCardCode()
{
ShowCardCode = !ShowCardCode;
@@ -717,6 +747,11 @@ namespace Bit.App.Pages
}
}
public void PasswordPromptHelp()
{
_platformUtilsService.LaunchUri("https://bitwarden.com/help/article/managing-items/#protect-individual-items");
}
private void TypeChanged()
{
if (Cipher != null && TypeSelectedIndex > -1)
@@ -769,13 +804,30 @@ namespace Bit.App.Pages
{
var cols = _writeableCollections.Where(c => c.OrganizationId == Cipher.OrganizationId)
.Select(c => new CollectionViewModel { Collection = c }).ToList();
HasCollections = cols.Any();
Collections.ResetWithRange(cols);
Collections = new ExtendedObservableCollection<CollectionViewModel>(cols);
}
else
{
HasCollections = false;
Collections.ResetWithRange(new List<CollectionViewModel>());
Collections = new ExtendedObservableCollection<CollectionViewModel>(new List<CollectionViewModel>());
}
HasCollections = Collections.Any();
}
public void HandleScroll()
{
// workaround for https://github.com/xamarin/Xamarin.Forms/issues/13607
// required for org ownership/collections to render properly in XF4.5+
if (!HasCollections ||
EditMode ||
(DateTime.Now - _lastHandledScrollTime < TimeSpan.FromMilliseconds(200)))
{
return;
}
CollectionsRepeaterView.ItemsSource = Collections;
_lastHandledScrollTime = DateTime.Now;
}
private void TriggerCipherChanged()

View File

@@ -47,40 +47,35 @@
Clicked="AddButton_Clicked"></Button>
</StackLayout>
<controls:ExtendedListView
<controls:ExtendedCollectionView
IsVisible="{Binding ShowList}"
ItemsSource="{Binding GroupedItems}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
IsGroupingEnabled="True"
ItemSelected="RowSelected"
IsGrouped="True"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
<ListView.GroupHeaderTemplate>
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
<ViewCell>
<StackLayout
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
StyleClass="list-row-header-container, list-row-header-container-platform">
<BoxView
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
<StackLayout StyleClass="list-row-header, list-row-header-platform">
<Label
Text="{Binding Name}"
StyleClass="list-header, list-header-platform" />
<Label
Text="{Binding ItemCount}"
StyleClass="list-header-sub" />
</StackLayout>
<StackLayout
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
StyleClass="list-row-header-container, list-row-header-container-platform">
<BoxView
StyleClass="list-section-separator-top, list-section-separator-top-platform" />
<StackLayout StyleClass="list-row-header, list-row-header-platform">
<Label
Text="{Binding Name}"
StyleClass="list-header, list-header-platform" />
<Label
Text="{Binding ItemCount}"
StyleClass="list-header-sub" />
</StackLayout>
</ViewCell>
</StackLayout>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</controls:ExtendedListView>
</CollectionView.GroupHeaderTemplate>
</controls:ExtendedCollectionView>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>

View File

@@ -4,7 +4,10 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -13,6 +16,7 @@ namespace Bit.App.Pages
{
private readonly AppOptions _appOptions;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private AutofillCiphersPageViewModel _vm;
@@ -25,11 +29,20 @@ namespace Bit.App.Pages
_vm.Init(appOptions);
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
}
protected async override void OnAppearing()
{
base.OnAppearing();
if (!await AppHelpers.IsVaultTimeoutImmediateAsync())
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
}
if (await _vaultTimeoutService.IsLockedAsync())
{
return;
}
await LoadOnAppearedAsync(_mainLayout, false, async () =>
{
try
@@ -44,14 +57,23 @@ namespace Bit.App.Pages
}, _mainContent);
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
protected override bool OnBackButtonPressed()
{
((ListView)sender).SelectedItem = null;
if (Device.RuntimePlatform == Device.Android)
{
_appOptions.Uri = null;
}
return base.OnBackButtonPressed();
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (e.SelectedItem is GroupingsPageListItem item && item.Cipher != null)
if (e.CurrentSelection?.FirstOrDefault() is GroupingsPageListItem item && item.Cipher != null)
{
await _vm.SelectCipherAsync(item.Cipher, item.FuzzyAutofill);
}

View File

@@ -23,6 +23,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly ICipherService _cipherService;
private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService;
private AppOptions _appOptions;
private bool _showList;
@@ -35,6 +36,7 @@ namespace Bit.App.Pages
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
GroupedItems = new ExtendedObservableCollection<GroupingsPageListGroup>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
@@ -118,10 +120,14 @@ namespace Bit.App.Pages
}
if (_deviceActionService.SystemMajorVersion() < 21)
{
await AppHelpers.CipherListOptions(Page, cipher);
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
}
else
{
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}
var autofillResponse = AppResources.Yes;
if (fuzzy)
{
@@ -175,7 +181,7 @@ namespace Bit.App.Pages
{
if ((Page as BaseContentPage).DoOnce())
{
await AppHelpers.CipherListOptions(Page, cipher);
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
}
}
}

View File

@@ -60,15 +60,14 @@
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center" />
<ListView x:Name="_listView"
<controls:ExtendedCollectionView
IsVisible="{Binding ShowList}"
ItemsSource="{Binding Ciphers}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
CachingStrategy="RecycleElement"
ItemSelected="RowSelected"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<ListView.ItemTemplate>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="views:CipherView">
<controls:CipherViewCell
Cipher="{Binding .}"
@@ -76,8 +75,8 @@
WebsiteIconsEnabled="{Binding BindingContext.WebsiteIconsEnabled, Source={x:Reference _page}}"
/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</CollectionView.ItemTemplate>
</controls:ExtendedCollectionView>
</StackLayout>
</pages:BaseContentPage>

View File

@@ -3,6 +3,8 @@ using Bit.App.Resources;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using System;
using System.Linq;
using Bit.App.Controls;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -119,15 +121,15 @@ namespace Bit.App.Pages
}
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ListView)sender).SelectedItem = null;
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (e.SelectedItem is CipherView cipher)
if (e.CurrentSelection?.FirstOrDefault() is CipherView cipher)
{
await _vm.SelectCipherAsync(cipher);
}

View File

@@ -22,6 +22,7 @@ namespace Bit.App.Pages
private readonly ISearchService _searchService;
private readonly IDeviceActionService _deviceActionService;
private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService;
private CancellationTokenSource _searchCancellationTokenSource;
private bool _showNoData;
@@ -35,6 +36,7 @@ namespace Bit.App.Pages
_searchService = ServiceContainer.Resolve<ISearchService>("searchService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
Ciphers = new ExtendedObservableCollection<CipherView>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
@@ -182,7 +184,7 @@ namespace Bit.App.Pages
}
if (_deviceActionService.SystemMajorVersion() < 21)
{
await Utilities.AppHelpers.CipherListOptions(Page, cipher);
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
}
else
{
@@ -195,7 +197,7 @@ namespace Bit.App.Pages
{
if ((Page as BaseContentPage).DoOnce())
{
await Utilities.AppHelpers.CipherListOptions(Page, cipher);
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
}
}
}

View File

@@ -14,7 +14,7 @@
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" />
</ContentPage.ToolbarItems>

View File

@@ -1,14 +1,14 @@
<?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.GroupingsPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:DataType="pages:GroupingsPageViewModel"
Title="{Binding PageTitle}"
x:Name="_page">
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.GroupingsPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:effects="clr-namespace:Bit.App.Effects"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:DataType="pages:GroupingsPageViewModel"
Title="{Binding PageTitle}"
x:Name="_page">
<ContentPage.BindingContext>
<pages:GroupingsPageViewModel />
@@ -45,29 +45,27 @@
<DataTemplate x:Key="groupTemplate"
x:DataType="pages:GroupingsPageListItem">
<ViewCell>
<StackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<controls:FaLabel Text="{Binding Icon, Mode=OneWay}"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform">
<controls:FaLabel.Effects>
<effects:FixedSizeEffect />
</controls:FaLabel.Effects>
</controls:FaLabel>
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title"/>
<Label Text="{Binding ItemCount, Mode=OneWay}"
HorizontalOptions="End"
VerticalOptions="CenterAndExpand"
HorizontalTextAlignment="End"
StyleClass="list-sub"/>
</StackLayout>
</ViewCell>
<controls:ExtendedStackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<controls:FaLabel Text="{Binding Icon, Mode=OneWay}"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform">
<controls:FaLabel.Effects>
<effects:FixedSizeEffect />
</controls:FaLabel.Effects>
</controls:FaLabel>
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title"/>
<Label Text="{Binding ItemCount, Mode=OneWay}"
HorizontalOptions="End"
VerticalOptions="CenterAndExpand"
HorizontalTextAlignment="End"
StyleClass="list-sub"/>
</controls:ExtendedStackLayout>
</DataTemplate>
<pages:GroupingsPageListItemSelector x:Key="listItemDataTemplateSelector"
@@ -89,27 +87,21 @@
IsVisible="{Binding ShowAddCipherButton}"></Button>
</StackLayout>
<controls:ExtendedListView
x:Name="_listView"
<RefreshView
IsVisible="{Binding ShowList}"
ItemsSource="{Binding GroupedItems}"
VerticalOptions="FillAndExpand"
HasUnevenRows="True"
RowHeight="-1"
RefreshCommand="{Binding RefreshCommand}"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding Refreshing}"
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
IsGroupingEnabled="True"
ItemSelected="RowSelected"
StyleClass="list, list-platform">
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
Command="{Binding RefreshCommand}">
<controls:ExtendedCollectionView
ItemsSource="{Binding GroupedItems}"
VerticalOptions="FillAndExpand"
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
IsGrouped="True"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform">
<ListView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
<ViewCell>
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
<StackLayout
Spacing="0" Padding="0" VerticalOptions="FillAndExpand"
StyleClass="list-row-header-container, list-row-header-container-platform">
@@ -126,10 +118,10 @@
</StackLayout>
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</controls:ExtendedListView>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
</controls:ExtendedCollectionView>
</RefreshView>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>

View File

@@ -1,5 +1,4 @@
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core;
@@ -9,6 +8,7 @@ using Bit.Core.Utilities;
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Controls;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -33,7 +33,6 @@ namespace Bit.App.Pages
{
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent();
ListView = _listView;
SetActivityIndicator(_mainContent);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
@@ -73,8 +72,6 @@ namespace Bit.App.Pages
}
}
public ExtendedListView ListView { get; set; }
protected async override void OnAppearing()
{
base.OnAppearing();
@@ -197,14 +194,14 @@ namespace Bit.App.Pages
_vm.DisableRefreshing();
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
private async void RowSelected(object sender, SelectionChangedEventArgs e)
{
((ListView)sender).SelectedItem = null;
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (!(e.SelectedItem is GroupingsPageListItem item))
if (!(e.CurrentSelection?.FirstOrDefault() is GroupingsPageListItem item))
{
return;
}

View File

@@ -75,30 +75,30 @@ namespace Bit.App.Pages
}
else if (Folder != null)
{
_icon = Folder.Id == null ? "" : "";
_icon = Folder.Id == null ? "\uf115" : "\uf07c"; // fa-folder-open-o : fa-folder-open
}
else if (Collection != null)
{
_icon = "";
_icon = "\uf1b2"; // fa-cube
}
else if (Type != null)
{
switch (Type.Value)
{
case CipherType.Login:
_icon = "";
_icon = "\uf0ac"; // fa-globe
break;
case CipherType.SecureNote:
_icon = "";
_icon = "\uf24a"; // fa-sticky-note-o
break;
case CipherType.Card:
_icon = "";
_icon = "\uf09d"; // fa-credit-card
break;
case CipherType.Identity:
_icon = "";
_icon = "\uf2c3"; // fa-id-card-o
break;
default:
_icon = "";
_icon = "\uf0ac"; // fa-globe
break;
}
}

View File

@@ -46,6 +46,7 @@ namespace Bit.App.Pages
private readonly IMessagingService _messagingService;
private readonly IStateService _stateService;
private readonly IStorageService _storageService;
private readonly IPasswordRepromptService _passwordRepromptService;
public GroupingsPageViewModel()
{
@@ -60,6 +61,7 @@ namespace Bit.App.Pages
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
Loading = true;
PageTitle = AppResources.MyVault;
@@ -514,7 +516,7 @@ namespace Bit.App.Pages
{
if ((Page as BaseContentPage).DoOnce())
{
await AppHelpers.CipherListOptions(Page, cipher);
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
}
}
}

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