1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

...

223 Commits

Author SHA1 Message Date
Kyle Spearrin
59d5314164 New Crowdin translations (#639)
* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Hebrew)

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

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Vietnamese)
2019-10-23 20:55:51 -04:00
Kyle Spearrin
9c08a37772 UseNativeBiometric only for SDK 29 2019-10-23 11:54:53 -04:00
Kyle Spearrin
b13f5356fe FingerprintManager to detect fingerprints on SDK 28 2019-10-23 11:26:00 -04:00
Kyle Spearrin
5f0c9725ce bump version 2019-10-23 09:27:31 -04:00
Kyle Spearrin
f951fea555 use bio strings for native android bio 2019-10-23 09:24:34 -04:00
Kyle Spearrin
4b989b01e9 use native biomatrics on Android 2019-10-23 09:11:48 -04:00
Kyle Spearrin
aed3ec5474 check authed and unlocked before trying to load 2019-10-22 16:42:05 -04:00
Kyle Spearrin
b354986199 null check apiexception error 2019-10-22 16:37:40 -04:00
Kyle Spearrin
e1983a7d66 fix error when login token expires 2019-10-22 16:30:28 -04:00
Kyle Spearrin
0400d79f43 android 10 and bio permission 2019-10-18 20:41:04 -04:00
Kyle Spearrin
c911484632 upgrade builds to vs 2019 2019-10-18 14:21:07 -04:00
Kyle Spearrin
713e441d2e upgrade to android 10 sdk 2019-10-18 14:19:56 -04:00
Kyle Spearrin
d4b577732b npm audit fix 2019-10-17 08:01:50 -04:00
Kyle Spearrin
440a410d7f skip com.treydev.pns 2019-10-17 08:00:58 -04:00
Kyle Spearrin
37a536b138 catch thrown sync errors from ui 2019-10-15 11:05:56 -04:00
Kyle Spearrin
a0aca3e837 add tf browser 2019-10-11 09:29:33 -04:00
Kyle Spearrin
b58c29111a bump version 2019-10-07 09:42:55 -04:00
Kyle Spearrin
b0f86ea161 focus search bar on appear 2019-10-07 09:23:41 -04:00
Kyle Spearrin
93b59a75a4 remove migration code 2019-10-05 21:36:47 -04:00
Kyle Spearrin
54fcabaea6 shorter delays 2019-10-05 21:31:55 -04:00
Kyle Spearrin
0e966c0304 fix min character assignments for pw gen 2019-10-05 20:39:42 -04:00
Kyle Spearrin
a363712127 use black text for search bar on light theme 2019-10-04 09:32:59 -04:00
Kyle Spearrin
4d8c665917 fix light theme 2019-10-04 09:11:14 -04:00
kspearrin
53bdd92e72 support dark theme default on extensions 2019-09-30 21:40:05 -04:00
Kyle Spearrin
e51aa39ede New Crowdin translations (#614)
* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Croatian)

* New translations copy.resx (German)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Dutch)

* New translations copy.resx (Dutch)

* New translations copy.resx (Dutch)
2019-09-30 21:35:24 -04:00
Kyle Spearrin
33c82129ff bump version 2019-09-30 21:19:20 -04:00
kspearrin
7c5b8c0e9f modal full screen 2019-09-30 21:17:53 -04:00
kspearrin
9dc01bca1c detect dark mode theme. set modal to full screen 2019-09-30 20:38:22 -04:00
Kyle Spearrin
3c7920b84c XF 3.6 update 2019-09-30 20:33:54 -04:00
Kyle Spearrin
b92f3abbaf support dark theme logos 2019-09-30 16:52:20 -04:00
Kyle Spearrin
b6747a63ed stub out support for dark theme by default 2019-09-30 16:41:31 -04:00
Kyle Spearrin
41a44548d2 use TextColor for search bar text color 2019-09-30 16:33:53 -04:00
Kyle Spearrin
a79d3a0d7c uisearchbar tint color 2019-09-30 16:28:07 -04:00
kspearrin
f3a17709e5 get proper hex string from nsdata token 2019-09-30 16:24:35 -04:00
Kyle Spearrin
ced9d33d2e memory stored pinProtectedKey 2019-09-20 16:43:03 -04:00
Kyle Spearrin
23b1373f80 add tag to entitlement 2019-09-20 08:17:37 -04:00
Kyle Spearrin
a80eb1f533 bump version 2019-09-20 07:56:49 -04:00
Kyle Spearrin
f657edf195 add support for vivaldi browser. resolves #599 2019-09-10 17:21:11 -04:00
Kyle Spearrin
d34279dca5 more null checks on add/edit save 2019-09-06 10:03:11 -04:00
Kyle Spearrin
954aa1112a added locale names dictionary 2019-09-06 09:44:25 -04:00
Kyle Spearrin
b35a3339cb device type should be int 2019-09-06 09:44:25 -04:00
Kyle Spearrin
b59433debd New Crowdin translations (#594)
* New translations copy.resx (Bulgarian)

* New translations copy.resx (Bulgarian)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Turkish)
2019-09-04 13:10:18 -04:00
Kyle Spearrin
fb2db9c652 version bump 2019-09-04 13:02:00 -04:00
Kyle Spearrin
2507f3301b device user agent 2019-09-04 11:52:32 -04:00
Kyle Spearrin
bdad5e4f0a fixes to opening file types on android 2019-08-30 16:43:58 -04:00
Kyle Spearrin
b5dcdc74d7 dark keyboard on dark themes. resolves #588 2019-08-28 20:27:15 -04:00
Kyle Spearrin
e2d1da02d3 more null checking on save 2019-08-27 15:03:33 -04:00
Kyle Spearrin
8253f18312 null check cipher service 2019-08-27 14:55:15 -04:00
kenjirooo
f4a98a2031 Added support for Sleipnir Mobile (https://play.google.com/store/apps/details?id=jp.co.fenrir.android.sleipnir). (#583) 2019-08-22 08:00:03 -04:00
Kyle Spearrin
224845cfd3 bump version 2019-08-12 09:41:12 -04:00
Kyle Spearrin
fc8c2ad67a add back references for autofill projects 2019-08-12 09:36:10 -04:00
Kyle Spearrin
c9d6f58563 delay for 1s on SyncIfNeeded 2019-08-12 09:35:18 -04:00
Kyle Spearrin
325b557506 null check on SyncIfNeeded 2019-08-12 08:51:49 -04:00
Kyle Spearrin
ce751cfc87 fix unlock logic 2019-08-12 08:05:45 -04:00
Kyle Spearrin
0f451fd4b9 set FingerprintUnlockKey before setKey 2019-08-10 00:19:01 -04:00
Kyle Spearrin
b7819838b8 parse fallback package id from first node title 2019-08-01 16:39:01 -04:00
Kyle Spearrin
67c6cf6b8c load previous view/edit page after lock 2019-07-31 16:50:16 -04:00
Kyle Spearrin
d91d71333b LastClipboardValue using static store rather than state 2019-07-31 11:21:07 -04:00
Kyle Spearrin
431804ea80 loop on reset with range instead of clear 2019-07-29 22:35:53 -04:00
Kyle Spearrin
7a7ab7bd0e New Crowdin translations (#570)
* New translations AppResources.resx (French)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Hungarian)

* New translations copy.resx (Hungarian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Russian)
2019-07-29 15:15:21 -04:00
Kyle Spearrin
b8cdee383b bump version 2019-07-27 12:42:42 -04:00
Kyle Spearrin
580fa02ee1 enable event logging 2019-07-27 12:41:38 -04:00
Kyle Spearrin
421834153d catch InteractionNotAllowed 2019-07-27 12:39:59 -04:00
Kyle Spearrin
011f04e1dc editorconfig 2019-07-26 11:44:43 -04:00
Kyle Spearrin
3d8056704c yubikey token entry is password field 2019-07-25 16:10:46 -04:00
Kyle Spearrin
41263f3419 New Crowdin translations (#569)
* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Polish)
2019-07-24 10:48:49 -04:00
Kyle Spearrin
9fe9210cb7 null check on id 2019-07-24 10:42:13 -04:00
Kyle Spearrin
2272b10820 null check on cipher when autofilling 2019-07-24 10:40:12 -04:00
Kyle Spearrin
9d6fc73fcc New Crowdin translations (#568)
* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (German)

* New translations AppResources.resx (French)

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

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Ukrainian)
2019-07-23 09:30:01 -04:00
Kyle Spearrin
73ecd67b20 New Crowdin translations (#567)
* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (German)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Estonian)
2019-07-23 09:16:10 -04:00
Kyle Spearrin
e5ce3dbd32 visibility typo 2019-07-23 09:11:01 -04:00
Kyle Spearrin
a0a5e30f48 re-work hockeyapp init 2019-07-23 09:08:16 -04:00
Kyle Spearrin
0eddee5816 check $LastExitCode 2019-07-23 00:10:25 -04:00
Kyle Spearrin
6d2dcb73ae event service func 2019-07-23 00:06:34 -04:00
Kyle Spearrin
0d6cc91b67 don't allow device PIN fallback 2019-07-22 23:34:39 -04:00
Kyle Spearrin
ae52922698 version bump 2019-07-22 23:07:00 -04:00
Kyle Spearrin
236496e69f formatting 2019-07-22 21:37:56 -04:00
kspearrin
fe5cdb0004 * CredentialProviderViewController.cs:
* LoadingViewController.cs: reset after using event service
2019-07-22 21:35:05 -04:00
Kyle Spearrin
f9547f158e log autofill events 2019-07-22 15:50:59 -04:00
Kyle Spearrin
0c75374c0f New Crowdin translations (#565)
* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (German)

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

* New translations AppResources.resx (French)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Estonian)
2019-07-22 14:53:12 -04:00
Kyle Spearrin
0b249d4dd4 check yubico token length == 44 2019-07-22 13:59:12 -04:00
Kyle Spearrin
a2bac9d368 a11y labeling 2019-07-22 12:04:17 -04:00
Kyle Spearrin
392e429dfd more options button for ios on folder add/edit 2019-07-22 10:52:19 -04:00
Kyle Spearrin
50623b9b29 fix no folder reference 2019-07-22 10:52:04 -04:00
Kyle Spearrin
e7ce050324 use fingerprintButtonText on extension unlock 2019-07-22 10:24:18 -04:00
Kyle Spearrin
762b574d49 rebuild same fingerprintName string 2019-07-22 10:12:14 -04:00
Kyle Spearrin
d73bf6d225 assign new messageCallback when subbing 2019-07-22 09:21:00 -04:00
Kyle Spearrin
c2108fdda0 null checking 2019-07-22 08:44:55 -04:00
Kyle Spearrin
2062a284e3 fix lock checks on sleep of app 2019-07-22 08:37:06 -04:00
Kyle Spearrin
9164c9b946 InitAppIfNeeded 2019-07-22 08:22:02 -04:00
Kyle Spearrin
13ddd10c40 update autofill identities if needed 2019-07-22 07:09:51 -04:00
Kyle Spearrin
e407acd2a7 bump version 2019-07-13 20:28:20 -04:00
Kyle Spearrin
11cdf52ec8 disable events funcionality for now 2019-07-13 20:27:15 -04:00
Kyle Spearrin
40a3541e8e handle sleep event on ios manually 2019-07-13 20:25:31 -04:00
Kyle Spearrin
7da13e22ad process event uploads on ios 2019-07-12 20:56:54 -04:00
Kyle Spearrin
38d702b6fe log some events 2019-07-12 17:29:40 -04:00
Kyle Spearrin
df2af5459e register event service with container 2019-07-12 15:51:47 -04:00
Kyle Spearrin
40d68b1654 add event service 2019-07-11 09:30:25 -04:00
Kyle Spearrin
a240a4ac66 get autofill web scheme for android p devices 2019-07-11 08:55:40 -04:00
Kyle Spearrin
416ec3812d iphone x screenshots 2019-07-10 14:17:28 -04:00
Kyle Spearrin
ff24891903 update event log types 2019-07-09 10:51:33 -04:00
Kyle Spearrin
ddcbe298ac 6.5 inch screenshots for ios 2019-07-09 09:48:27 -04:00
Kyle Spearrin
6d8f647aee two new screenshots for iphone, update 1 2019-07-09 01:09:53 -04:00
Kyle Spearrin
a654987175 upadte screenshots for 5.5 inch iphone 6 2019-07-08 17:34:04 -04:00
Kyle Spearrin
a5f960d8a1 show proper name for faceid. homepage margin on ios 2019-07-08 13:37:45 -04:00
Kyle Spearrin
1f707cda68 New Crowdin translations (#556)
* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (French)
2019-07-08 13:12:09 -04:00
Kyle Spearrin
4e7f195fd2 bump versions 2019-07-08 13:02:48 -04:00
Kyle Spearrin
7728e930be delay focus on entry on ios too 2019-07-08 12:06:37 -04:00
Kyle Spearrin
ab84200347 change default lock on new installs to 15 minutes 2019-07-06 23:04:10 -04:00
Kyle Spearrin
62d8824450 clear cache in main app if change made in extension 2019-07-06 22:49:17 -04:00
Kyle Spearrin
cf35d20adb refresh search on appear if has text 2019-07-06 22:19:29 -04:00
Kyle Spearrin
65725b5a38 yubikey token must be > 40 in length too 2019-07-06 22:09:20 -04:00
Kyle Spearrin
eca4777b99 yubikey fixes for ios 2019-07-06 21:59:13 -04:00
Kyle Spearrin
066b3aba5b wait 5 seconds after migration 2019-07-05 17:37:21 -04:00
Kyle Spearrin
8e485ff26f add back refs for extensions 2019-07-05 17:14:08 -04:00
Kyle Spearrin
341b66f44f settings shim with ios group id 2019-07-05 17:10:37 -04:00
Kyle Spearrin
19c62d3320 Merge branch 'master' of github.com:bitwarden/mobile 2019-07-05 16:37:12 -04:00
Kyle Spearrin
13ffbd7675 add app extension flags to migration 2019-07-05 16:36:56 -04:00
Kyle Spearrin
9af6aae699 fix baseurl check when return web vault url 2019-07-05 13:46:54 -04:00
Kyle Spearrin
2e562e8318 ios migration from old version 2019-07-05 13:35:22 -04:00
Kyle Spearrin
c6db763716 New Crowdin translations (#554)
* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (French)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Ukrainian)
2019-07-04 08:43:40 -04:00
Kyle Spearrin
a3383af4ae hides shadow on nav bar 2019-07-03 20:39:00 -04:00
Kyle Spearrin
6c56e44b61 prefix keychain key with appid 2019-07-03 20:04:23 -04:00
Kyle Spearrin
64506a7080 UINavigationBar appearance for no bottom line 2019-07-03 17:37:33 -04:00
Kyle Spearrin
fac9ae4b6c only init hockeyapp once 2019-07-03 16:50:12 -04:00
Kyle Spearrin
a2dc73afef New Crowdin translations (#553)
* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Estonian)

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

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Thai)
2019-07-03 15:15:21 -04:00
Kyle Spearrin
59c5a34cd0 adjust bundle signing config 2019-07-03 15:03:09 -04:00
Kyle Spearrin
3321e6b0e2 set entitlements 2019-07-03 14:54:12 -04:00
Kyle Spearrin
8b7ac179fa in memory storage service 2019-07-03 12:31:18 -04:00
Kyle Spearrin
ea745665c8 remove continue button if no 2fa available 2019-07-03 12:21:06 -04:00
Kyle Spearrin
ca8f6ee10b FFImageLoading only for main app 2019-07-03 10:08:59 -04:00
Kyle Spearrin
3e51ff46f3 forget bg colors :( 2019-07-02 23:31:52 -04:00
Kyle Spearrin
fa2e814559 style headers and footer 2019-07-02 22:27:21 -04:00
Kyle Spearrin
87e337cbeb try color header bg again 2019-07-02 20:52:44 -04:00
Kyle Spearrin
abb39df547 reset service container from extensions 2019-07-02 20:45:54 -04:00
Kyle Spearrin
43e15bf911 more bg color for header 2019-07-02 20:14:23 -04:00
Kyle Spearrin
4d79d0af89 bg for header/footer is tint color 2019-07-02 19:49:50 -04:00
Kyle Spearrin
69100d7db5 search bar bg is same as list header 2019-07-02 19:36:11 -04:00
Kyle Spearrin
a064a6cf9b theme updates to extensions 2019-07-02 19:35:01 -04:00
Kyle Spearrin
7953a9a3ce autofill activated regular color 2019-07-02 17:28:57 -04:00
Kyle Spearrin
be3c6f210d remove see apps from ext page 2019-07-02 17:27:55 -04:00
Kyle Spearrin
f7cbddab4b remove navibar outlet 2019-07-02 17:17:20 -04:00
Kyle Spearrin
d423818764 add hockeyapp to extensions 2019-07-02 16:28:01 -04:00
Kyle Spearrin
519acd43aa provisioning profiles 2019-07-02 16:13:09 -04:00
Kyle Spearrin
2682a0d9e4 --nodevcodeshare on debug 2019-07-02 14:58:18 -04:00
Kyle Spearrin
8629ae048c update build props 2019-07-02 14:43:07 -04:00
Kyle Spearrin
905d01e804 adjust settings 2019-07-02 14:14:59 -04:00
Kyle Spearrin
0588bbc41d remove remaining jsonnet deps 2019-07-02 14:06:21 -04:00
Kyle Spearrin
b308b4c54f move some json dependencies out of extension project 2019-07-02 14:03:59 -04:00
Kyle Spearrin
c2c73d5460 autofill pages for ios 2019-07-02 13:15:00 -04:00
Kyle Spearrin
e01bf57874 re-set state for website icons on login/unlock
resolves #549
2019-07-02 08:05:34 -04:00
Kyle Spearrin
7ced93225b logic on autoPromptFingerprint when locked 2019-07-02 07:50:09 -04:00
Kyle Spearrin
b5e61864af adjust header for autofill page 2019-07-02 00:19:59 -04:00
Kyle Spearrin
1e5aaea8f4 restore lockOptionMs 2019-07-01 23:45:31 -04:00
Kyle Spearrin
ab3bebf06a use background tasks to keep timers alive 2019-07-01 23:44:47 -04:00
Kyle Spearrin
4a294d6a77 save some lines 2019-07-01 21:16:28 -04:00
Kyle Spearrin
e0fda1a0bc fix ui thread issue 2019-07-01 21:15:53 -04:00
Kyle Spearrin
d17da80f19 lock timer 2019-07-01 21:10:24 -04:00
Kyle Spearrin
2e7658f857 clear clipboard timer 2019-07-01 16:56:42 -04:00
Kyle Spearrin
53d0b28c7c fix add matching eq domains
resolves #550
2019-07-01 16:06:52 -04:00
Kyle Spearrin
33ba4d3871 add capitalize and include num to generator 2019-07-01 15:35:26 -04:00
Kyle Spearrin
225db6397d ios app extension theming 2019-07-01 15:12:54 -04:00
Nicholas
73b5d1b3f1 add support for org.mozilla.fenix.nightly (#551)
* Update autofillservice.xml

* Update AutofillHelpers.cs

* Update AccessibilityHelpers.cs
2019-07-01 11:35:58 -04:00
Kyle Spearrin
8da2eac6d0 add support for org.mozilla.fennec_fdroid
resolves #548
2019-06-28 23:23:51 -04:00
Kyle Spearrin
fbd62153ee theme splash for extensions 2019-06-28 12:30:48 -04:00
Kyle Spearrin
9145fa1c48 improvement to lock screen 2019-06-28 11:47:04 -04:00
Kyle Spearrin
caa0af1258 remove old action view controllers 2019-06-28 10:45:42 -04:00
Kyle Spearrin
7a230ee5f5 app extension for autofill ios 2019-06-28 08:57:08 -04:00
Kyle Spearrin
f237fa98d2 ios autofill extension implemented 2019-06-28 08:21:44 -04:00
Kyle Spearrin
be4ae605a9 implement ASHelpers from messages 2019-06-27 16:22:58 -04:00
Kyle Spearrin
9c2cbc0ecb add shared controllers and view to ios core 2019-06-27 15:48:25 -04:00
Kyle Spearrin
fb3009fc66 core utils 2019-06-27 14:07:25 -04:00
Kyle Spearrin
04c32e28cd move device action to ios core 2019-06-27 13:58:08 -04:00
Kyle Spearrin
645576c949 port over models 2019-06-27 13:45:16 -04:00
Kyle Spearrin
775bee3546 fix dependency hell 2019-06-27 13:41:32 -04:00
Kyle Spearrin
88aea96034 add autofill resources 2019-06-26 20:52:17 -04:00
Kyle Spearrin
5f474dfaf5 add some missing resources 2019-06-26 20:43:14 -04:00
Kyle Spearrin
fe7aad0835 autofill extension project 2019-06-26 20:39:45 -04:00
Kyle Spearrin
79746efa2d action extension project 2019-06-26 20:28:23 -04:00
Kyle Spearrin
a158021f46 return selection collection logic 2019-06-26 17:50:57 -04:00
Kyle Spearrin
2d91a893f7 fix cursor color to renderers 2019-06-26 10:20:42 -04:00
Kyle Spearrin
dd4561d985 style cursor color 2019-06-26 10:12:34 -04:00
Kyle Spearrin
92764eeae0 hide status bar on homepage for ios 2019-06-26 10:05:31 -04:00
Kyle Spearrin
b72808ab40 splash screen bg colors and white logo 2019-06-26 09:35:18 -04:00
Kyle Spearrin
14f3f99218 fix attachments selection on ios 2019-06-25 17:46:37 -04:00
Kyle Spearrin
d7130d9b67 no entities state adjustments 2019-06-25 17:16:47 -04:00
Kyle Spearrin
3f94eee4d5 events url 2019-06-25 16:36:21 -04:00
Kyle Spearrin
72cbdcbc8d use internal FilesDir for temp photo 2019-06-25 11:54:31 -04:00
Kyle Spearrin
e33b49e78c externalsFileDir w/ FileProvider for temp store 2019-06-25 10:30:16 -04:00
Kyle Spearrin
8e04945d4e box-row-input-options-platform on share 2019-06-24 17:38:29 -04:00
Kyle Spearrin
3ca5da55cb fix more options for sharing on view/add/edit 2019-06-24 17:34:00 -04:00
Kyle Spearrin
ea30373a09 picker SetUpdateMode for ios 2019-06-24 17:32:24 -04:00
Kyle Spearrin
4b4757d0e5 ios resumed 2019-06-24 17:02:05 -04:00
Kyle Spearrin
4bc837509d fix double key formatting 2019-06-24 16:51:54 -04:00
Kyle Spearrin
c9d1e8dc65 nord theme toast for ios 2019-06-24 16:29:02 -04:00
Kyle Spearrin
88b8a192b5 no listview selection type on non-light theme 2019-06-24 16:16:17 -04:00
Kyle Spearrin
94fbf627ba no options during selection mode 2019-06-24 15:22:46 -04:00
Kyle Spearrin
45fbdb8411 ios theming 2019-06-24 15:13:33 -04:00
Kyle Spearrin
d9c947ccd0 black theme for ios 2019-06-24 14:49:47 -04:00
Kyle Spearrin
2b670a5ae1 ios themeing 2019-06-24 14:29:23 -04:00
Kyle Spearrin
1af0178b50 set theme properly on app launch for ios 2019-06-24 12:23:00 -04:00
Kyle Spearrin
3ec5d894b3 spacing for ios on options page 2019-06-24 12:05:01 -04:00
Kyle Spearrin
d81585ccc3 search bar for ios 2019-06-24 11:53:19 -04:00
Kyle Spearrin
38f91bce1c notes separator for ios 2019-06-24 11:22:34 -04:00
Kyle Spearrin
2d41dd6ae0 switch styling on iOS 2019-06-22 09:51:04 -04:00
Kyle Spearrin
1705a21f68 slider styling 2019-06-22 09:45:54 -04:00
Kyle Spearrin
164d79898a button styling 2019-06-22 09:15:37 -04:00
Kyle Spearrin
50f809d290 undo busy when syncing complete 2019-06-21 16:53:17 -04:00
Kyle Spearrin
39284b475d bottom border on picker and no padding on editor 2019-06-21 16:36:23 -04:00
Kyle Spearrin
d44950d46c bottom border for ios entry 2019-06-21 16:09:20 -04:00
Kyle Spearrin
e9b55bc207 fix tag issue on settings page 2019-06-21 10:01:35 -04:00
Kyle Spearrin
5470f08fee list-row-header-container bg color 2019-06-21 09:59:22 -04:00
Kyle Spearrin
f9a3bbd7fa remove green background 2019-06-21 09:47:10 -04:00
Kyle Spearrin
9d3165dc65 New grid layout structure for cipher view cell 2019-06-21 09:46:46 -04:00
Kyle Spearrin
3475d39f37 use bold colored headers 2019-06-20 17:26:42 -04:00
Kyle Spearrin
44782b1ddf header upper on iOS 2019-06-20 17:05:28 -04:00
Kyle Spearrin
dd8d5fd84c icon sizes for ios 2019-06-20 16:49:27 -04:00
Kyle Spearrin
e8f2d9d0dd list section border colors 2019-06-20 16:40:13 -04:00
Kyle Spearrin
a2de3b5d80 remove binding context from header viewcell 2019-06-20 16:34:17 -04:00
Kyle Spearrin
a2960c45bc accessible font sizes 2019-06-20 16:32:22 -04:00
Kyle Spearrin
dc91624597 some listview styling for iOS 2019-06-20 16:02:39 -04:00
Kyle Spearrin
223ec180fc disable spell check & prediction on certain fields 2019-06-19 16:03:55 -04:00
Kyle Spearrin
0116572fec show nested collections in groupings pages 2019-06-17 10:21:05 -04:00
324 changed files with 20129 additions and 10243 deletions

View File

@@ -1,3 +1,112 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Don't use tabs for indentation.
[*]
indent_style = space
# (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# JSON files
[*.json]
indent_size = 2
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# Prefix private members with underscore
dotnet_naming_rule.private_members_with_underscore.symbols = private_members
dotnet_naming_rule.private_members_with_underscore.style = underscore_prefix
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
dotnet_naming_symbols.private_members.applicable_kinds = field
dotnet_naming_symbols.private_members.applicable_accessibilities = private
dotnet_naming_symbols.private_members.required_modifiers = readonly
dotnet_naming_style.underscore_prefix.capitalization = camel_case
dotnet_naming_style.underscore_prefix.required_prefix = _
dotnet_naming_style.underscore_prefix.required_suffix =
dotnet_naming_style.underscore_prefix.word_separator =
# Async methods should have "Async" suffix
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
dotnet_naming_rule.async_methods_end_in_async.severity = suggestion
dotnet_naming_symbols.any_async_methods.applicable_kinds = method
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
dotnet_naming_symbols.any_async_methods.required_modifiers = async
dotnet_naming_style.end_in_async.required_prefix =
dotnet_naming_style.end_in_async.required_suffix = Async
dotnet_naming_style.end_in_async.capitalization = pascal_case
dotnet_naming_style.end_in_async.word_separator =
# CSharp code style settings:
[*.cs]
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Prefer method-like constructs to have a expression-body
csharp_style_expression_bodied_methods = true:none
csharp_style_expression_bodied_constructors = true:none
csharp_style_expression_bodied_operators = true:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
# All files
[*]
guidelines = 120
guidelines = 120

View File

@@ -8,7 +8,7 @@
The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="300" height="533" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios.png" alt="" width="300" height="533" />
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="300" height="533" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="533" />
# Build/Run

View File

@@ -1,5 +1,5 @@
image:
- Visual Studio 2017
- Visual Studio 2019
- Ubuntu1804
branches:
@@ -95,7 +95,9 @@ build_script:
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.apk
Push-AppveyorArtifact .\com.x8bit.bitwarden-fdroid.apk
}

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.539
# Visual Studio Version 16
VisualStudioVersion = 16.0.29009.5
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}"
EndProject
@@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "google", "google", "{2E3996
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76690DFB-B7F4-4781-83E4-113FDC450AFE}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
appveyor.yml = appveyor.yml
CONTRIBUTING.md = CONTRIBUTING.md
@@ -35,6 +36,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Core", "src\iOS.Core\iO
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "src\iOS\iOS.csproj", "{599E0201-420A-4C3E-A7BA-5349F72E0B15}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Extension", "src\iOS.Extension\iOS.Extension.csproj", "{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@@ -99,24 +104,24 @@ Global
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Deploy.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Deploy.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Deploy.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Build.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Deploy.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Deploy.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Build.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Build.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Deploy.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
@@ -141,18 +146,18 @@ Global
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.Build.0 = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.Build.0 = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.Build.0 = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhone.ActiveCfg = Debug|Any CPU
@@ -231,18 +236,18 @@ Global
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhone.Build.0 = Release|Any CPU
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.ActiveCfg = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.Build.0 = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.ActiveCfg = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.Build.0 = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.Build.0 = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
@@ -286,6 +291,62 @@ Global
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhone.Build.0 = Release|iPhone
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|Any CPU.Build.0 = AppStore|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|iPhone.Build.0 = AppStore|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.ActiveCfg = Debug|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.Build.0 = Debug|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.ActiveCfg = Release|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.Build.0 = Release|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|iPhone.ActiveCfg = Release|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|iPhone.Build.0 = Release|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|Any CPU.ActiveCfg = Release|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|iPhone.ActiveCfg = Release|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|iPhone.Build.0 = Release|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|Any CPU.Build.0 = AppStore|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|iPhone.Build.0 = AppStore|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|Any CPU.ActiveCfg = Debug|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.ActiveCfg = Debug|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.Build.0 = Debug|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|Any CPU.ActiveCfg = Release|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|Any CPU.Build.0 = Release|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|iPhone.ActiveCfg = Release|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|iPhone.Build.0 = Release|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|Any CPU.ActiveCfg = Release|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhone.ActiveCfg = Release|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhone.Build.0 = Release|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -299,6 +360,8 @@ Global
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D} = {2E399654-26A2-46F6-B9CA-1B496A3F370A}
{E71F3053-056C-4381-9638-048ED73BDFF6} = {D10CA4A9-F866-40E1-B658-F69051236C71}
{599E0201-420A-4C3E-A7BA-5349F72E0B15} = {D10CA4A9-F866-40E1-B658-F69051236C71}
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545} = {D10CA4A9-F866-40E1-B658-F69051236C71}
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A} = {D10CA4A9-F866-40E1-B658-F69051236C71}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7D436EA3-8B7E-45D2-8D14-0730BD2E0410}

8
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{
"name": "bitwarden-fdroid",
"name": "bitwarden-mobile",
"version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
@@ -196,9 +196,9 @@
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"minimatch": {

View File

@@ -37,9 +37,11 @@ namespace Bit.Droid.Accessibility
new Browser("org.mozilla.firefox", "url_bar_title"),
new Browser("org.mozilla.firefox_beta", "url_bar_title"),
new Browser("org.mozilla.fennec_aurora", "url_bar_title"),
new Browser("org.mozilla.fennec_fdroid", "url_bar_title"),
new Browser("org.mozilla.focus", "display_url"),
new Browser("org.mozilla.klar", "display_url"),
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"),
new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"),
new Browser("com.ghostery.android.ghostery", "search_field"),
new Browser("org.adblockplus.browser", "url_bar_title"),
@@ -61,6 +63,11 @@ namespace Bit.Droid.Accessibility
new Browser("com.kiwibrowser.browser", "url_bar"),
new Browser("com.ecosia.android", "url_bar"),
new Browser("com.qwant.liberty", "url_bar_title"),
new Browser("jp.co.fenrir.android.sleipnir", "url_text"),
new Browser("jp.co.fenrir.android.sleipnir_black", "url_text"),
new Browser("jp.co.fenrir.android.sleipnir_test", "url_text"),
new Browser("com.vivaldi.browser", "url_bar"),
new Browser("com.feedback.browser.wjbrowser", "addressbar_url"),
}.ToDictionary(n => n.PackageName);
// Known packages to skip
@@ -80,13 +87,18 @@ namespace Bit.Droid.Accessibility
"com.teslacoilsw.launcher.prime",
"is.shortcut",
"me.craftsapp.nlauncher",
"com.ss.squarehome2"
"com.ss.squarehome2",
"com.treydev.pns"
};
public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e)
{
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
foreach(var node in testNodesData)
{
System.Diagnostics.Debug.WriteLine("Node: {0} = {1}", node.id, node.text);
}
}
public static string GetUri(AccessibilityNodeInfo root)
@@ -243,4 +255,4 @@ namespace Bit.Droid.Accessibility
return allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
}
}
}
}

View File

@@ -16,7 +16,7 @@
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
@@ -74,7 +74,6 @@
<Reference Include="Mono.Android.Export" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
@@ -117,10 +116,10 @@
<Compile Include="Effects\FixedSizeEffect.cs" />
<Compile Include="Effects\SelectableLabelEffect.cs" />
<Compile Include="Effects\TabBarEffect.cs" />
<Compile Include="Migration\AndroidKeyStoreStorageService.cs" />
<Compile Include="Push\FirebaseInstanceIdService.cs" />
<Compile Include="Push\FirebaseMessagingService.cs" />
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
<Compile Include="Receivers\EventUploadReceiver.cs" />
<Compile Include="Receivers\LockAlarmReceiver.cs" />
<Compile Include="Receivers\PackageReplacedReceiver.cs" />
<Compile Include="Renderers\CipherViewCellRenderer.cs" />
@@ -145,6 +144,7 @@
<Compile Include="Utilities\AndroidHelpers.cs" />
<Compile Include="Utilities\CustomFingerprintDialogFragment.cs" />
<Compile Include="Utilities\HockeyAppCrashManagerListener.cs" />
<Compile Include="Utilities\StaticStore.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\FontAwesome.ttf" />

View File

@@ -53,11 +53,14 @@ namespace Bit.Droid.Autofill
"com.ecosia.android",
"com.opera.mini.native.beta",
"org.mozilla.fennec_aurora",
"org.mozilla.fennec_fdroid",
"com.qwant.liberty",
"com.opera.touch",
"org.mozilla.fenix",
"org.mozilla.fenix.nightly",
"org.mozilla.reference.browser",
"org.mozilla.rocket",
"com.vivaldi.browser",
};
// The URLs are blacklisted from autofilling

View File

@@ -5,6 +5,7 @@ using Bit.Core;
using Android.Content;
using Bit.Core.Abstractions;
using System.Threading.Tasks;
using Android.OS;
namespace Bit.Droid.Autofill
{
@@ -17,7 +18,7 @@ namespace Bit.Droid.Autofill
private readonly AssistStructure _structure;
private string _uri;
private string _packageName;
private string _webDomain;
private string _website;
public Parser(AssistStructure structure, Context applicationContext)
{
@@ -36,14 +37,14 @@ namespace Bit.Droid.Autofill
{
return _uri;
}
var webDomainNull = string.IsNullOrWhiteSpace(WebDomain);
if(webDomainNull && string.IsNullOrWhiteSpace(PackageName))
var websiteNull = string.IsNullOrWhiteSpace(Website);
if(websiteNull && string.IsNullOrWhiteSpace(PackageName))
{
_uri = null;
}
else if(!webDomainNull)
else if(!websiteNull)
{
_uri = string.Concat("http://", WebDomain);
_uri = Website;
}
else
{
@@ -66,16 +67,16 @@ namespace Bit.Droid.Autofill
}
}
public string WebDomain
public string Website
{
get => _webDomain;
get => _website;
set
{
if(string.IsNullOrWhiteSpace(value))
{
_webDomain = _uri = null;
_website = _uri = null;
}
_webDomain = value;
_website = value;
}
}
@@ -96,15 +97,24 @@ namespace Bit.Droid.Autofill
public void Parse()
{
string titlePackageId = null;
for(var i = 0; i < _structure.WindowNodeCount; i++)
{
var node = _structure.GetWindowNodeAt(i);
if(i == 0)
{
titlePackageId = GetTitlePackageId(node);
}
ParseNode(node.RootViewNode);
}
if(string.IsNullOrWhiteSpace(PackageName) && string.IsNullOrWhiteSpace(Website))
{
PackageName = titlePackageId;
}
if(!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
!AutofillHelpers.CompatBrowsers.Contains(PackageName))
{
WebDomain = null;
Website = null;
}
}
@@ -135,10 +145,32 @@ namespace Bit.Droid.Autofill
{
PackageName = node.IdPackage;
}
if(string.IsNullOrWhiteSpace(WebDomain) && !string.IsNullOrWhiteSpace(node.WebDomain))
if(string.IsNullOrWhiteSpace(Website) && !string.IsNullOrWhiteSpace(node.WebDomain))
{
WebDomain = node.WebDomain;
var scheme = "http";
if((int)Build.VERSION.SdkInt >= 28)
{
scheme = node.WebScheme;
}
Website = string.Format("{0}://{1}", scheme, node.WebDomain);
}
}
private string GetTitlePackageId(WindowNode node)
{
if(node != null && !string.IsNullOrWhiteSpace(node.Title))
{
var slashPosition = node.Title.IndexOf('/');
if(slashPosition > -1)
{
var packageId = node.Title.Substring(0, slashPosition);
if(packageId.Contains("."))
{
return packageId;
}
}
}
return null;
}
}
}

View File

@@ -17,6 +17,7 @@ using Bit.Core.Enums;
using Android.Nfc;
using Bit.App.Utilities;
using System.Threading.Tasks;
using Android.Support.V4.Content;
namespace Bit.Droid
{
@@ -29,17 +30,16 @@ namespace Bit.Droid
[Register("com.x8bit.bitwarden.MainActivity")]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
private IDeviceActionService _deviceActionService;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
private IUserService _userService;
private IAppIdService _appIdService;
private IStorageService _storageService;
private IStateService _stateService;
private IEventService _eventService;
private PendingIntent _lockAlarmPendingIntent;
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 =
@@ -47,6 +47,9 @@ namespace Bit.Droid
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));
_lockAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
PendingIntentFlags.UpdateCurrent);
@@ -63,12 +66,12 @@ namespace Bit.Droid
_userService = ServiceContainer.Resolve<IUserService>("userService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
UpdateTheme(ThemeManager.GetTheme());
UpdateTheme(ThemeManager.GetTheme(true));
base.OnCreate(savedInstanceState);
if(!CoreHelpers.InDebugMode())
{
@@ -77,8 +80,7 @@ namespace Bit.Droid
#if !FDROID
var hockeyAppListener = new HockeyAppCrashManagerListener(_appIdService, _userService);
var hockeyAppTask = hockeyAppListener.InitAsync();
HockeyApp.Android.CrashManager.Register(this, HockeyAppId, hockeyAppListener);
var hockeyAppTask = hockeyAppListener.InitAsync(this);
#endif
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
@@ -90,10 +92,10 @@ namespace Bit.Droid
{
if(message.Command == "scheduleLockTimer")
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
var lockOptionMinutes = (int)message.Data;
var lockOptionMs = lockOptionMinutes * 60000;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + lockOptionMs + 10;
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _lockAlarmPendingIntent);
}
else if(message.Command == "cancelLockTimer")
@@ -101,6 +103,14 @@ namespace Bit.Droid
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Cancel(_lockAlarmPendingIntent);
}
else if(message.Command == "startEventTimer")
{
StartEventAlarm();
}
else if(message.Command == "stopEventTimer")
{
var task = StopEventAlarmAsync();
}
else if(message.Command == "finishMainActivity")
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish());
@@ -202,9 +212,8 @@ namespace Bit.Droid
else
{
// camera
var root = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
uri = Android.Net.Uri.FromFile(file);
var file = new Java.IO.File(FilesDir, "temp_camera_photo.jpg");
uri = FileProvider.GetUriForFile(this, "com.x8bit.bitwarden.fileprovider", file);
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
}
@@ -367,10 +376,23 @@ namespace Bit.Droid
{
return;
}
await _stateService.SaveAsync(Constants.LastClipboardValueKey, data.Item1);
StaticStore.LastClipboardValue = data.Item1;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs.Value;
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent);
}
private void StartEventAlarm()
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.SetInexactRepeating(AlarmType.ElapsedRealtime, 120000, 300000, _eventUploadPendingIntent);
}
private async Task StopEventAlarmAsync()
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Cancel(_eventUploadPendingIntent);
await _eventService.UploadEventsAsync();
}
}
}

View File

@@ -40,12 +40,8 @@ namespace Bit.Droid
if(ServiceContainer.RegisteredServices.Count == 0)
{
RegisterLocalServices();
ServiceContainer.Init(new AndroidClientHandler());
if(App.Migration.MigrationHelpers.NeedsMigration())
{
var task = App.Migration.MigrationHelpers.PerformMigrationAsync();
Task.Delay(2000).Wait();
}
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
}
#if !FDROID
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -73,12 +69,6 @@ namespace Bit.Droid
private void RegisterLocalServices()
{
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
ServiceContainer.Register("settingsShim", new App.Migration.SettingsShim());
if(App.Migration.MigrationHelpers.NeedsMigration())
{
ServiceContainer.Register<App.Migration.Abstractions.IOldSecureStorageService>(
"oldSecureStorageService", new Migration.AndroidKeyStoreStorageService());
}
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
// Note: This might cause a race condition. Investigate more.
@@ -107,7 +97,7 @@ namespace Bit.Droid
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService,
broadcasterService);
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
@@ -148,11 +138,11 @@ namespace Bit.Droid
private async Task BootstrapAsync()
{
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService").GetAsync<bool?>(
Constants.DisableFaviconKey);
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(Constants.DisableFaviconKey,
disableFavicon);
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService")
.GetAsync<bool?>(Constants.DisableFaviconKey);
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
Constants.DisableFaviconKey, disableFavicon);
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
}
}
}
}

View File

@@ -1,373 +0,0 @@
using Java.Security;
using Javax.Crypto;
using Android.OS;
using Bit.App.Abstractions;
using System;
using Android.Security;
using Javax.Security.Auth.X500;
using Java.Math;
using Android.Security.Keystore;
using Android.App;
using Java.Util;
using Javax.Crypto.Spec;
using Android.Preferences;
using Bit.App.Migration;
using Bit.Core.Utilities;
using Bit.App.Migration.Abstractions;
namespace Bit.Droid.Migration
{
public class AndroidKeyStoreStorageService : IOldSecureStorageService
{
private const string AndroidKeyStore = "AndroidKeyStore";
private const string AesMode = "AES/GCM/NoPadding";
private const string KeyAlias = "bitwardenKey2";
private const string KeyAliasV1 = "bitwardenKey";
private const string SettingsFormat = "ksSecured2:{0}";
private const string SettingsFormatV1 = "ksSecured:{0}";
private const string AesKey = "ksSecured2:aesKeyForService";
private const string AesKeyV1 = "ksSecured:aesKeyForService";
private readonly string _rsaMode;
private readonly bool _oldAndroid;
private readonly SettingsShim _settings;
private readonly KeyStore _keyStore;
public AndroidKeyStoreStorageService()
{
_oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
_rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
_settings = ServiceContainer.Resolve<SettingsShim>("settingsShim");
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
_keyStore.Load(null);
/*
try
{
GenerateStoreKey(true);
}
catch
{
GenerateStoreKey(false);
}
GenerateAesKey();
*/
}
public bool Contains(string key)
{
return _settings.Contains(string.Format(SettingsFormat, key)) ||
_settings.Contains(string.Format(SettingsFormatV1, key));
}
public void Delete(string key)
{
CleanupOld(key);
var formattedKey = string.Format(SettingsFormat, key);
if(_settings.Contains(formattedKey))
{
_settings.Remove(formattedKey);
}
}
public byte[] Retrieve(string key)
{
var formattedKey = string.Format(SettingsFormat, key);
if(!_settings.Contains(formattedKey))
{
return TryGetAndMigrate(key);
}
var cs = _settings.GetValueOrDefault(formattedKey, null);
if(string.IsNullOrWhiteSpace(cs))
{
return null;
}
var aesKey = GetAesKey();
if(aesKey == null)
{
return null;
}
try
{
return App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKey);
}
catch
{
Console.WriteLine("Failed to decrypt from secure storage.");
_settings.Remove(formattedKey);
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
return null;
}
}
public void Store(string key, byte[] dataBytes)
{
var formattedKey = string.Format(SettingsFormat, key);
CleanupOld(key);
if(dataBytes == null)
{
_settings.Remove(formattedKey);
return;
}
var aesKey = GetAesKey();
if(aesKey == null)
{
return;
}
try
{
var cipherString = App.Migration.Crypto.AesCbcEncrypt(dataBytes, aesKey);
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
}
catch
{
Console.WriteLine("Failed to encrypt to secure storage.");
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
}
}
private void GenerateStoreKey(bool withDate)
{
if(_keyStore.ContainsAlias(KeyAlias))
{
return;
}
ClearSettings();
var end = Calendar.Instance;
end.Add(CalendarField.Year, 99);
if(_oldAndroid)
{
var subject = new X500Principal($"CN={KeyAlias}");
var builder = new KeyPairGeneratorSpec.Builder(Application.Context)
.SetAlias(KeyAlias)
.SetSubject(subject)
.SetSerialNumber(BigInteger.Ten);
if(withDate)
{
builder.SetStartDate(new Date(0)).SetEndDate(end.Time);
}
var spec = builder.Build();
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
gen.Initialize(spec);
gen.GenerateKeyPair();
}
else
{
var builder = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
.SetBlockModes(KeyProperties.BlockModeGcm)
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone);
if(withDate)
{
builder.SetKeyValidityStart(new Date(0)).SetKeyValidityEnd(end.Time);
}
var spec = builder.Build();
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
gen.Init(spec);
gen.GenerateKey();
}
}
private KeyStore.PrivateKeyEntry GetRsaKeyEntry(string alias)
{
return _keyStore.GetEntry(alias, null) as KeyStore.PrivateKeyEntry;
}
private void GenerateAesKey()
{
if(_settings.Contains(AesKey))
{
return;
}
var key = App.Migration.Crypto.RandomBytes(512 / 8);
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
_settings.AddOrUpdateValue(AesKey, encKey);
}
private App.Migration.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
{
try
{
var aesKey = v1 ? AesKeyV1 : AesKey;
if(!_settings.Contains(aesKey))
{
return null;
}
var encKey = _settings.GetValueOrDefault(aesKey, null);
if(string.IsNullOrWhiteSpace(encKey))
{
return null;
}
if(_oldAndroid || v1)
{
var encKeyBytes = Convert.FromBase64String(encKey);
var key = RsaDecrypt(encKeyBytes, v1);
return new App.Migration.Models.SymmetricCryptoKey(key);
}
else
{
var parts = encKey.Split('|');
if(parts.Length < 2)
{
return null;
}
var ivBytes = Convert.FromBase64String(parts[0]);
var encKeyBytes = Convert.FromBase64String(parts[1]);
var key = AesDecrypt(ivBytes, encKeyBytes);
return new App.Migration.Models.SymmetricCryptoKey(key);
}
}
catch
{
Console.WriteLine("Cannot get AesKey.");
_keyStore.DeleteEntry(KeyAlias);
_settings.Remove(AesKey);
if(!v1)
{
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
}
return null;
}
}
private string AesEncrypt(byte[] input)
{
using(var entry = _keyStore.GetKey(KeyAlias, null))
using(var cipher = Cipher.GetInstance(AesMode))
{
cipher.Init(CipherMode.EncryptMode, entry);
var encBytes = cipher.DoFinal(input);
var ivBytes = cipher.GetIV();
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
}
}
private byte[] AesDecrypt(byte[] iv, byte[] encData)
{
using(var entry = _keyStore.GetKey(KeyAlias, null))
using(var cipher = Cipher.GetInstance(AesMode))
{
var spec = new GCMParameterSpec(128, iv);
cipher.Init(CipherMode.DecryptMode, entry, spec);
var decBytes = cipher.DoFinal(encData);
return decBytes;
}
}
private string RsaEncrypt(byte[] data)
{
using(var entry = GetRsaKeyEntry(KeyAlias))
using(var cipher = Cipher.GetInstance(_rsaMode))
{
cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
var cipherText = cipher.DoFinal(data);
return Convert.ToBase64String(cipherText);
}
}
private byte[] RsaDecrypt(byte[] encData, bool v1)
{
using(var entry = GetRsaKeyEntry(v1 ? KeyAliasV1 : KeyAlias))
using(var cipher = Cipher.GetInstance(_rsaMode))
{
if(_oldAndroid)
{
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
}
else
{
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey, OAEPParameterSpec.Default);
}
var plainText = cipher.DoFinal(encData);
return plainText;
}
}
private byte[] TryGetAndMigrate(string key)
{
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
if(_settings.Contains(formattedKeyV1))
{
var aesKeyV1 = GetAesKey(true);
if(aesKeyV1 != null)
{
try
{
var cs = _settings.GetValueOrDefault(formattedKeyV1, null);
var value = App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKeyV1);
Store(key, value);
return value;
}
catch
{
Console.WriteLine("Failed to decrypt v1 from secure storage.");
}
}
_settings.Remove(formattedKeyV1);
}
return null;
}
private void CleanupOld(string key)
{
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
if(_settings.Contains(formattedKeyV1))
{
_settings.Remove(formattedKeyV1);
}
}
private void ClearSettings(string format = SettingsFormat)
{
var prefix = string.Format(format, string.Empty);
using(var sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(Application.Context))
using(var sharedPreferencesEditor = sharedPreferences.Edit())
{
var removed = false;
foreach(var pref in sharedPreferences.All)
{
if(pref.Key.StartsWith(prefix))
{
removed = true;
sharedPreferencesEditor.Remove(pref.Key);
}
}
if(removed)
{
sharedPreferencesEditor.Commit();
}
}
}
}
}

View File

@@ -3,16 +3,17 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="2.0.6"
android:versionName="2.2.6"
package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28" />
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="29" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />

View File

@@ -2,22 +2,21 @@
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Utilities;
namespace Bit.Droid.Receivers
{
[BroadcastReceiver(Name = "com.x8bit.bitwarden.ClearClipboardAlarmReceiver", Exported = false)]
public class ClearClipboardAlarmReceiver : BroadcastReceiver
{
public async override void OnReceive(Context context, Intent intent)
public override void OnReceive(Context context, Intent intent)
{
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
var lastClipboardValue = await stateService.GetAsync<string>(Constants.LastClipboardValueKey);
await stateService.RemoveAsync(Constants.LastClipboardValueKey);
if(lastClipboardValue == clipboardManager.Text)
if(StaticStore.LastClipboardValue != null && StaticStore.LastClipboardValue == clipboardManager.Text)
{
clipboardManager.Text = string.Empty;
}
StaticStore.LastClipboardValue = null;
}
}
}

View File

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

View File

@@ -23,7 +23,14 @@ namespace Bit.Droid.Renderers
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
if(t is GradientDrawable thumb)
{
thumb.SetColor(view.ThumbColor.ToAndroid());
if(view.ThumbColor == Color.Default)
{
thumb.SetColor(Color.White.ToAndroid());
}
else
{
thumb.SetColor(view.ThumbColor.ToAndroid());
}
thumb.SetStroke(3, view.ThumbBorderColor.ToAndroid());
Control.SetThumb(thumb);
}

File diff suppressed because it is too large Load Diff

View File

@@ -48,6 +48,9 @@
<compatibility-package
android:name="org.mozilla.fenix"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="org.mozilla.fenix.nightly"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="org.mozilla.reference.browser"
android:maxLongVersionCode="10000000000"/>
@@ -87,4 +90,7 @@
<compatibility-package
android:name="com.ecosia.android"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.vivaldi.browser"
android:maxLongVersionCode="10000000000"/>
</autofill-service>

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache" path="." />
<files-path name="internal" path="." />
</paths>

View File

@@ -8,9 +8,12 @@ using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.Content.PM;
using Android.Hardware.Biometrics;
using Android.Hardware.Fingerprints;
using Android.Nfc;
using Android.OS;
using Android.Provider;
using Android.Runtime;
using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Text;
@@ -28,6 +31,7 @@ using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Plugin.CurrentActivity;
using Plugin.Fingerprint;
namespace Bit.Droid.Services
{
@@ -36,18 +40,22 @@ namespace Bit.Droid.Services
private readonly IStorageService _storageService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly Func<IEventService> _eventServiceFunc;
private ProgressDialog _progressDialog;
private bool _cameraPermissionsDenied;
private Toast _toast;
private string _userAgent;
public DeviceActionService(
IStorageService storageService,
IMessagingService messagingService,
IBroadcasterService broadcasterService)
IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc)
{
_storageService = storageService;
_messagingService = messagingService;
_broadcasterService = broadcasterService;
_eventServiceFunc = eventServiceFunc;
_broadcasterService.Subscribe(nameof(DeviceActionService), (message) =>
{
@@ -58,6 +66,19 @@ namespace Bit.Droid.Services
});
}
public string DeviceUserAgent
{
get
{
if(string.IsNullOrWhiteSpace(_userAgent))
{
_userAgent = $"Bitwarden_Mobile/{Xamarin.Essentials.AppInfo.VersionString} " +
$"(Android {Build.VERSION.Release}; SDK {Build.VERSION.Sdk}; Model {Build.Model})";
}
return _userAgent;
}
}
public DeviceType DeviceType => DeviceType.Android;
public void Toast(string text, bool longDuration = false)
@@ -111,38 +132,14 @@ namespace Bit.Droid.Services
public bool OpenFile(byte[] fileData, string id, string fileName)
{
if(!CanOpenFile(fileName))
{
return false;
}
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if(extension == null)
{
return false;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if(mimeType == null)
{
return false;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var cachePath = activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if(!file.IsFile)
{
return false;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(fileData, fileName);
if(intent == null)
{
return false;
}
activity.StartActivity(intent);
return true;
}
@@ -151,22 +148,57 @@ namespace Bit.Droid.Services
}
public bool CanOpenFile(string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
if(intent == null)
{
return false;
}
var activities = activity.PackageManager.QueryIntentActivities(intent,
PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
}
catch { }
return false;
}
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
{
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if(extension == null)
{
return false;
return null;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if(mimeType == null)
{
return false;
return null;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = new Intent(Intent.ActionView);
intent.SetType(mimeType);
var activities = activity.PackageManager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
var cachePath = activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if(!file.IsFile)
{
return null;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
return intent;
}
catch { }
return null;
}
public async Task ClearCacheAsync()
@@ -182,7 +214,8 @@ namespace Bit.Droid.Services
public Task SelectFileAsync()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var hasStorageWritePermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.WriteExternalStorage);
var hasStorageWritePermission = !_cameraPermissionsDenied &&
HasPermission(Manifest.Permission.WriteExternalStorage);
var additionalIntents = new List<IParcelable>();
if(activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
{
@@ -201,14 +234,14 @@ namespace Bit.Droid.Services
{
try
{
var root = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
var file = new Java.IO.File(activity.FilesDir, "temp_camera_photo.jpg");
if(!file.Exists())
{
file.ParentFile.Mkdirs();
file.CreateNewFile();
}
var outputFileUri = Android.Net.Uri.FromFile(file);
var outputFileUri = FileProvider.GetUriForFile(activity,
"com.x8bit.bitwarden.fileprovider", file);
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
}
catch(Java.IO.IOException) { }
@@ -306,11 +339,72 @@ namespace Bit.Droid.Services
Application.Context.PackageName, 0).VersionCode.ToString();
}
public bool SupportsFaceId()
public bool SupportsFaceBiometric()
{
return false;
}
public Task<bool> SupportsFaceBiometricAsync()
{
return Task.FromResult(SupportsFaceBiometric());
}
public async Task<bool> BiometricAvailableAsync()
{
if(UseNativeBiometric())
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var manager = activity.GetSystemService(Context.BiometricService) as BiometricManager;
return manager.CanAuthenticate() == BiometricCode.Success;
}
else
{
try
{
return await CrossFingerprint.Current.IsAvailableAsync();
}
catch
{
return false;
}
}
}
public bool UseNativeBiometric()
{
return (int)Build.VERSION.SdkInt >= 29;
}
public Task<bool> AuthenticateBiometricAsync(string text = null)
{
if(string.IsNullOrWhiteSpace(text))
{
text = AppResources.BiometricsDirection;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
using(var builder = new BiometricPrompt.Builder(activity))
{
builder.SetTitle(text);
builder.SetConfirmationRequired(false);
builder.SetNegativeButton(AppResources.Cancel, activity.MainExecutor,
new DialogInterfaceOnClickListener
{
Clicked = () => { }
});
var prompt = builder.Build();
var result = new TaskCompletionSource<bool>();
prompt.Authenticate(new CancellationSignal(), activity.MainExecutor,
new BiometricAuthenticationCallback
{
Success = authResult => result.TrySetResult(true),
Failed = () => result.TrySetResult(false),
Help = (helpCode, helpString) => { }
});
return result.Task;
}
}
public bool SupportsNfc()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@@ -463,6 +557,7 @@ namespace Bit.Droid.Services
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
activity.SetResult(Result.Ok, replyIntent);
activity.Finish();
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
else
{
@@ -488,6 +583,10 @@ namespace Bit.Droid.Services
}
activity.Finish();
_messagingService.Send("finishMainActivity");
if(cipher != null)
{
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
}
}
@@ -535,7 +634,8 @@ namespace Bit.Droid.Services
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var afm = (AutofillManager)activity.GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager)));
var afm = (AutofillManager)activity.GetSystemService(
Java.Lang.Class.FromType(typeof(AutofillManager)));
return afm.IsEnabled && afm.HasEnabledAutofillServices;
}
catch
@@ -577,6 +677,11 @@ namespace Bit.Droid.Services
}
}
public bool UsingDarkTheme()
{
return false;
}
private bool DeleteDir(Java.IO.File dir)
{
if(dir != null && dir.IsDirectory)
@@ -675,5 +780,41 @@ namespace Bit.Droid.Services
Context.ClipboardService) as Android.Content.ClipboardManager;
clipboardManager.Text = text;
}
private class BiometricAuthenticationCallback : BiometricPrompt.AuthenticationCallback
{
public Action<BiometricPrompt.AuthenticationResult> Success { get; set; }
public Action Failed { get; set; }
public Action<BiometricAcquiredStatus, Java.Lang.ICharSequence> Help { get; set; }
public override void OnAuthenticationSucceeded(BiometricPrompt.AuthenticationResult authResult)
{
base.OnAuthenticationSucceeded(authResult);
Success?.Invoke(authResult);
}
public override void OnAuthenticationFailed()
{
base.OnAuthenticationFailed();
Failed?.Invoke();
}
public override void OnAuthenticationHelp([GeneratedEnum] BiometricAcquiredStatus helpCode,
Java.Lang.ICharSequence helpString)
{
base.OnAuthenticationHelp(helpCode, helpString);
Help?.Invoke(helpCode, helpString);
}
}
private class DialogInterfaceOnClickListener : Java.Lang.Object, IDialogInterfaceOnClickListener
{
public Action Clicked { get; set; }
public void OnClick(IDialogInterface dialog, int which)
{
Clicked?.Invoke();
}
}
}
}
}

View File

@@ -5,11 +5,14 @@ using Newtonsoft.Json;
using Android.Runtime;
using Bit.Core.Abstractions;
using System.Threading.Tasks;
using Android.Content;
namespace Bit.Droid.Utilities
{
public class HockeyAppCrashManagerListener : CrashManagerListener
{
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
private readonly IAppIdService _appIdService;
private readonly IUserService _userService;
@@ -31,10 +34,11 @@ namespace Bit.Droid.Utilities
_userService = userService;
}
public async Task InitAsync()
public async Task InitAsync(Context context)
{
_userId = await _userService.GetUserIdAsync();
_appId = await _appIdService.GetAppIdAsync();
CrashManager.Register(context, HockeyAppId, this);
}
public override string Description

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace Bit.Droid.Utilities
{
public static class StaticStore
{
public static string LastClipboardValue { get; set; }
}
}

View File

@@ -6,6 +6,7 @@ namespace Bit.App.Abstractions
{
public interface IDeviceActionService
{
string DeviceUserAgent { get; }
DeviceType DeviceType { get; }
void Toast(string text, bool longDuration = false);
bool LaunchApp(string appName);
@@ -19,7 +20,11 @@ namespace Bit.App.Abstractions
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
bool autofocus = true);
void RateApp();
bool SupportsFaceId();
bool SupportsFaceBiometric();
Task<bool> SupportsFaceBiometricAsync();
Task<bool> BiometricAvailableAsync();
bool UseNativeBiometric();
Task<bool> AuthenticateBiometricAsync(string text = null);
bool SupportsNfc();
bool SupportsCamera();
bool SupportsAutofillService();
@@ -34,5 +39,6 @@ namespace Bit.App.Abstractions
string GetBuildNumber();
void OpenAccessibilitySettings();
void OpenAutofillSettings();
bool UsingDarkTheme();
}
}

View File

@@ -18,7 +18,7 @@
<PackageReference Include="Refractored.FloatingActionButtonForms" Version="2.1.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.1.0" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="3.6.0.344457" />
<PackageReference Include="Xamarin.Forms" Version="3.6.0.709228" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.1.47" />
</ItemGroup>
@@ -51,6 +51,12 @@
<Compile Update="Pages\Generator\GeneratorHistoryPage.xaml.cs">
<DependentUpon>GeneratorHistoryPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Settings\AutofillPage.xaml.cs">
<DependentUpon>AutofillPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Settings\ExtensionPage.xaml.cs">
<DependentUpon>ExtensionPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\Settings\AutofillServicePage.xaml.cs">
<DependentUpon>AutofillServicePage.xaml</DependentUpon>
</Compile>

View File

@@ -89,9 +89,7 @@ namespace Bit.App
}
else if(message.Command == "locked")
{
await _stateService.PurgeAsync();
var lockPage = new LockPage(_appOptions, !(message.Data as bool?).GetValueOrDefault());
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
await LockedAsync(!(message.Data as bool?).GetValueOrDefault());
}
else if(message.Command == "lockVault")
{
@@ -99,11 +97,8 @@ namespace Bit.App
}
else if(message.Command == "logout")
{
if(Migration.MigrationHelpers.Migrating)
{
return;
}
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(false));
Device.BeginInvokeOnMainThread(async () =>
await LogOutAsync((message.Data as bool?).GetValueOrDefault()));
}
else if(message.Command == "loggedOut")
{
@@ -114,7 +109,14 @@ namespace Bit.App
{
if(Device.RuntimePlatform == Device.iOS)
{
SyncIfNeeded();
ResumedAsync();
}
}
else if(message.Command == "slept")
{
if(Device.RuntimePlatform == Device.iOS)
{
await SleptAsync();
}
}
else if(message.Command == "migrated")
@@ -153,6 +155,7 @@ namespace Bit.App
{
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
await ClearCacheIfNeededAsync();
await TryClearCiphersCacheAsync();
Prime();
if(string.IsNullOrWhiteSpace(_appOptions.Uri))
{
@@ -163,6 +166,7 @@ namespace Bit.App
SyncIfNeeded();
}
}
_messagingService.Send("startEventTimer");
}
protected async override void OnSleep()
@@ -170,22 +174,39 @@ namespace Bit.App
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
if(Device.RuntimePlatform == Device.Android)
{
await _storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow);
var isLocked = await _lockService.IsLockedAsync();
if(!isLocked)
{
await _storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow);
}
SetTabsPageFromAutofill(isLocked);
await SleptAsync();
}
SetTabsPageFromAutofill();
await HandleLockingAsync();
}
protected async override void OnResume()
protected override void OnResume()
{
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
_messagingService.Send("cancelLockTimer");
await ClearCacheIfNeededAsync();
Prime();
if(Device.RuntimePlatform == Device.Android)
{
SyncIfNeeded();
ResumedAsync();
}
}
private async Task SleptAsync()
{
await HandleLockingAsync();
_messagingService.Send("stopEventTimer");
}
private async void ResumedAsync()
{
_messagingService.Send("cancelLockTimer");
_messagingService.Send("startEventTimer");
await ClearCacheIfNeededAsync();
await TryClearCiphersCacheAsync();
Prime();
SyncIfNeeded();
if(Current.MainPage is NavigationPage navPage && navPage.CurrentPage is LockPage lockPage)
{
await lockPage.PromptFingerprintAfterResumeAsync();
@@ -213,13 +234,17 @@ namespace Bit.App
_folderService.ClearAsync(userId),
_collectionService.ClearAsync(userId),
_passwordGenerationService.ClearAsync(),
_lockService.ClearAsync());
_lockService.PinLocked = false;
_lockService.ClearAsync(),
_stateService.PurgeAsync());
_lockService.FingerprintLocked = true;
_searchService.ClearIndex();
_authService.LogOut(() =>
{
Current.MainPage = new HomePage();
if(expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
@@ -287,7 +312,7 @@ namespace Bit.App
}
}
private void SetTabsPageFromAutofill()
private void SetTabsPageFromAutofill(bool isLocked)
{
if(Device.RuntimePlatform == Device.Android && !string.IsNullOrWhiteSpace(_appOptions.Uri) &&
!_appOptions.FromAutofillFramework)
@@ -296,8 +321,15 @@ namespace Bit.App
{
Device.BeginInvokeOnMainThread(() =>
{
Current.MainPage = new TabsPage();
_appOptions.Uri = null;
if(isLocked)
{
Current.MainPage = new NavigationPage(new LockPage());
}
else
{
Current.MainPage = new TabsPage();
}
});
});
}
@@ -316,7 +348,7 @@ namespace Bit.App
{
InitializeComponent();
SetCulture();
ThemeManager.SetTheme();
ThemeManager.SetTheme(Device.RuntimePlatform == Device.Android);
Current.MainPage = new HomePage();
var mainPageTask = SetMainPageAsync();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
@@ -324,10 +356,6 @@ namespace Bit.App
private void SyncIfNeeded()
{
if(Migration.MigrationHelpers.Migrating)
{
return;
}
if(Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
return;
@@ -335,11 +363,66 @@ namespace Bit.App
Task.Run(async () =>
{
var lastSync = await _syncService.GetLastSyncAsync();
if(DateTime.UtcNow - lastSync > TimeSpan.FromMinutes(30))
if(lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
{
await Task.Delay(1000);
await _syncService.FullSyncAsync(false);
}
});
}
private async Task TryClearCiphersCacheAsync()
{
if(Device.RuntimePlatform != Device.iOS)
{
return;
}
var clearCache = await _storageService.GetAsync<bool?>(Constants.ClearCiphersCacheKey);
if(clearCache.GetValueOrDefault())
{
_cipherService.ClearCache();
await _storageService.RemoveAsync(Constants.ClearCiphersCacheKey);
}
}
private async Task LockedAsync(bool autoPromptFingerprint)
{
await _stateService.PurgeAsync();
if(autoPromptFingerprint && Device.RuntimePlatform == Device.iOS)
{
var lockOptions = await _storageService.GetAsync<int?>(Constants.LockOptionKey);
if(lockOptions == 0)
{
autoPromptFingerprint = false;
}
}
PreviousPageInfo lastPageBeforeLock = null;
if(Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
{
var topPage = tabbedPage.Navigation.ModalStack[tabbedPage.Navigation.ModalStack.Count - 1];
if(topPage is NavigationPage navPage)
{
if(navPage.CurrentPage is ViewPage viewPage)
{
lastPageBeforeLock = new PreviousPageInfo
{
Page = "view",
CipherId = viewPage.ViewModel.CipherId
};
}
else if(navPage.CurrentPage is AddEditPage addEditPage && addEditPage.ViewModel.EditMode)
{
lastPageBeforeLock = new PreviousPageInfo
{
Page = "edit",
CipherId = addEditPage.ViewModel.CipherId
};
}
}
}
await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock);
var lockPage = new LockPage(_appOptions, autoPromptFingerprint);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
}
}
}

View File

@@ -3,84 +3,112 @@
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">
<Grid x:Name="_grid"
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.BindingContext>
<controls:CipherViewCellViewModel />
</Grid.BindingContext>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<controls:FaLabel x:Name="_icon"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform" />
<ff:CachedImage x:Name="_image"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
BitmapOptimizations="True"
ErrorPlaceholder="login.png"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="22"
HeightRequest="22"
IsVisible="False"/>
<Label LineBreakMode="TailTruncation"
Grid.Column="1"
<controls:FaLabel
x:Name="_icon"
Grid.Row="0"
Grid.Column="0"
HorizontalOptions="Center"
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="1"
<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="2"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf1e0;"
IsVisible="{Binding Cipher.Shared, Mode=OneWay}" />
<controls:FaLabel
Grid.Column="3"
Grid.Row="0"
HorizontalOptions="Start"
VerticalOptions="Center"
StyleClass="list-title-icon"
Margin="5, 0, 0, 0"
Text="&#xf0c6;"
IsVisible="{Binding Cipher.HasAttachments, 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
Text="&#xe5d4;"
StyleClass="list-row-button, list-row-button-platform, btn-disabled"
Clicked="ImageButton_Clicked"
Grid.Column="4"
Grid.Row="0"
Grid.RowSpan="2" />
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}" />
</Grid>
</ViewCell>

View File

@@ -181,7 +181,7 @@ namespace Bit.App.Controls
return new Tuple<string, string>(icon, image);
}
private void ImageButton_Clicked(object sender, EventArgs e)
private void MoreButton_Clicked(object sender, EventArgs e)
{
ButtonCommand?.Execute(Cipher);
}

View File

@@ -0,0 +1,21 @@
using Bit.App.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedSearchBar : SearchBar
{
public ExtendedSearchBar()
{
if(Device.RuntimePlatform == Device.iOS)
{
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService", true);
if(!deviceActionService?.UsingDarkTheme() ?? false)
{
TextColor = Color.Black;
}
}
}
}
}

View File

@@ -1,10 +0,0 @@
namespace Bit.App.Migration.Abstractions
{
public interface IOldSecureStorageService
{
bool Contains(string key);
void Delete(string key);
byte[] Retrieve(string key);
void Store(string key, byte[] dataBytes);
}
}

View File

@@ -1,199 +0,0 @@
using Bit.App.Migration.Models;
using Bit.Core.Enums;
using PCLCrypto;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Bit.App.Migration
{
public static class Crypto
{
public static CipherString AesCbcEncrypt(byte[] plainBytes, SymmetricCryptoKey key)
{
var parts = AesCbcEncryptToParts(plainBytes, key);
return new CipherString(parts.Item1, Convert.ToBase64String(parts.Item2),
Convert.ToBase64String(parts.Item4), parts.Item3 != null ? Convert.ToBase64String(parts.Item3) : null);
}
public static byte[] AesCbcEncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key)
{
var parts = AesCbcEncryptToParts(plainBytes, key);
var macLength = parts.Item3?.Length ?? 0;
var encBytes = new byte[1 + parts.Item2.Length + macLength + parts.Item4.Length];
encBytes[0] = (byte)parts.Item1;
parts.Item2.CopyTo(encBytes, 1);
if(parts.Item3 != null)
{
parts.Item3.CopyTo(encBytes, 1 + parts.Item2.Length);
}
parts.Item4.CopyTo(encBytes, 1 + parts.Item2.Length + macLength);
return encBytes;
}
private static Tuple<EncryptionType, byte[], byte[], byte[]> AesCbcEncryptToParts(byte[] plainBytes,
SymmetricCryptoKey key)
{
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(plainBytes == null)
{
throw new ArgumentNullException(nameof(plainBytes));
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var iv = RandomBytes(provider.BlockLength);
var ct = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv);
var mac = key.MacKey != null ? ComputeMac(ct, iv, key.MacKey) : null;
return new Tuple<EncryptionType, byte[], byte[], byte[]>(key.EncryptionType, iv, mac, ct);
}
public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
{
if(encyptedValue == null)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
return AesCbcDecrypt(encyptedValue.EncryptionType, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes, encyptedValue.MacBytes, key);
}
public static byte[] AesCbcDecrypt(EncryptionType type, byte[] ct, byte[] iv, byte[] mac,
SymmetricCryptoKey key)
{
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(ct == null)
{
throw new ArgumentNullException(nameof(ct));
}
if(iv == null)
{
throw new ArgumentNullException(nameof(iv));
}
if(key.MacKey != null && mac == null)
{
throw new ArgumentNullException(nameof(mac));
}
if(key.EncryptionType != type)
{
throw new InvalidOperationException(nameof(type));
}
if(key.MacKey != null && mac != null)
{
var computedMacBytes = ComputeMac(ct, iv, key.MacKey);
if(!MacsEqual(computedMacBytes, mac))
{
throw new InvalidOperationException("MAC failed.");
}
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, ct, iv);
return decryptedBytes;
}
public static byte[] RandomBytes(int length)
{
return WinRTCrypto.CryptographicBuffer.GenerateRandom(length);
}
public static byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
if(ctBytes == null)
{
throw new ArgumentNullException(nameof(ctBytes));
}
if(ivBytes == null)
{
throw new ArgumentNullException(nameof(ivBytes));
}
return ComputeMac(ivBytes.Concat(ctBytes), macKey);
}
public static byte[] ComputeMac(IEnumerable<byte> dataBytes, byte[] macKey)
{
if(macKey == null)
{
throw new ArgumentNullException(nameof(macKey));
}
if(dataBytes == null)
{
throw new ArgumentNullException(nameof(dataBytes));
}
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(dataBytes.ToArray());
var mac = hasher.GetValueAndReset();
return mac;
}
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
public static bool MacsEqual(byte[] mac1, byte[] mac2)
{
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(RandomBytes(32));
hasher.Append(mac1);
mac1 = hasher.GetValueAndReset();
hasher.Append(mac2);
mac2 = hasher.GetValueAndReset();
if(mac1.Length != mac2.Length)
{
return false;
}
for(int i = 0; i < mac2.Length; i++)
{
if(mac1[i] != mac2[i])
{
return false;
}
}
return true;
}
// ref: https://tools.ietf.org/html/rfc5869
public static byte[] HkdfExpand(byte[] prk, byte[] info, int size)
{
var hashLen = 32; // sha256
var okm = new byte[size];
var previousT = new byte[0];
var n = (int)Math.Ceiling((double)size / hashLen);
for(int i = 0; i < n; i++)
{
var t = new byte[previousT.Length + info.Length + 1];
previousT.CopyTo(t, 0);
info.CopyTo(t, previousT.Length);
t[t.Length - 1] = (byte)(i + 1);
previousT = ComputeMac(t, prk);
previousT.CopyTo(okm, i * hashLen);
}
return okm;
}
}
}

View File

@@ -1,196 +0,0 @@
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using System;
using System.Text;
using System.Threading.Tasks;
namespace Bit.App.Migration
{
public static class MigrationHelpers
{
public static bool Migrating = false;
public static bool NeedsMigration()
{
return ServiceContainer.Resolve<SettingsShim>("settingsShim")
.GetValueOrDefault(Constants.OldUserIdKey, null) != null; ;
}
public static async Task<bool> PerformMigrationAsync()
{
if(!NeedsMigration() || Migrating)
{
return false;
}
Migrating = true;
var settingsShim = ServiceContainer.Resolve<SettingsShim>("settingsShim");
var oldSecureStorageService = ServiceContainer.Resolve<Abstractions.IOldSecureStorageService>(
"oldSecureStorageService");
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
var userService = ServiceContainer.Resolve<IUserService>("userService");
var environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
var passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
var syncService = ServiceContainer.Resolve<ISyncService>("syncService");
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
// Get old data
var oldTokenBytes = oldSecureStorageService.Retrieve("accessToken");
var oldToken = oldTokenBytes == null ? null : Encoding.UTF8.GetString(
oldTokenBytes, 0, oldTokenBytes.Length);
var oldKeyBytes = oldSecureStorageService.Retrieve("key");
var oldKey = oldKeyBytes == null ? null : new Models.SymmetricCryptoKey(oldKeyBytes);
var oldUserId = settingsShim.GetValueOrDefault("userId", null);
var isAuthenticated = oldKey != null && !string.IsNullOrWhiteSpace(oldToken) &&
!string.IsNullOrWhiteSpace(oldUserId);
if(!isAuthenticated)
{
Migrating = false;
return false;
}
var oldRefreshTokenBytes = oldSecureStorageService.Retrieve("refreshToken");
var oldRefreshToken = oldRefreshTokenBytes == null ? null : Encoding.UTF8.GetString(
oldRefreshTokenBytes, 0, oldRefreshTokenBytes.Length);
var oldPinBytes = oldSecureStorageService.Retrieve("pin");
var oldPin = oldPinBytes == null ? null : Encoding.UTF8.GetString(
oldPinBytes, 0, oldPinBytes.Length);
var oldEncKey = settingsShim.GetValueOrDefault("encKey", null);
var oldEncPrivateKey = settingsShim.GetValueOrDefault("encPrivateKey", null);
var oldEmail = settingsShim.GetValueOrDefault("email", null);
var oldKdf = (KdfType)settingsShim.GetValueOrDefault("kdf", (int)KdfType.PBKDF2_SHA256);
var oldKdfIterations = settingsShim.GetValueOrDefault("kdfIterations", 5000);
var oldTwoFactorTokenBytes = oldSecureStorageService.Retrieve(
string.Format("twoFactorToken_{0}", Convert.ToBase64String(Encoding.UTF8.GetBytes(oldEmail))));
var oldTwoFactorToken = oldTwoFactorTokenBytes == null ? null : Encoding.UTF8.GetString(
oldTwoFactorTokenBytes, 0, oldTwoFactorTokenBytes.Length);
var oldAppIdBytes = oldSecureStorageService.Retrieve("appId");
var oldAppId = oldAppIdBytes == null ? null : new Guid(oldAppIdBytes).ToString();
var oldAnonAppIdBytes = oldSecureStorageService.Retrieve("anonymousAppId");
var oldAnonAppId = oldAnonAppIdBytes == null ? null : new Guid(oldAnonAppIdBytes).ToString();
var oldFingerprint = settingsShim.GetValueOrDefault("setting:fingerprintUnlockOn", false);
// Save settings
await storageService.SaveAsync(Constants.AccessibilityAutofillPersistNotificationKey,
settingsShim.GetValueOrDefault("setting:persistNotification", false));
await storageService.SaveAsync(Constants.AccessibilityAutofillPasswordFieldKey,
settingsShim.GetValueOrDefault("setting:autofillPasswordField", false));
await storageService.SaveAsync(Constants.DisableAutoTotpCopyKey,
settingsShim.GetValueOrDefault("setting:disableAutoCopyTotp", false));
await storageService.SaveAsync(Constants.DisableFaviconKey,
settingsShim.GetValueOrDefault("setting:disableWebsiteIcons", false));
await storageService.SaveAsync(Constants.AddSitePromptShownKey,
settingsShim.GetValueOrDefault("addedSiteAlert", false));
await storageService.SaveAsync(Constants.PushInitialPromptShownKey,
settingsShim.GetValueOrDefault("push:initialPromptShown", false));
await storageService.SaveAsync(Constants.PushCurrentTokenKey,
settingsShim.GetValueOrDefault("push:currentToken", null));
await storageService.SaveAsync(Constants.PushRegisteredTokenKey,
settingsShim.GetValueOrDefault("push:registeredToken", null));
// For some reason "push:lastRegistrationDate" isn't getting pulled from settingsShim correctly.
// We don't really need it anyways.
// var lastReg = settingsShim.GetValueOrDefault("push:lastRegistrationDate", DateTime.MinValue);
// await storageService.SaveAsync(Constants.PushLastRegistrationDateKey, lastReg);
await storageService.SaveAsync("rememberedEmail",
settingsShim.GetValueOrDefault("other:lastLoginEmail", null));
await environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
{
Base = settingsShim.GetValueOrDefault("other:baseUrl", null),
Api = settingsShim.GetValueOrDefault("other:apiUrl", null),
WebVault = settingsShim.GetValueOrDefault("other:webVaultUrl", null),
Identity = settingsShim.GetValueOrDefault("other:identityUrl", null),
Icons = settingsShim.GetValueOrDefault("other:iconsUrl", null)
});
await passwordGenerationService.SaveOptionsAsync(new Core.Models.Domain.PasswordGenerationOptions
{
Ambiguous = settingsShim.GetValueOrDefault("pwGenerator:ambiguous", false),
Length = settingsShim.GetValueOrDefault("pwGenerator:length", 15),
Uppercase = settingsShim.GetValueOrDefault("pwGenerator:uppercase", true),
Lowercase = settingsShim.GetValueOrDefault("pwGenerator:lowercase", true),
Number = settingsShim.GetValueOrDefault("pwGenerator:numbers", true),
MinNumber = settingsShim.GetValueOrDefault("pwGenerator:minNumbers", 0),
Special = settingsShim.GetValueOrDefault("pwGenerator:special", true),
MinSpecial = settingsShim.GetValueOrDefault("pwGenerator:minSpecial", 0),
WordSeparator = "-",
NumWords = 3
});
// Save lock options
int? lockOptionsSeconds = settingsShim.GetValueOrDefault("setting:lockSeconds", -10);
if(lockOptionsSeconds == -10)
{
lockOptionsSeconds = 60 * 15;
}
else if(lockOptionsSeconds == -1)
{
lockOptionsSeconds = null;
}
await storageService.SaveAsync(Constants.LockOptionKey,
lockOptionsSeconds == null ? (int?)null : lockOptionsSeconds.Value / 60);
// Save app ids
await storageService.SaveAsync("appId", oldAppId);
await storageService.SaveAsync("anonymousAppId", oldAnonAppId);
// Save new authed data
await tokenService.SetTwoFactorTokenAsync(oldTwoFactorToken, oldEmail);
await tokenService.SetTokensAsync(oldToken, oldRefreshToken);
await userService.SetInformationAsync(oldUserId, oldEmail, oldKdf, oldKdfIterations);
var newKey = new Core.Models.Domain.SymmetricCryptoKey(oldKey.Key);
await cryptoService.SetKeyAsync(newKey);
// Key hash is unavailable in old version, store old key until we can move it to key hash
await secureStorageService.SaveAsync("oldKey", newKey.KeyB64);
await cryptoService.SetEncKeyAsync(oldEncKey);
await cryptoService.SetEncPrivateKeyAsync(oldEncPrivateKey);
// Save fingerprint/pin
if(oldFingerprint)
{
await storageService.SaveAsync(Constants.FingerprintUnlockKey, true);
}
else if(!string.IsNullOrWhiteSpace(oldPin))
{
var pinKey = await cryptoService.MakePinKeyAysnc(oldPin, oldEmail, oldKdf, oldKdfIterations);
var pinProtectedKey = await cryptoService.EncryptAsync(oldKeyBytes, pinKey);
await storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString);
}
// Post migration tasks
await cryptoService.ToggleKeyAsync();
await storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow.AddYears(-1));
await lockService.CheckLockAsync();
// Remove "needs migration" flag
settingsShim.Remove(Constants.OldUserIdKey);
await storageService.SaveAsync(Constants.MigratedFromV1, true);
Migrating = false;
messagingService.Send("migrated");
if(Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
{
var task = Task.Run(() => syncService.FullSyncAsync(true));
}
return true;
}
}
}

View File

@@ -1,117 +0,0 @@
using System;
using Bit.Core.Enums;
namespace Bit.App.Migration.Models
{
public class CipherString
{
private string _decryptedValue;
public CipherString(string encryptedString)
{
if(string.IsNullOrWhiteSpace(encryptedString))
{
throw new ArgumentException(nameof(encryptedString));
}
var headerPieces = encryptedString.Split('.');
string[] encPieces;
EncryptionType encType;
if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out encType))
{
EncryptionType = encType;
encPieces = headerPieces[1].Split('|');
}
else if(headerPieces.Length == 1)
{
encPieces = headerPieces[0].Split('|');
EncryptionType = encPieces.Length == 3 ? EncryptionType.AesCbc128_HmacSha256_B64 :
EncryptionType.AesCbc256_B64;
}
else
{
throw new ArgumentException("Malformed header.");
}
switch(EncryptionType)
{
case EncryptionType.AesCbc256_B64:
if(encPieces.Length != 2)
{
throw new ArgumentException("Malformed encPieces.");
}
InitializationVector = encPieces[0];
CipherText = encPieces[1];
break;
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if(encPieces.Length != 3)
{
throw new ArgumentException("Malformed encPieces.");
}
InitializationVector = encPieces[0];
CipherText = encPieces[1];
Mac = encPieces[2];
break;
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha1_B64:
if(encPieces.Length != 1)
{
throw new ArgumentException("Malformed encPieces.");
}
CipherText = encPieces[0];
break;
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
if(encPieces.Length != 2)
{
throw new ArgumentException("Malformed encPieces.");
}
CipherText = encPieces[0];
Mac = encPieces[1];
break;
default:
throw new ArgumentException("Unknown encType.");
}
EncryptedString = encryptedString;
}
public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText,
string mac = null)
{
if(string.IsNullOrWhiteSpace(initializationVector))
{
throw new ArgumentNullException(nameof(initializationVector));
}
if(string.IsNullOrWhiteSpace(cipherText))
{
throw new ArgumentNullException(nameof(cipherText));
}
EncryptionType = encryptionType;
EncryptedString = string.Format("{0}.{1}|{2}", (byte)EncryptionType, initializationVector, cipherText);
if(!string.IsNullOrWhiteSpace(mac))
{
EncryptedString = string.Format("{0}|{1}", EncryptedString, mac);
}
CipherText = cipherText;
InitializationVector = initializationVector;
Mac = mac;
}
public EncryptionType EncryptionType { get; private set; }
public string EncryptedString { get; private set; }
public string InitializationVector { get; private set; }
public string CipherText { get; private set; }
public string Mac { get; private set; }
public byte[] InitializationVectorBytes => string.IsNullOrWhiteSpace(InitializationVector) ?
null : Convert.FromBase64String(InitializationVector);
public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);
}
}

View File

@@ -1,62 +0,0 @@
using Bit.Core.Enums;
using System;
using System.Linq;
namespace Bit.App.Migration.Models
{
public class SymmetricCryptoKey
{
public SymmetricCryptoKey(byte[] rawBytes, EncryptionType? encType = null)
{
if(rawBytes == null || rawBytes.Length == 0)
{
throw new Exception("Must provide keyBytes.");
}
if(encType == null)
{
if(rawBytes.Length == 32)
{
encType = EncryptionType.AesCbc256_B64;
}
else if(rawBytes.Length == 64)
{
encType = EncryptionType.AesCbc256_HmacSha256_B64;
}
else
{
throw new Exception("Unable to determine encType.");
}
}
EncryptionType = encType.Value;
Key = rawBytes;
if(EncryptionType == EncryptionType.AesCbc256_B64 && Key.Length == 32)
{
EncKey = Key;
MacKey = null;
}
else if(EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
{
EncKey = Key.Take(16).ToArray();
MacKey = Key.Skip(16).Take(16).ToArray();
}
else if(EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
{
EncKey = Key.Take(32).ToArray();
MacKey = Key.Skip(32).Take(32).ToArray();
}
else
{
throw new Exception("Unsupported encType/key length.");
}
}
public byte[] Key { get; set; }
public string B64Key => Convert.ToBase64String(Key);
public byte[] EncKey { get; set; }
public byte[] MacKey { get; set; }
public EncryptionType EncryptionType { get; set; }
}
}

View File

@@ -1,67 +0,0 @@
using System;
namespace Bit.App.Migration
{
public class SettingsShim
{
public bool Contains(string key)
{
return Xamarin.Essentials.Preferences.ContainsKey(key);
}
public string GetValueOrDefault(string key, string defaultValue)
{
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public DateTime GetValueOrDefault(string key, DateTime defaultValue)
{
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public bool GetValueOrDefault(string key, bool defaultValue)
{
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public int GetValueOrDefault(string key, int defaultValue)
{
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public long GetValueOrDefault(string key, long defaultValue)
{
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public void AddOrUpdateValue(string key, string value)
{
Xamarin.Essentials.Preferences.Set(key, value);
}
public void AddOrUpdateValue(string key, DateTime value)
{
Xamarin.Essentials.Preferences.Set(key, value);
}
public void AddOrUpdateValue(string key, bool value)
{
Xamarin.Essentials.Preferences.Set(key, value);
}
public void AddOrUpdateValue(string key, long value)
{
Xamarin.Essentials.Preferences.Set(key, value);
}
public void AddOrUpdateValue(string key, int value)
{
Xamarin.Essentials.Preferences.Set(key, value);
}
public void Remove(string key)
{
Xamarin.Essentials.Preferences.Remove(key);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Models
{
public class PreviousPageInfo
{
public string Page { get; set; }
public string CipherId { get; set; }
public string SearchText { get; set; }
}
}

View File

@@ -21,7 +21,7 @@
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n SelfHostedEnvironment}"
<Label Text="{u:I18n SelfHostedEnvironment, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row">
@@ -42,7 +42,7 @@
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n CustomEnvironment}"
<Label Text="{u:I18n CustomEnvironment, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row">

View File

@@ -1,14 +1,19 @@
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class EnvironmentPage : BaseContentPage
{
private EnvironmentPageViewModel _vm;
private readonly IMessagingService _messagingService;
private readonly EnvironmentPageViewModel _vm;
public EnvironmentPage()
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
InitializeComponent();
_vm = BindingContext as EnvironmentPageViewModel;
_vm.Page = this;
@@ -33,10 +38,11 @@ namespace Bit.App.Pages
}
}
private async void Close_Clicked(object sender, System.EventArgs e)
private async void Close_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
}
}

View File

@@ -60,7 +60,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
}

View File

@@ -16,7 +16,16 @@
<controls:FaButton Text="&#xf013;"
StyleClass="btn-muted, btn-icon, btn-icon-platform"
HorizontalOptions="Start"
Clicked="Settings_Clicked" />
Clicked="Settings_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}">
<controls:FaButton.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0, 10, 0, 0" />
<On Platform="Android" Value="0" />
</OnPlatform>
</controls:FaButton.Margin>
</controls:FaButton>
<StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
<Image
x:Name="_logo"

View File

@@ -1,4 +1,6 @@
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
@@ -7,12 +9,14 @@ namespace Bit.App.Pages
{
public partial class HomePage : BaseContentPage
{
private IMessagingService _messagingService;
public HomePage()
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", false);
InitializeComponent();
var theme = ThemeManager.GetTheme();
var darkbasedTheme = theme == "dark" || theme == "black" || theme == "nord";
_logo.Source = darkbasedTheme ? "logo_white.png" : "logo.png";
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
}
public async Task DismissRegisterPageAndLogInAsync(string email)
@@ -21,6 +25,12 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(new LoginPage(email)));
}
protected override void OnAppearing()
{
base.OnAppearing();
_messagingService.Send("showStatusBar", false);
}
private void LogIn_Clicked(object sender, EventArgs e)
{
if(DoOnce())

View File

@@ -45,6 +45,8 @@
Text="{Binding Pin}"
StyleClass="box-value"
Keyboard="Numeric"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
@@ -56,7 +58,9 @@
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Grid StyleClass="box-row" IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}">
<Grid.RowDefinitions>
@@ -76,6 +80,8 @@
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
@@ -87,7 +93,9 @@
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Label
Text="{Binding LockedVerifyText}"

View File

@@ -1,4 +1,7 @@
using Bit.App.Models;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
@@ -7,6 +10,7 @@ namespace Bit.App.Pages
{
public partial class LockPage : BaseContentPage
{
private readonly IStorageService _storageService;
private readonly AppOptions _appOptions;
private readonly bool _autoPromptFingerprint;
private readonly LockPageViewModel _vm;
@@ -16,28 +20,13 @@ namespace Bit.App.Pages
public LockPage(AppOptions appOptions = null, bool autoPromptFingerprint = true)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_appOptions = appOptions;
_autoPromptFingerprint = autoPromptFingerprint;
InitializeComponent();
_vm = BindingContext as LockPageViewModel;
_vm.Page = this;
_vm.UnlockedAction = () =>
{
if(_appOptions != null)
{
if(_appOptions.FromAutofillFramework && _appOptions.SaveType.HasValue)
{
Application.Current.MainPage = new NavigationPage(new AddEditPage(appOptions: _appOptions));
return;
}
else if(_appOptions.Uri != null)
{
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(_appOptions));
return;
}
}
Application.Current.MainPage = new TabsPage(_appOptions);
};
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
MasterPasswordEntry = _masterPassword;
PinEntry = _pin;
}
@@ -87,10 +76,7 @@ namespace Bit.App.Pages
var tasks = Task.Run(async () =>
{
await Task.Delay(50);
Device.BeginInvokeOnMainThread(async () =>
{
await _vm.SubmitAsync();
});
Device.BeginInvokeOnMainThread(async () => await _vm.SubmitAsync());
});
}
}
@@ -110,5 +96,28 @@ namespace Bit.App.Pages
await _vm.PromptFingerprintAsync();
}
}
private async Task UnlockedAsync()
{
if(_appOptions != null)
{
if(_appOptions.FromAutofillFramework && _appOptions.SaveType.HasValue)
{
Application.Current.MainPage = new NavigationPage(new AddEditPage(appOptions: _appOptions));
return;
}
else if(_appOptions.Uri != null)
{
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(_appOptions));
return;
}
}
var previousPage = await _storageService.GetAsync<PreviousPageInfo>(Constants.PreviousPageKey);
if(previousPage != null)
{
await _storageService.RemoveAsync(Constants.PreviousPageKey);
}
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
}
}

View File

@@ -23,8 +23,8 @@ namespace Bit.App.Pages
private readonly IMessagingService _messagingService;
private readonly IStorageService _secureStorageService;
private readonly IEnvironmentService _environmentService;
private readonly IStateService _stateService;
private bool _hasKey;
private string _email;
private bool _showPassword;
private bool _pinLock;
@@ -46,6 +46,7 @@ namespace Bit.App.Pages
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
@@ -102,8 +103,7 @@ namespace Bit.App.Pages
public async Task InitAsync(bool autoPromptFingerprint)
{
_pinSet = await _lockService.IsPinLockSetAsync();
_hasKey = await _cryptoService.HasKeyAsync();
PinLock = (_pinSet.Item1 && _hasKey) || _pinSet.Item2;
PinLock = (_pinSet.Item1 && _lockService.PinProtectedKey != null) || _pinSet.Item2;
FingerprintLock = await _lockService.IsFingerprintLockSetAsync();
_email = await _userService.GetEmailAsync();
var webVault = _environmentService.GetWebVaultUrl();
@@ -126,8 +126,19 @@ namespace Bit.App.Pages
if(FingerprintLock)
{
FingerprintButtonText = _deviceActionService.SupportsFaceId() ? AppResources.UseFaceIDToUnlock :
AppResources.UseFingerprintToUnlock;
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
if(Device.RuntimePlatform == Device.iOS && supportsFace)
{
FingerprintButtonText = AppResources.UseFaceIDToUnlock;
}
else if(Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric())
{
FingerprintButtonText = AppResources.UseBiometricsToUnlock;
}
else
{
FingerprintButtonText = AppResources.UseFingerprintToUnlock;
}
if(autoPromptFingerprint)
{
var tasks = Task.Run(async () =>
@@ -167,14 +178,17 @@ namespace Bit.App.Pages
{
if(_pinSet.Item1)
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
_lockService.PinProtectedKey);
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin));
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
failed = decPin != Pin;
_lockService.PinLocked = failed;
if(!failed)
{
Pin = string.Empty;
DoContinue();
await SetKeyAndContinueAsync(key);
}
}
else
@@ -219,6 +233,15 @@ namespace Bit.App.Pages
}
if(storedKeyHash != null && keyHash != null && storedKeyHash == keyHash)
{
if(_pinSet.Item1)
{
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
_lockService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
}
MasterPassword = string.Empty;
await SetKeyAndContinueAsync(key);
}
@@ -254,7 +277,7 @@ namespace Bit.App.Pages
{
return;
}
var success = await _platformUtilsService.AuthenticateFingerprintAsync(null,
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword, () =>
{
var page = Page as LockPage;
@@ -270,21 +293,25 @@ namespace Bit.App.Pages
_lockService.FingerprintLocked = !success;
if(success)
{
DoContinue();
await DoContinueAsync();
}
}
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
{
if(!_hasKey)
var hasKey = await _cryptoService.HasKeyAsync();
if(!hasKey)
{
await _cryptoService.SetKeyAsync(key);
}
DoContinue();
await DoContinueAsync();
}
private void DoContinue()
private async Task DoContinueAsync()
{
_lockService.FingerprintLocked = false;
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
_messagingService.Send("unlocked");
UnlockedAction?.Invoke();
}

View File

@@ -55,6 +55,8 @@
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
@@ -66,7 +68,9 @@
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout Padding="10, 0">

View File

@@ -1,14 +1,19 @@
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class LoginPage : BaseContentPage
{
private LoginPageViewModel _vm;
private readonly IMessagingService _messagingService;
private readonly LoginPageViewModel _vm;
public LoginPage(string email = null)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
InitializeComponent();
_vm = BindingContext as LoginPageViewModel;
_vm.Page = this;
@@ -55,10 +60,11 @@ namespace Bit.App.Pages
}
}
private async void Close_Clicked(object sender, System.EventArgs e)
private async void Close_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
}
}

View File

@@ -1,5 +1,6 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;
@@ -18,6 +19,7 @@ namespace Bit.App.Pages
private readonly ISyncService _syncService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private bool _showPassword;
private string _email;
@@ -30,6 +32,7 @@ namespace Bit.App.Pages
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword);
@@ -123,6 +126,8 @@ namespace Bit.App.Pages
}
else
{
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
Application.Current.MainPage = new TabsPage();
}
@@ -130,7 +135,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}

View File

@@ -55,6 +55,8 @@
x:Name="_masterPassword"
Text="{Binding MasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
@@ -64,7 +66,9 @@
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<Label
Text="{u:I18n MasterPasswordDescription}"
@@ -89,6 +93,8 @@
x:Name="_confirmMasterPassword"
Text="{Binding ConfirmMasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
@@ -98,7 +104,9 @@
Command="{Binding ToggleConfirmPasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
<StackLayout StyleClass="box-row">
<Label

View File

@@ -1,14 +1,19 @@
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class RegisterPage : BaseContentPage
{
private RegisterPageViewModel _vm;
private readonly IMessagingService _messagingService;
private readonly RegisterPageViewModel _vm;
public RegisterPage(HomePage homePage)
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", true);
InitializeComponent();
_vm = BindingContext as RegisterPageViewModel;
_vm.Page = this;
@@ -51,10 +56,11 @@ namespace Bit.App.Pages
}
}
private async void Close_Clicked(object sender, System.EventArgs e)
private async void Close_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
_messagingService.Send("showStatusBar", false);
await Navigation.PopModalAsync();
}
}

View File

@@ -135,7 +135,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}

View File

@@ -76,6 +76,9 @@
x:Name="_yubikeyTokenEntry"
Text="{Binding Token}"
StyleClass="box-value"
IsPassword="True"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
</StackLayout>
@@ -124,6 +127,10 @@
IsVisible="{Binding EmailMethod}"
Clicked="ResendEmail_Clicked"
Margin="10, 0"></Button>
<Button Text="{u:I18n TryAgain}"
IsVisible="{Binding ShowTryAgain}"
Clicked="TryAgain_Clicked"
Margin="10, 0"></Button>
<Button Text="{u:I18n UseAnotherTwoStepMethod}"
Clicked="Methods_Clicked"
Margin="10, 0"></Button>

View File

@@ -55,11 +55,13 @@ namespace Bit.App.Pages
{
if(message.Command == "gotYubiKeyOTP")
{
if(_vm.YubikeyMethod)
var token = (string)message.Data;
if(_vm.YubikeyMethod && !string.IsNullOrWhiteSpace(token) &&
token.Length == 44 && !token.Contains(" "))
{
Device.BeginInvokeOnMainThread(async () =>
{
_vm.Token = (string)message.Data;
_vm.Token = token;
await _vm.SubmitAsync();
});
}
@@ -138,5 +140,16 @@ namespace Bit.App.Pages
await Navigation.PopModalAsync();
}
}
private void TryAgain_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
if(_vm.YubikeyMethod)
{
_messagingService.Send("listenYubiKeyOTP", true);
}
}
}
}
}

View File

@@ -1,5 +1,6 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@@ -23,6 +24,7 @@ namespace Bit.App.Pages
private readonly IEnvironmentService _environmentService;
private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly IStateService _stateService;
private bool _u2fSupported = false;
private TwoFactorProviderType? _selectedProviderType;
@@ -40,6 +42,7 @@ namespace Bit.App.Pages
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
@@ -66,6 +69,8 @@ namespace Bit.App.Pages
public bool TotpMethod => AuthenticatorMethod || EmailMethod;
public bool ShowTryAgain => YubikeyMethod && Device.RuntimePlatform == Device.iOS;
public string YubikeyInstruction => Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
AppResources.YubiKeyInstruction;
@@ -79,6 +84,7 @@ namespace Bit.App.Pages
nameof(YubikeyMethod),
nameof(AuthenticatorMethod),
nameof(TotpMethod),
nameof(ShowTryAgain),
});
}
public Command SubmitCommand { get; }
@@ -157,7 +163,7 @@ namespace Bit.App.Pages
{
_messagingService.Send("listenYubiKeyOTP", false);
}
if(DuoMethod)
if(SelectedProviderType == null || DuoMethod)
{
page.RemoveContinueButton();
}
@@ -200,13 +206,18 @@ namespace Bit.App.Pages
var task = Task.Run(() => _syncService.FullSyncAsync(true));
_messagingService.Send("listenYubiKeyOTP", false);
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
Application.Current.MainPage = new TabsPage();
}
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}

View File

@@ -4,6 +4,8 @@ using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
@@ -11,8 +13,16 @@ namespace Bit.App.Pages
{
private IStorageService _storageService;
protected int AndroidShowModalAnimationDelay = 400;
protected int AndroidShowPageAnimationDelay = 100;
protected int ShowModalAnimationDelay = 400;
protected int ShowPageAnimationDelay = 100;
public BaseContentPage()
{
if (Device.RuntimePlatform == Device.iOS)
{
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
}
}
public DateTime? LastPageAction { get; set; }
@@ -77,21 +87,16 @@ namespace Bit.App.Pages
}
await Task.Run(async () =>
{
await Task.Delay(fromModal ? AndroidShowModalAnimationDelay : AndroidShowPageAnimationDelay);
await Task.Delay(fromModal ? ShowModalAnimationDelay : ShowPageAnimationDelay);
Device.BeginInvokeOnMainThread(async () => await DoWorkAsync());
});
}
protected void RequestFocus(InputView input)
{
if(Device.RuntimePlatform == Device.iOS)
{
input.Focus();
return;
}
Task.Run(async () =>
{
await Task.Delay(AndroidShowModalAnimationDelay);
await Task.Delay(ShowModalAnimationDelay);
Device.BeginInvokeOnMainThread(() => input.Focus());
});
}

View File

@@ -84,7 +84,9 @@
CommandParameter="{Binding .}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyPassword}" />
</Grid>
</ViewCell>
</DataTemplate>

View File

@@ -15,6 +15,8 @@
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Name="_closeItem" x:Key="closeItem" />
<ToolbarItem Text="{u:I18n Select}"
Clicked="Select_Clicked"
Order="Primary"
@@ -52,7 +54,7 @@
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Options}"
<Label Text="{u:I18n Options, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
@@ -60,6 +62,7 @@
Text="{u:I18n Type}"
StyleClass="box-label" />
<Picker
x:Name="_typePicker"
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
SelectedIndex="{Binding TypeSelectedIndex}"
StyleClass="box-value" />
@@ -93,8 +96,31 @@
StyleClass="box-label" />
<Entry
Text="{Binding WordSeparator}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
StyleClass="box-value" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n Capitalize}"
StyleClass="box-label, box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding Capitalize}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
<Label
Text="{u:I18n IncludeNumber}"
StyleClass="box-label, box-label-regular"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding IncludeNumber}"
StyleClass="box-value"
HorizontalOptions="End" />
</StackLayout>
</StackLayout>
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding IsPassword}">
<StackLayout StyleClass="box-row, box-row-slider">

View File

@@ -2,6 +2,8 @@
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
@@ -20,17 +22,29 @@ namespace Bit.App.Pages
_vm.Page = this;
_fromTabPage = fromTabPage;
_selectAction = selectAction;
var isIos = Device.RuntimePlatform == Device.iOS;
if(selectAction != null)
{
if(isIos)
{
ToolbarItems.Add(_closeItem);
}
ToolbarItems.Add(_selectItem);
}
if(Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_moreItem);
}
else
{
ToolbarItems.Add(_historyItem);
if(isIos)
{
ToolbarItems.Add(_moreItem);
}
else
{
ToolbarItems.Add(_historyItem);
}
}
if(isIos)
{
_typePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
}
}
@@ -79,7 +93,7 @@ namespace Bit.App.Pages
if(selection == AppResources.PasswordHistory)
{
var page = new GeneratorHistoryPage();
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
}
@@ -91,12 +105,20 @@ namespace Bit.App.Pages
private async void History_Clicked(object sender, EventArgs e)
{
var page = new GeneratorHistoryPage();
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
private async void LengthSlider_DragCompleted(object sender, EventArgs e)
{
await _vm.SliderChangedAsync();
}
private async void Close_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
await Navigation.PopModalAsync();
}
}
}
}

View File

@@ -3,9 +3,7 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms;
@@ -29,6 +27,8 @@ namespace Bit.App.Pages
private int _length = 5;
private int _numWords = 3;
private string _wordSeparator;
private bool _capitalize;
private bool _includeNumber;
private int _typeSelectedIndex;
private bool _doneIniting;
@@ -196,6 +196,32 @@ namespace Bit.App.Pages
}
}
public bool Capitalize
{
get => _capitalize;
set
{
if(SetProperty(ref _capitalize, value))
{
_options.Capitalize = value;
var task = SaveOptionsAsync();
}
}
}
public bool IncludeNumber
{
get => _includeNumber;
set
{
if(SetProperty(ref _includeNumber, value))
{
_options.Number = value;
var task = SaveOptionsAsync();
}
}
}
public int TypeSelectedIndex
{
get => _typeSelectedIndex;
@@ -273,6 +299,8 @@ namespace Bit.App.Pages
Uppercase = _options.Uppercase.GetValueOrDefault();
Lowercase = _options.Lowercase.GetValueOrDefault();
Length = _options.Length.GetValueOrDefault(5);
Capitalize = _options.Capitalize.GetValueOrDefault();
IncludeNumber = _options.IncludeNumber.GetValueOrDefault();
}
private void SetOptions()
@@ -288,6 +316,8 @@ namespace Bit.App.Pages
_options.Uppercase = Uppercase;
_options.Lowercase = Lowercase;
_options.Length = Length;
_options.Capitalize = Capitalize;
_options.IncludeNumber = IncludeNumber;
}
}
}

View File

@@ -0,0 +1,48 @@
<?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.AutofillPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
Title="{u:I18n PasswordAutofill}">
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout Spacing="5"
Padding="20, 20, 20, 30"
VerticalOptions="FillAndExpand">
<Label Text="{u:I18n ExtensionInstantAccess}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
StyleClass="text-lg"
Margin="0, 0, 0, 15" />
<Label Text="{u:I18n AutofillTurnOn}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
Margin="0, 0, 0, 15" />
<Label Text="{u:I18n AutofillTurnOn1}"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n AutofillTurnOn2}"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n AutofillTurnOn3}"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n AutofillTurnOn4}"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n AutofillTurnOn5}"
LineBreakMode="WordWrap" />
<Image Source="autofill-kb.png"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Margin="0, 10, 0, 0"
WidthRequest="290"
HeightRequest="252" />
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View File

@@ -0,0 +1,20 @@
using System;
namespace Bit.App.Pages
{
public partial class AutofillPage : BaseContentPage
{
public AutofillPage()
{
InitializeComponent();
}
private void Close_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
Navigation.PopModalAsync();
}
}
}
}

View File

@@ -0,0 +1,95 @@
<?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.ExtensionPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:ExtensionPageViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:ExtensionPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout Padding="0" Spacing="0" VerticalOptions="FillAndExpand">
<StackLayout Spacing="20"
Padding="20, 20, 20, 30"
VerticalOptions="FillAndExpand"
IsVisible="{Binding NotStarted}">
<Label Text="{u:I18n ExtensionInstantAccess}"
StyleClass="text-lg"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n ExtensionTurnOn}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<Image Source="ext-more.png"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Margin="0, -10, 0, 0"
WidthRequest="290"
HeightRequest="252" />
<Button Text="{u:I18n ExtensionEnable}"
Clicked="Show_Clicked"
VerticalOptions="End"
HorizontalOptions="Fill" />
</StackLayout>
<StackLayout Spacing="20"
Padding="20, 20, 20, 30"
VerticalOptions="FillAndExpand"
IsVisible="{Binding StartedAndNotActivated}">
<Label Text="{u:I18n ExtensionAlmostDone}"
StyleClass="text-lg"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n ExtensionTapIcon}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<Image Source="ext-act.png"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Margin="0, -10, 0, 0"
WidthRequest="290"
HeightRequest="252" />
<Button Text="{u:I18n ExtensionEnable}"
Clicked="Show_Clicked"
VerticalOptions="End"
HorizontalOptions="Fill" />
</StackLayout>
<StackLayout Spacing="20"
Padding="20, 20, 20, 30"
VerticalOptions="FillAndExpand"
IsVisible="{Binding StartedAndActivated}">
<Label Text="{u:I18n ExtensionReady}"
StyleClass="text-lg"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n ExtensionInSafari}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<Image Source="ext-use.png"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Margin="0, -10, 0, 0"
WidthRequest="290"
HeightRequest="252" />
<Button Text="{u:I18n ExntesionReenable}"
Clicked="Show_Clicked"
VerticalOptions="End"
HorizontalOptions="Fill" />
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View File

@@ -0,0 +1,38 @@
using System;
namespace Bit.App.Pages
{
public partial class ExtensionPage : BaseContentPage
{
private readonly ExtensionPageViewModel _vm;
public ExtensionPage()
{
InitializeComponent();
_vm = BindingContext as ExtensionPageViewModel;
_vm.Page = this;
}
protected async override void OnAppearing()
{
base.OnAppearing();
await _vm.InitAsync();
}
private void Show_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
_vm.ShowExtension();
}
}
private void Close_Clicked(object sender, EventArgs e)
{
if(DoOnce())
{
Navigation.PopModalAsync();
}
}
}
}

View File

@@ -0,0 +1,75 @@
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Threading.Tasks;
namespace Bit.App.Pages
{
public class ExtensionPageViewModel : BaseViewModel
{
private const string StartedKey = "appExtensionStarted";
private const string ActivatedKey = "appExtensionActivated";
private readonly IMessagingService _messagingService;
private readonly IStorageService _storageService;
private readonly IPlatformUtilsService _platformUtilsService;
private bool _started;
private bool _activated;
public ExtensionPageViewModel()
{
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
PageTitle = AppResources.AppExtension;
}
public bool Started
{
get => _started;
set => SetProperty(ref _started, value, additionalPropertyNames: new string[]
{
nameof(NotStarted),
nameof(StartedAndNotActivated),
nameof(StartedAndActivated)
});
}
public bool Activated
{
get => _activated;
set => SetProperty(ref _activated, value, additionalPropertyNames: new string[]
{
nameof(StartedAndNotActivated),
nameof(StartedAndActivated)
});
}
public bool NotStarted => !Started;
public bool StartedAndNotActivated => Started && !Activated;
public bool StartedAndActivated => Started && Activated;
public async Task InitAsync()
{
var started = await _storageService.GetAsync<bool?>(StartedKey);
var activated = await _storageService.GetAsync<bool?>(ActivatedKey);
Started = started.GetValueOrDefault();
Activated = activated.GetValueOrDefault();
}
public void ShowExtension()
{
_messagingService.Send("showAppExtension", this);
}
public void EnabledExtension(bool enabled)
{
Started = true;
if(!Activated && enabled)
{
Activated = enabled;
}
}
}
}

View File

@@ -21,6 +21,15 @@
IsDestructive="True"
x:Name="_deleteItem" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<ToolbarItem Icon="more_vert.png" Clicked="More_Clicked" Order="Primary" x:Name="_moreItem"
x:Key="moreItem"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView x:Name="_scrollView">
<StackLayout Spacing="20">

View File

@@ -1,4 +1,6 @@
using Xamarin.Forms;
using Bit.App.Resources;
using System.Collections.Generic;
using Xamarin.Forms;
namespace Bit.App.Pages
{
@@ -19,6 +21,10 @@ namespace Bit.App.Pages
{
ToolbarItems.Remove(_deleteItem);
}
if(_vm.EditMode && Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_moreItem);
}
if(Device.RuntimePlatform == Device.Android)
{
ToolbarItems.RemoveAt(0);
@@ -61,5 +67,20 @@ namespace Bit.App.Pages
await Navigation.PopModalAsync();
}
}
private async void More_Clicked(object sender, System.EventArgs e)
{
if(!DoOnce())
{
return;
}
var options = new List<string> { };
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
_vm.EditMode ? AppResources.Delete : null, options.ToArray());
if(selection == AppResources.Delete)
{
await _vm.DeleteAsync();
}
}
}
}

View File

@@ -93,7 +93,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
@@ -128,7 +132,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}

View File

@@ -10,14 +10,12 @@ namespace Bit.App.Pages
{
public class FoldersPageViewModel : BaseViewModel
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IFolderService _folderService;
private bool _showNoData;
public FoldersPageViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
PageTitle = AppResources.Folders;

View File

@@ -19,7 +19,7 @@
<ScrollView Padding="0, 0, 0, 20">
<StackLayout Padding="0" Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n Theme}"
StyleClass="box-label" />
@@ -35,7 +35,7 @@
x:Name="_themeDescriptionLabel" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n DefaultUriMatchDetection}"
StyleClass="box-label" />
@@ -50,7 +50,7 @@
StyleClass="box-footer-label" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n ClearClipboard}"
StyleClass="box-label" />
@@ -96,7 +96,7 @@
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n AutofillService}"
<Label Text="{u:I18n AutofillService, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">
@@ -134,7 +134,7 @@
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAccessibilitySettings}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n AutofillAccessibilityService}"
<Label Text="{u:I18n AutofillAccessibilityService, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch">

View File

@@ -2,6 +2,8 @@
using Bit.App.Resources;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
@@ -27,6 +29,12 @@ namespace Bit.App.Pages
_themeDescriptionLabel.Text = string.Concat(_themeDescriptionLabel.Text, " ",
AppResources.RestartIsRequired);
}
else
{
_themePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
}
}
protected async override void OnAppearing()

View File

@@ -46,6 +46,7 @@ namespace Bit.App.Pages
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
PageTitle = AppResources.Options;
var iosIos = Device.RuntimePlatform == Device.iOS;
ClearClipboardOptions = new List<KeyValuePair<int?, string>>
{
@@ -53,21 +54,21 @@ namespace Bit.App.Pages
new KeyValuePair<int?, string>(10, AppResources.TenSeconds),
new KeyValuePair<int?, string>(20, AppResources.TwentySeconds),
new KeyValuePair<int?, string>(30, AppResources.ThirtySeconds),
new KeyValuePair<int?, string>(60, AppResources.OneMinute),
new KeyValuePair<int?, string>(120, AppResources.TwoMinutes),
new KeyValuePair<int?, string>(300, AppResources.FiveMinutes),
new KeyValuePair<int?, string>(60, AppResources.OneMinute)
};
if(!iosIos)
{
ClearClipboardOptions.Add(new KeyValuePair<int?, string>(120, AppResources.TwoMinutes));
ClearClipboardOptions.Add(new KeyValuePair<int?, string>(300, AppResources.FiveMinutes));
}
ThemeOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(null, AppResources.Default),
new KeyValuePair<string, string>("light", AppResources.Light),
new KeyValuePair<string, string>("dark", AppResources.Dark),
new KeyValuePair<string, string>("black", AppResources.Black),
new KeyValuePair<string, string>("nord", "Nord"),
};
if(Device.RuntimePlatform == Device.Android)
{
ThemeOptions.Add(new KeyValuePair<string, string>("black", AppResources.Black));
}
ThemeOptions.Add(new KeyValuePair<string, string>("nord", "Nord"));
UriMatchOptions = new List<KeyValuePair<UriMatchType?, string>>
{
new KeyValuePair<UriMatchType?, string>(UriMatchType.Domain, AppResources.BaseDomain),
@@ -300,6 +301,11 @@ namespace Bit.App.Pages
await Task.Delay(1000);
}
_messagingService.Send("updatedTheme", theme);
if(Device.RuntimePlatform == Device.iOS)
{
await Task.Delay(500);
await _platformUtilsService.ShowDialogAsync(AppResources.ThemeAppliedOnRestart);
}
}
}

View File

@@ -47,7 +47,8 @@
<ListView
ItemsSource="{Binding GroupedItems}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
HasUnevenRows="True"
RowHeight="-1"
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
IsGroupingEnabled="True"
ItemSelected="RowSelected"
@@ -56,14 +57,18 @@
<ListView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:SettingsPageListGroup">
<ViewCell>
<StackLayout Padding="0" Spacing="0">
<BoxView StyleClass="list-section-separator"
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
<StackLayout StyleClass="list-row-header">
<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>
</ViewCell>
</DataTemplate>

View File

@@ -67,11 +67,11 @@ namespace Bit.App.Pages
}
else if(item.Name == AppResources.PasswordAutofill)
{
// await Navigation.PushModalAsync(new NavigationPage(new OptionsPage()));
await Navigation.PushModalAsync(new NavigationPage(new AutofillPage()));
}
else if(item.Name == AppResources.AppExtension)
{
// await Navigation.PushModalAsync(new NavigationPage(new OptionsPage()));
await Navigation.PushModalAsync(new NavigationPage(new ExtensionPage()));
}
else if(item.Name == AppResources.Options)
{
@@ -137,10 +137,22 @@ namespace Bit.App.Pages
{
await _vm.UpdatePinAsync();
}
else if(item.Name.Contains(AppResources.Fingerprint) || item.Name.Contains(AppResources.TouchID) ||
item.Name.Contains(AppResources.FaceID))
else
{
await _vm.UpdateFingerprintAsync();
var fingerprintName = AppResources.Fingerprint;
if(Device.RuntimePlatform == Device.iOS)
{
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
fingerprintName = supportsFace ? AppResources.FaceID : AppResources.TouchID;
}
else if(Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric())
{
fingerprintName = AppResources.Biometrics;
}
if(item.Name == string.Format(AppResources.UnlockWith, fingerprintName))
{
await _vm.UpdateFingerprintAsync();
}
}
}
}

View File

@@ -23,7 +23,6 @@ namespace Bit.App.Pages
private readonly IStorageService _storageService;
private readonly ISyncService _syncService;
private string _fingerprintName;
private bool _supportsFingerprint;
private bool _pin;
private bool _fingerprint;
@@ -57,19 +56,13 @@ namespace Bit.App.Pages
GroupedItems = new ExtendedObservableCollection<SettingsPageListGroup>();
PageTitle = AppResources.Settings;
_fingerprintName = AppResources.Fingerprint;
if(Device.RuntimePlatform == Device.iOS)
{
_fingerprintName = _deviceActionService.SupportsFaceId() ? AppResources.FaceID : AppResources.TouchID;
}
}
public ExtendedObservableCollection<SettingsPageListGroup> GroupedItems { get; set; }
public async Task InitAsync()
{
_supportsFingerprint = await _platformUtilsService.SupportsFingerprintAsync();
_supportsFingerprint = await _platformUtilsService.SupportsBiometricAsync();
var lastSync = await _syncService.GetLastSyncAsync();
if(lastSync != null)
{
@@ -221,21 +214,24 @@ namespace Bit.App.Pages
var masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
AppResources.Yes, AppResources.No);
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
var key = await _cryptoService.GetKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
if(masterPassOnRestart)
{
var encPin = await _cryptoService.EncryptAsync(pin);
await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString);
_lockService.PinProtectedKey = pinProtectedKey;
}
else
{
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
var key = await _cryptoService.GetKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
await _storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString);
}
}
@@ -246,8 +242,8 @@ namespace Bit.App.Pages
}
if(!_pin)
{
await _storageService.RemoveAsync(Constants.PinProtectedKey);
await _storageService.RemoveAsync(Constants.ProtectedPin);
await _cryptoService.ClearPinProtectedKeyAsync();
await _lockService.ClearAsync();
}
BuildList();
}
@@ -259,9 +255,9 @@ namespace Bit.App.Pages
{
_fingerprint = false;
}
else if(await _platformUtilsService.SupportsFingerprintAsync())
else if(await _platformUtilsService.SupportsBiometricAsync())
{
_fingerprint = await _platformUtilsService.AuthenticateFingerprintAsync(null,
_fingerprint = await _platformUtilsService.AuthenticateBiometricAsync(null,
_deviceActionService.DeviceType == Core.Enums.DeviceType.Android ? "." : null);
}
if(_fingerprint == current)
@@ -327,11 +323,21 @@ namespace Bit.App.Pages
new SettingsPageListItem { Name = AppResources.LockNow },
new SettingsPageListItem { Name = AppResources.TwoStepLogin }
};
if(_supportsFingerprint)
if(_supportsFingerprint || _fingerprint)
{
var fingerprintName = AppResources.Fingerprint;
if(Device.RuntimePlatform == Device.iOS)
{
fingerprintName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID :
AppResources.TouchID;
}
else if(Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric())
{
fingerprintName = AppResources.Biometrics;
}
var item = new SettingsPageListItem
{
Name = string.Format(AppResources.UnlockWith, _fingerprintName),
Name = string.Format(AppResources.UnlockWith, fingerprintName),
SubLabel = _fingerprint ? AppResources.Enabled : AppResources.Disabled
};
securityItems.Insert(1, item);

View File

@@ -70,7 +70,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
}

View File

@@ -10,9 +10,9 @@ namespace Bit.App.Pages
private NavigationPage _groupingsPage;
private NavigationPage _generatorPage;
public TabsPage(AppOptions appOptions = null)
public TabsPage(AppOptions appOptions = null, PreviousPageInfo previousPage = null)
{
_groupingsPage = new NavigationPage(new GroupingsPage(true))
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage))
{
Title = AppResources.MyVault,
Icon = "lock.png"

View File

@@ -57,7 +57,7 @@
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n ItemInformation}"
<Label Text="{u:I18n ItemInformation, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input"
@@ -112,28 +112,36 @@
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}" />
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf058;"
Command="{Binding CheckPasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CheckPassword}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf021;"
Command="{Binding GeneratePasswordCommand}"
Grid.Row="0"
Grid.Column="3"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n GeneratePassword}" />
</Grid>
<Grid StyleClass="box-row, box-row-input">
@@ -153,6 +161,8 @@
<controls:MonoEntry
x:Name="_loginTotpEntry"
Text="{Binding Cipher.Login.Totp}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0" />
@@ -162,7 +172,9 @@
Clicked="ScanTotp_Clicked"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ScanQrTitle}" />
</Grid>
</StackLayout>
<StackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
@@ -236,14 +248,18 @@
Grid.Row="1"
Grid.Column="0"
Keyboard="Numeric"
IsPassword="{Binding ShowCardCode, Converter={StaticResource inverseBool}}" />
IsPassword="{Binding ShowCardCode, Converter={StaticResource inverseBool}}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowCardCodeIcon}"
Command="{Binding ToggleCardCodeCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
</Grid>
</StackLayout>
<StackLayout IsVisible="{Binding IsIdentity}" Spacing="0" Padding="0">
@@ -335,6 +351,7 @@
StyleClass="box-label" />
<Entry
x:Name="_identityEmailEntry"
Keyboard="Email"
Text="{Binding Cipher.Identity.Email}"
StyleClass="box-value" />
</StackLayout>
@@ -414,7 +431,7 @@
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding IsLogin}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n URIs}"
<Label Text="{u:I18n URIs, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Uris}">
@@ -447,7 +464,9 @@
CommandParameter="{Binding .}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</Grid>
</DataTemplate>
</controls:RepeaterView.ItemTemplate>
@@ -457,7 +476,7 @@
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Miscellaneous}"
<Label Text="{u:I18n Miscellaneous, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
@@ -484,7 +503,7 @@
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Notes}"
<Label Text="{u:I18n Notes, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
@@ -494,10 +513,11 @@
Text="{Binding Cipher.Notes}"
StyleClass="box-value" />
</StackLayout>
<BoxView StyleClass="box-row-separator" IsVisible="{Binding ShowNotesSeparator}" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n CustomFields}"
<Label Text="{u:I18n CustomFields, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Fields}">
@@ -541,7 +561,9 @@
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding IsHiddenType}"
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}" />
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" />
<Switch
IsToggled="{Binding BooleanValue}"
Grid.Row="0"
@@ -555,7 +577,9 @@
IsVisible="{Binding IsHiddenType}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf013;"
@@ -563,7 +587,9 @@
CommandParameter="{Binding .}"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</Grid>
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsBooleanType}" />
</StackLayout>
@@ -575,7 +601,7 @@
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Ownership}"
<Label Text="{u:I18n Ownership, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
@@ -591,7 +617,7 @@
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowCollections}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Collections}"
<Label Text="{u:I18n Collections, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout Spacing="0" Padding="0"

View File

@@ -8,6 +8,8 @@ using Bit.Core.Utilities;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
@@ -39,7 +41,7 @@ namespace Bit.App.Pages
_vm = BindingContext as AddEditPageViewModel;
_vm.Page = this;
_vm.CipherId = cipherId;
_vm.FolderId = folderId;
_vm.FolderId = folderId == "none" ? null : folderId;
_vm.CollectionIds = collectionId != null ? new HashSet<string>(new List<string> { collectionId }) : null;
_vm.Type = type;
_vm.DefaultName = name ?? appOptions?.SaveName;
@@ -58,6 +60,10 @@ namespace Bit.App.Pages
{
ToolbarItems.Add(_moreItem);
}
_vm.ShowNotesSeparator = true;
_typePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_ownershipPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
}
_typePicker.ItemDisplayBinding = new Binding("Key");
@@ -125,6 +131,7 @@ namespace Bit.App.Pages
}
public bool FromAutofillFramework { get; set; }
public AddEditPageViewModel ViewModel => _vm;
protected override async void OnAppearing()
{
@@ -155,7 +162,7 @@ namespace Bit.App.Pages
{
if(FromAutofillFramework)
{
Application.Current.MainPage = new TabsPage();
Xamarin.Forms.Application.Current.MainPage = new TabsPage();
return true;
}
return base.OnBackButtonPressed();
@@ -165,7 +172,8 @@ namespace Bit.App.Pages
{
if(DoOnce())
{
await Navigation.PushModalAsync(new NavigationPage(new PasswordHistoryPage(_vm.CipherId)));
await Navigation.PushModalAsync(
new Xamarin.Forms.NavigationPage(new PasswordHistoryPage(_vm.CipherId)));
}
}
@@ -192,7 +200,7 @@ namespace Bit.App.Pages
if(DoOnce())
{
var page = new AttachmentsPage(_vm.CipherId);
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
}
@@ -201,7 +209,7 @@ namespace Bit.App.Pages
if(DoOnce())
{
var page = new SharePage(_vm.CipherId);
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
}
@@ -221,7 +229,7 @@ namespace Bit.App.Pages
if(DoOnce())
{
var page = new CollectionsPage(_vm.CipherId);
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
}
@@ -237,7 +245,7 @@ namespace Bit.App.Pages
await _vm.UpdateTotpKeyAsync(key);
});
});
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
}
@@ -250,7 +258,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.Share : AppResources.Collections);
}
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
_vm.EditMode ? AppResources.Delete : null, options.ToArray());
@@ -264,17 +272,17 @@ namespace Bit.App.Pages
else if(selection == AppResources.Attachments)
{
var page = new AttachmentsPage(_vm.CipherId);
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
else if(selection == AppResources.Collections)
{
var page = new CollectionsPage(_vm.CipherId);
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
else if(selection == AppResources.Share)
{
var page = new SharePage(_vm.CipherId);
await Navigation.PushModalAsync(new NavigationPage(page));
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
}
}

View File

@@ -23,7 +23,9 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService;
private readonly IEventService _eventService;
private CipherView _cipher;
private bool _showNotesSeparator;
private bool _showPassword;
private bool _showCardCode;
private int _typeSelectedIndex;
@@ -33,6 +35,7 @@ namespace Bit.App.Pages
private int _folderSelectedIndex;
private int _ownershipSelectedIndex;
private bool _hasCollections;
private string _previousCipherId;
private List<Core.Models.View.CollectionView> _writeableCollections;
private string[] _additionalCipherProperties = new string[]
{
@@ -73,6 +76,7 @@ namespace Bit.App.Pages
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardCodeCommand = new Command(ToggleCardCode);
@@ -224,6 +228,11 @@ namespace Bit.App.Pages
get => _cipher;
set => SetProperty(ref _cipher, value, additionalPropertyNames: _additionalCipherProperties);
}
public bool ShowNotesSeparator
{
get => _showNotesSeparator;
set => SetProperty(ref _showNotesSeparator, value);
}
public bool ShowPassword
{
get => _showPassword;
@@ -359,9 +368,16 @@ namespace Bit.App.Pages
}
if(Cipher.Fields != null)
{
Fields.ResetWithRange(Cipher.Fields?.Select(f => new AddEditPageFieldViewModel(f)));
Fields.ResetWithRange(Cipher.Fields?.Select(f => new AddEditPageFieldViewModel(Cipher, f)));
}
}
if(EditMode && _previousCipherId != CipherId)
{
var task = _eventService.CollectAsync(EventType.Cipher_ClientViewed, CipherId);
}
_previousCipherId = CipherId;
return true;
}
@@ -385,12 +401,13 @@ namespace Bit.App.Pages
return false;
}
Cipher.Fields = Fields != null && Fields.Any() ? Fields.Select(f => f.Field).ToList() : null;
Cipher.Fields = Fields != null && Fields.Any() ?
Fields.Where(f => f != null).Select(f => f.Field).ToList() : null;
if(Cipher.Login != null)
{
Cipher.Login.Uris = Uris?.ToList();
if(!EditMode && Cipher.Type == CipherType.Login && (Cipher.Login.Uris?.Count ?? 0) == 1 &&
string.IsNullOrWhiteSpace(Cipher.Login.Uris.First().Uri))
if(!EditMode && Cipher.Type == CipherType.Login && Cipher.Login.Uris != null &&
Cipher.Login.Uris.Count == 1 && string.IsNullOrWhiteSpace(Cipher.Login.Uris[0].Uri))
{
Cipher.Login.Uris = null;
}
@@ -398,7 +415,7 @@ namespace Bit.App.Pages
if(!EditMode && Cipher.OrganizationId != null)
{
if(!Collections?.Any(c => c.Checked) ?? true)
if(Collections == null || !Collections.Any(c => c != null && c.Checked))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.SelectOneCollection,
AppResources.Ok);
@@ -406,7 +423,8 @@ namespace Bit.App.Pages
}
Cipher.CollectionIds = Collections.Any() ?
new HashSet<string>(Collections.Where(c => c.Checked).Select(c => c.Collection.Id)) : null;
new HashSet<string>(Collections.Where(c => c != null && c.Checked && c.Collection?.Id != null)
.Select(c => c.Collection.Id)) : null;
}
var cipher = await _cipherService.EncryptAsync(Cipher);
@@ -422,9 +440,9 @@ namespace Bit.App.Pages
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.ItemUpdated : AppResources.NewItemCreated);
_messagingService.Send(EditMode ? "editedCipher" : "addedCipher");
_messagingService.Send(EditMode ? "editedCipher" : "addedCipher", Cipher.Id);
if((Page as AddEditPage).FromAutofillFramework)
if(Page is AddEditPage page && page.FromAutofillFramework)
{
// Close and go back to app
_deviceActionService.CloseAutofill();
@@ -438,7 +456,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
@@ -463,13 +485,17 @@ namespace Bit.App.Pages
await _cipherService.DeleteWithServerAsync(Cipher.Id);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.ItemDeleted);
_messagingService.Send("deletedCipher");
_messagingService.Send("deletedCipher", Cipher);
return true;
}
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
@@ -585,7 +611,7 @@ namespace Bit.App.Pages
Fields = new ExtendedObservableCollection<AddEditPageFieldViewModel>();
}
var type = _fieldTypeOptions.FirstOrDefault(f => f.Value == typeSelection).Key;
Fields.Add(new AddEditPageFieldViewModel(new FieldView
Fields.Add(new AddEditPageFieldViewModel(Cipher, new FieldView
{
Type = type,
Name = string.IsNullOrWhiteSpace(name) ? null : name
@@ -596,11 +622,19 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
if(EditMode && ShowPassword)
{
var task = _eventService.CollectAsync(EventType.Cipher_ClientToggledPasswordVisible, CipherId);
}
}
public void ToggleCardCode()
{
ShowCardCode = !ShowCardCode;
if(EditMode && ShowCardCode)
{
var task = _eventService.CollectAsync(EventType.Cipher_ClientToggledCardCodeVisible, CipherId);
}
}
public async Task UpdateTotpKeyAsync(string key)
@@ -714,6 +748,7 @@ namespace Bit.App.Pages
public class AddEditPageFieldViewModel : ExtendedViewModel
{
private FieldView _field;
private CipherView _cipher;
private bool _showHiddenValue;
private bool _booleanValue;
private string[] _additionalFieldProperties = new string[]
@@ -723,8 +758,9 @@ namespace Bit.App.Pages
nameof(IsTextType),
};
public AddEditPageFieldViewModel(FieldView field)
public AddEditPageFieldViewModel(CipherView cipher, FieldView field)
{
_cipher = cipher;
Field = field;
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
BooleanValue = IsBooleanType && field.Value == "true";
@@ -769,6 +805,11 @@ namespace Bit.App.Pages
public void ToggleHiddenValue()
{
ShowHiddenValue = !ShowHiddenValue;
if(ShowHiddenValue && _cipher?.Id != null)
{
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
var task = eventService.CollectAsync(EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
}
}
public void TriggerFieldChanged()

View File

@@ -30,9 +30,9 @@
<ScrollView x:Name="_scrollView">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row"
<StackLayout StyleClass="box-row" Padding="10, 20"
IsVisible="{Binding HasAttachments, Converter={StaticResource inverseBool}}">
<Label Text="{u:I18n NoAttachments}" />
<Label Text="{u:I18n NoAttachments}" HorizontalTextAlignment="Center" />
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Attachments}" IsVisible="{Binding HasAttachments}">
<controls:RepeaterView.ItemTemplate>
@@ -54,7 +54,9 @@
Text="&#xf014;"
Command="{Binding BindingContext.DeleteAttachmentCommand, Source={x:Reference _page}}"
CommandParameter="{Binding .}"
VerticalOptions="Center" />
VerticalOptions="Center"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Delete}" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
@@ -64,7 +66,7 @@
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n AddNewAttachment}"
<Label Text="{u:I18n AddNewAttachment, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row">

View File

@@ -45,7 +45,10 @@ namespace Bit.App.Pages
protected override void OnDisappearing()
{
base.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(AttachmentsPage));
if(Device.RuntimePlatform != Device.iOS)
{
_broadcasterService.Unsubscribe(nameof(AttachmentsPage));
}
}
private async void Save_Clicked(object sender, EventArgs e)

View File

@@ -124,7 +124,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
@@ -164,7 +168,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}

View File

@@ -21,7 +21,7 @@
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<DataTemplate x:Key="cipherTemplate"
x:DataType="pages:GroupingsPageListItem">
<controls:CipherViewCell
@@ -63,13 +63,19 @@
<ListView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
<ViewCell>
<StackLayout StyleClass="list-row-header">
<Label
Text="{Binding Name}"
StyleClass="list-header, list-header-platform" />
<Label
Text="{Binding ItemCount}"
StyleClass="list-header-sub" />
<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>
</ViewCell>
</DataTemplate>

View File

@@ -39,7 +39,7 @@ namespace Bit.App.Pages
}
catch(Exception e) when(e.Message.Contains("No key."))
{
await Task.Delay(5000);
await Task.Delay(1000);
await _vm.LoadAsync();
}
}, _mainContent);

View File

@@ -157,8 +157,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(),
AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
if(autofillResponse == AppResources.Yes || autofillResponse == AppResources.YesAndSave)

View File

@@ -20,34 +20,33 @@
<u:DateTimeConverter x:Key="dateTime" />
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1"
x:Name="_closeItem" x:Key="closeItem" />
<StackLayout
Orientation="Horizontal"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Spacing="0"
Padding="0"
x:Name="_titleLayout"
x:Key="titleLayout">
<controls:MiButton
StyleClass="btn-title, btn-title-platform"
Text="&#xe5c4;"
VerticalOptions="CenterAndExpand"
Clicked="BackButton_Clicked"
x:Name="_backButton" />
<controls:ExtendedSearchBar
x:Name="_searchBar"
HorizontalOptions="FillAndExpand"
TextChanged="SearchBar_TextChanged"
SearchButtonPressed="SearchBar_SearchButtonPressed"
Placeholder="{Binding PageTitle}" />
</StackLayout>
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform"
x:Name="_separator" x:Key="separator" />
</ResourceDictionary>
</ContentPage.Resources>
<NavigationPage.TitleView>
<StackLayout
Orientation="Horizontal"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Spacing="0"
Padding="0"
x:Name="_titleLayout">
<controls:MiButton
StyleClass="btn-title, btn-title-platform"
Text="&#xe5c4;"
VerticalOptions="CenterAndExpand"
Clicked="BackButton_Clicked"
x:Name="_backButton" />
<SearchBar
x:Name="_searchBar"
HorizontalOptions="FillAndExpand"
BackgroundColor="Transparent"
TextChanged="SearchBar_TextChanged"
SearchButtonPressed="SearchBar_SearchButtonPressed"
Placeholder="{Binding PageTitle}" />
</StackLayout>
</NavigationPage.TitleView>
<StackLayout x:Name="_mainLayout">
<StackLayout x:Name="_mainLayout" Spacing="0" Padding="0">
<controls:FaLabel IsVisible="{Binding ShowSearchDirection}"
Text="&#xf002;"
StyleClass="text-muted"
@@ -62,13 +61,13 @@
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center" />
<ListView x:Name="_listView"
IsVisible="{Binding ShowList}"
ItemsSource="{Binding Ciphers}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
CachingStrategy="RecycleElement"
ItemSelected="RowSelected"
StyleClass="list, list-platform">
IsVisible="{Binding ShowList}"
ItemsSource="{Binding Ciphers}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
CachingStrategy="RecycleElement"
ItemSelected="RowSelected"
StyleClass="list, list-platform">
<ListView.ItemTemplate>
<DataTemplate x:DataType="views:CipherView">
<controls:CipherViewCell

View File

@@ -43,7 +43,13 @@ namespace Bit.App.Pages
if(Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_closeItem);
_titleLayout.Children.Remove(_backButton);
_searchBar.Placeholder = AppResources.Search;
_mainLayout.Children.Insert(0, _searchBar);
_mainLayout.Children.Insert(1, _separator);
}
else
{
NavigationPage.SetTitleView(this, _titleLayout);
}
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
}

View File

@@ -75,6 +75,10 @@ namespace Bit.App.Pages
{
WebsiteIconsEnabled = !(await _stateService.GetAsync<bool?>(Constants.DisableFaviconKey))
.GetValueOrDefault();
if(!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
{
Search((Page as CiphersPage).SearchBar.Text, 500);
}
}
public void Search(string searchText, int? timeout = null)
@@ -126,7 +130,7 @@ namespace Bit.App.Pages
if(!string.IsNullOrWhiteSpace(AutofillUrl))
{
var options = new List<string> { AppResources.Autofill };
if(cipher.Type == CipherType.Login &&
if(cipher.Type == CipherType.Login &&
Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
{
options.Add(AppResources.AutofillAndSave);
@@ -164,8 +168,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(),
AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
if(_deviceActionService.SystemMajorVersion() < 21)

View File

@@ -28,9 +28,9 @@
<ScrollView x:Name="_scrollView">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row"
<StackLayout StyleClass="box-row" Padding="10, 20"
IsVisible="{Binding HasCollections, Converter={StaticResource inverseBool}}">
<Label Text="{u:I18n NoCollectionsToList}" />
<Label Text="{u:I18n NoCollectionsToList}" HorizontalTextAlignment="Center" />
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Collections}" IsVisible="{Binding HasCollections}">
<controls:RepeaterView.ItemTemplate>

View File

@@ -58,7 +58,8 @@ namespace Bit.App.Pages
public async Task<bool> SubmitAsync()
{
if(!Collections.Any(c => c.Checked))
var selectedCollectionIds = Collections?.Where(c => c.Checked).Select(c => c.Collection.Id);
if(!selectedCollectionIds?.Any() ?? true)
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.SelectOneCollection,
AppResources.Ok);
@@ -71,8 +72,7 @@ namespace Bit.App.Pages
return false;
}
_cipherDomain.CollectionIds = new HashSet<string>(
Collections.Where(c => c.Checked).Select(c => c.Collection.Id));
_cipherDomain.CollectionIds = new HashSet<string>(selectedCollectionIds);
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
@@ -85,7 +85,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}

View File

@@ -95,9 +95,10 @@
IsVisible="{Binding ShowList}"
ItemsSource="{Binding GroupedItems}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
HasUnevenRows="True"
RowHeight="-1"
RefreshCommand="{Binding RefreshCommand}"
IsPullToRefreshEnabled="true"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding Refreshing}"
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
IsGroupingEnabled="True"
@@ -110,10 +111,13 @@
<ListView.GroupHeaderTemplate>
<DataTemplate x:DataType="pages:GroupingsPageListGroup">
<ViewCell>
<StackLayout Spacing="0" Padding="0">
<BoxView StyleClass="list-section-separator"
IsVisible="{Binding First, Converter={StaticResource inverseBool}}" />
<StackLayout StyleClass="list-row-header">
<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"
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" />
@@ -121,6 +125,7 @@
Text="{Binding ItemCount}"
StyleClass="list-header-sub" />
</StackLayout>
<BoxView StyleClass="list-section-separator-bottom, list-section-separator-bottom-platform" />
</StackLayout>
</ViewCell>
</DataTemplate>

View File

@@ -1,5 +1,6 @@
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
@@ -22,8 +23,10 @@ namespace Bit.App.Pages
private readonly GroupingsPageViewModel _vm;
private readonly string _pageName;
private PreviousPageInfo _previousPage;
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
string collectionId = null, string pageTitle = null)
string collectionId = null, string pageTitle = null, PreviousPageInfo previousPage = null)
{
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent();
@@ -41,6 +44,7 @@ namespace Bit.App.Pages
_vm.Type = type;
_vm.FolderId = folderId;
_vm.CollectionId = collectionId;
_previousPage = previousPage;
if(pageTitle != null)
{
_vm.PageTitle = pageTitle;
@@ -78,15 +82,14 @@ namespace Bit.App.Pages
}
else if(message.Command == "syncCompleted")
{
if(!_vm.LoadedOnce)
{
return;
}
await Task.Delay(500);
Device.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
var task = _vm.LoadAsync();
if(_vm.LoadedOnce)
{
var task = _vm.LoadAsync();
}
});
}
});
@@ -102,7 +105,7 @@ namespace Bit.App.Pages
}
catch(Exception e) when(e.Message.Contains("No key."))
{
await Task.Delay(5000);
await Task.Delay(1000);
await _vm.LoadAsync();
}
}
@@ -126,6 +129,7 @@ namespace Bit.App.Pages
await _syncService.FullSyncAsync(true);
}
}
await ShowPreviousPageAsync();
}, _mainContent);
if(!_vm.MainPage)
@@ -245,5 +249,22 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async Task ShowPreviousPageAsync()
{
if(_previousPage == null)
{
return;
}
if(_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.CipherId))
{
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.CipherId)));
}
else if(_previousPage.Page == "edit" && !string.IsNullOrWhiteSpace(_previousPage.CipherId))
{
await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_previousPage.CipherId)));
}
_previousPage = null;
}
}
}

View File

@@ -37,6 +37,8 @@ namespace Bit.App.Pages
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly ISyncService _syncService;
private readonly IUserService _userService;
private readonly ILockService _lockService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
@@ -48,6 +50,8 @@ namespace Bit.App.Pages
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@@ -134,6 +138,15 @@ namespace Bit.App.Pages
{
return;
}
var authed = await _userService.IsAuthenticatedAsync();
if(!authed)
{
return;
}
if(await _lockService.IsLockedAsync())
{
return;
}
_doingLoad = true;
LoadedOnce = true;
ShowNoData = false;
@@ -305,9 +318,17 @@ namespace Bit.App.Pages
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.Syncing);
await _syncService.FullSyncAsync(false);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.SyncingComplete);
try
{
await _syncService.FullSyncAsync(false, true);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.SyncingComplete);
}
catch
{
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("error", null, AppResources.SyncingFailed);
}
}
private async Task LoadDataAsync()
@@ -328,7 +349,7 @@ namespace Bit.App.Pages
{
Folders = await _folderService.GetAllDecryptedAsync();
NestedFolders = await _folderService.GetAllNestedAsync();
HasFolders = NestedFolders.Any();
HasFolders = NestedFolders.Any(f => f.Node?.Id != null);
Collections = await _collectionService.GetAllDecryptedAsync();
NestedCollections = await _collectionService.GetAllNestedAsync(Collections);
HasCollections = NestedCollections.Any();
@@ -366,6 +387,7 @@ namespace Bit.App.Pages
if(collectionNode?.Node != null)
{
PageTitle = collectionNode.Node.Name;
NestedCollections = (collectionNode.Children?.Count ?? 0) > 0 ? collectionNode.Children : null;
}
Filter = c => c.CollectionIds?.Contains(CollectionId) ?? false;
}

View File

@@ -75,7 +75,9 @@
CommandParameter="{Binding .}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyPassword}" />
</Grid>
</ViewCell>
</DataTemplate>

View File

@@ -27,12 +27,15 @@
<ScrollView x:Name="_scrollView">
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row"
IsVisible="{Binding HasOrganizations, Converter={StaticResource inverseBool}}">
<Label Text="{u:I18n NoOrgsToList}" />
<StackLayout StyleClass="box"
IsVisible="{Binding HasOrganizations, Converter={StaticResource inverseBool}}">
<StackLayout StyleClass="box-row" Padding="10, 20">
<Label Text="{u:I18n NoOrgsToList}" HorizontalTextAlignment="Center" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input">
</StackLayout>
<StackLayout StyleClass="box"
IsVisible="{Binding HasOrganizations}">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n Organization}"
StyleClass="box-label" />
@@ -49,7 +52,7 @@
<StackLayout StyleClass="box"
IsVisible="{Binding OrganizationId, Converter={StaticResource notNull}}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Collections}"
<Label Text="{u:I18n Collections, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row"

View File

@@ -1,4 +1,6 @@
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
@@ -17,6 +19,10 @@ namespace Bit.App.Pages
{
ToolbarItems.RemoveAt(0);
}
else
{
_organizationPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
}
_organizationPicker.ItemDisplayBinding = new Binding("Key");
}

View File

@@ -86,7 +86,8 @@ namespace Bit.App.Pages
public async Task<bool> SubmitAsync()
{
if(!Collections?.Any(c => c.Checked) ?? true)
var selectedCollectionIds = Collections?.Where(c => c.Checked).Select(c => c.Collection.Id);
if(!selectedCollectionIds?.Any() ?? true)
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.SelectOneCollection,
AppResources.Ok);
@@ -102,8 +103,7 @@ namespace Bit.App.Pages
var cipherDomain = await _cipherService.GetAsync(CipherId);
var cipherView = await cipherDomain.DecryptAsync();
var checkedCollectionIds = new HashSet<string>(
Collections.Where(c => c.Checked).Select(c => c.Collection.Id));
var checkedCollectionIds = new HashSet<string>(selectedCollectionIds);
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
@@ -116,12 +116,19 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
catch(System.Exception e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Message, AppResources.Ok);
if(e.Message != null)
{
await _platformUtilsService.ShowDialogAsync(e.Message, AppResources.AnErrorHasOccurred);
}
}
return false;
}

View File

@@ -48,7 +48,7 @@
<StackLayout Spacing="20" x:Name="_mainLayout">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n ItemInformation}"
<Label Text="{u:I18n ItemInformation, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row">
@@ -88,7 +88,9 @@
CommandParameter="LoginUsername"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyUsername}" />
</Grid>
<BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Login.Username, Converter={StaticResource stringHasValue}}" />
@@ -120,6 +122,7 @@
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
LineBreakMode="CharacterWrap"
IsVisible="{Binding ShowPassword}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
@@ -127,14 +130,18 @@
Command="{Binding CheckPasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CheckPassword}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf0ea;"
@@ -142,7 +149,9 @@
CommandParameter="LoginPassword"
Grid.Row="0"
Grid.Column="3"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyPassword}" />
</Grid>
<BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}" />
@@ -183,7 +192,9 @@
CommandParameter="LoginTotp"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyTotp}" />
</Grid>
<BoxView StyleClass="box-row-separator" IsVisible="{Binding ShowTotp}" />
</StackLayout>
@@ -226,7 +237,9 @@
CommandParameter="CardNumber"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyNumber}" />
</Grid>
<BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Card.Number, Converter={StaticResource stringHasValue}}" />
@@ -286,7 +299,9 @@
Command="{Binding ToggleCardCodeCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf0ea;"
@@ -294,7 +309,9 @@
CommandParameter="CardCode"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopySecurityCode}" />
</Grid>
<BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Card.Code, Converter={StaticResource stringHasValue}}" />
@@ -418,7 +435,7 @@
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowUris}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n URIs}"
<Label Text="{u:I18n URIs, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Cipher.Login.Uris}">
@@ -460,7 +477,9 @@
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
IsVisible="{Binding CanLaunch, Mode=OneWay}" />
IsVisible="{Binding CanLaunch, Mode=OneWay}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Launch}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf0ea;"
@@ -468,7 +487,9 @@
CommandParameter="{Binding .}"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Copy}" />
</Grid>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
@@ -479,7 +500,7 @@
<StackLayout StyleClass="box"
IsVisible="{Binding Cipher.Notes, Converter={StaticResource stringHasValue}}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Notes}"
<Label Text="{u:I18n Notes, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row">
@@ -496,7 +517,7 @@
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding Cipher.HasFields}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n CustomFields}"
<Label Text="{u:I18n CustomFields, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Fields}">
@@ -550,7 +571,9 @@
IsVisible="{Binding IsHiddenType}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="&#xf0ea;"
@@ -559,7 +582,9 @@
IsVisible="{Binding ShowCopyButton}"
Grid.Row="0"
Grid.Column="2"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Copy}" />
</Grid>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
@@ -569,7 +594,7 @@
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowAttachments}">
<StackLayout StyleClass="box-row-header">
<Label Text="{u:I18n Attachments}"
<Label Text="{u:I18n Attachments, Header=True}"
StyleClass="box-header, box-header-platform" />
</StackLayout>
<controls:RepeaterView ItemsSource="{Binding Cipher.Attachments}">
@@ -592,7 +617,9 @@
Text="&#xf019;"
Command="{Binding BindingContext.DownloadAttachmentCommand, Source={x:Reference _page}}"
CommandParameter="{Binding .}"
VerticalOptions="Center" />
VerticalOptions="Center"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Download}" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>

View File

@@ -39,6 +39,8 @@ namespace Bit.App.Pages
}
}
public ViewPageViewModel ViewModel => _vm;
protected override async void OnAppearing()
{
base.OnAppearing();
@@ -154,7 +156,7 @@ namespace Bit.App.Pages
return;
}
var options = new List<string> { AppResources.Attachments };
options.Add(_vm.Cipher.OrganizationId != null ? AppResources.Share : AppResources.Collections);
options.Add(_vm.Cipher.OrganizationId == null ? AppResources.Share : AppResources.Collections);
var selection = await DisplayActionSheet(AppResources.Options, AppResources.Cancel,
AppResources.Delete, options.ToArray());
if(selection == AppResources.Delete)

View File

@@ -22,6 +22,7 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IAuditService _auditService;
private readonly IMessagingService _messagingService;
private readonly IEventService _eventService;
private CipherView _cipher;
private List<ViewPageFieldViewModel> _fields;
private bool _canAccessPremium;
@@ -32,6 +33,7 @@ namespace Bit.App.Pages
private string _totpSec;
private bool _totpLow;
private DateTime? _totpInterval = null;
private string _previousCipherId;
public ViewPageViewModel()
{
@@ -42,6 +44,7 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_auditService = ServiceContainer.Resolve<IAuditService>("auditService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
CopyCommand = new Command<string>((id) => CopyAsync(id, null));
CopyUriCommand = new Command<LoginUriView>(CopyUri);
CopyFieldCommand = new Command<FieldView>(CopyField);
@@ -217,7 +220,7 @@ namespace Bit.App.Pages
}
Cipher = await cipher.DecryptAsync();
CanAccessPremium = await _userService.CanAccessPremiumAsync();
Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(f)).ToList();
Fields = Cipher.Fields?.Select(f => new ViewPageFieldViewModel(Cipher, f)).ToList();
if(Cipher.Type == Core.Enums.CipherType.Login && !string.IsNullOrWhiteSpace(Cipher.Login.Totp) &&
(Cipher.OrganizationUseTotp || CanAccessPremium))
@@ -236,6 +239,11 @@ namespace Bit.App.Pages
return true;
});
}
if(_previousCipherId != CipherId)
{
var task = _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientViewed, CipherId);
}
_previousCipherId = CipherId;
finishedLoadingAction?.Invoke();
return true;
}
@@ -248,11 +256,20 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
if(ShowPassword)
{
var task = _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientToggledPasswordVisible, CipherId);
}
}
public void ToggleCardCode()
{
ShowCardCode = !ShowCardCode;
if(ShowCardCode)
{
var task = _eventService.CollectAsync(
Core.Enums.EventType.Cipher_ClientToggledCardCodeVisible, CipherId);
}
}
public async Task<bool> DeleteAsync()
@@ -275,13 +292,17 @@ namespace Bit.App.Pages
await _cipherService.DeleteWithServerAsync(Cipher.Id);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.ItemDeleted);
_messagingService.Send("deletedCipher");
_messagingService.Send("deletedCipher", Cipher);
return true;
}
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
@@ -434,7 +455,7 @@ namespace Bit.App.Pages
{
name = AppResources.URI;
}
else if(id == "FieldValue")
else if(id == "FieldValue" || id == "H_FieldValue")
{
name = AppResources.Value;
}
@@ -456,6 +477,18 @@ namespace Bit.App.Pages
{
_platformUtilsService.ShowToast("info", null, string.Format(AppResources.ValueHasBeenCopied, name));
}
if(id == "LoginPassword")
{
await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedPassword, CipherId);
}
else if(id == "CardCode")
{
await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedCardCode, CipherId);
}
else if(id == "H_FieldValue")
{
await _eventService.CollectAsync(Core.Enums.EventType.Cipher_ClientCopiedHiddenField, CipherId);
}
}
}
@@ -466,7 +499,7 @@ namespace Bit.App.Pages
private void CopyField(FieldView field)
{
CopyAsync("FieldValue", field.Value);
CopyAsync(field.Type == Core.Enums.FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value);
}
private void LaunchUri(LoginUriView uri)
@@ -481,10 +514,12 @@ namespace Bit.App.Pages
public class ViewPageFieldViewModel : ExtendedViewModel
{
private FieldView _field;
private CipherView _cipher;
private bool _showHiddenValue;
public ViewPageFieldViewModel(FieldView field)
public ViewPageFieldViewModel(CipherView cipher, FieldView field)
{
_cipher = cipher;
Field = field;
ToggleHiddenValueCommand = new Command(ToggleHiddenValue);
}
@@ -526,6 +561,12 @@ namespace Bit.App.Pages
public void ToggleHiddenValue()
{
ShowHiddenValue = !ShowHiddenValue;
if(ShowHiddenValue)
{
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
var task = eventService.CollectAsync(
Core.Enums.EventType.Cipher_ClientToggledHiddenFieldVisible, _cipher.Id);
}
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Bit.App.Resources {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class AppResources {
@@ -537,6 +537,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Biometrics.
/// </summary>
public static string Biometrics {
get {
return ResourceManager.GetString("Biometrics", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use biometrics to verify..
/// </summary>
public static string BiometricsDirection {
get {
return ResourceManager.GetString("BiometricsDirection", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bitwarden.
/// </summary>
@@ -807,6 +825,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Capitalize.
/// </summary>
public static string Capitalize {
get {
return ResourceManager.GetString("Capitalize", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cardholder Name.
/// </summary>
@@ -1239,6 +1266,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Download.
/// </summary>
public static string Download {
get {
return ResourceManager.GetString("Download", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Downloading....
/// </summary>
@@ -1932,6 +1968,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Include Number.
/// </summary>
public static string IncludeNumber {
get {
return ResourceManager.GetString("IncludeNumber", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please connect to the internet before continuing..
/// </summary>
@@ -2247,6 +2292,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Your login session has expired..
/// </summary>
public static string LoginExpired {
get {
return ResourceManager.GetString("LoginExpired", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login.
/// </summary>
@@ -3327,6 +3381,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Shared.
/// </summary>
public static string Shared {
get {
return ResourceManager.GetString("Shared", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Choose an organization that you wish to share this item with. Sharing transfers ownership of the item to the organization. You will no longer be the direct owner of this item once it has been shared..
/// </summary>
@@ -3498,6 +3561,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Your theme changes will apply when the app is restarted..
/// </summary>
public static string ThemeAppliedOnRestart {
get {
return ResourceManager.GetString("ThemeAppliedOnRestart", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Change the application&apos;s color theme..
/// </summary>
@@ -3525,6 +3597,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Toggle Visibility.
/// </summary>
public static string ToggleVisibility {
get {
return ResourceManager.GetString("ToggleVisibility", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Tools.
/// </summary>
@@ -3553,7 +3634,7 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Try Again.
/// Looks up a localized string similar to Try again.
/// </summary>
public static string TryAgain {
get {
@@ -3759,6 +3840,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Use Biometrics To Unlock.
/// </summary>
public static string UseBiometricsToUnlock {
get {
return ResourceManager.GetString("UseBiometricsToUnlock", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use Face ID To Unlock.
/// </summary>

View File

@@ -1556,4 +1556,35 @@
<data name="AutofillServiceNotEnabled" xml:space="preserve">
<value>Auto-fill makes it easy to securely access your Bitwarden vault from other websites and apps. It looks like you have not enabled an auto-fill service for Bitwarden. Enable auto-fill for Bitwarden from the "Settings" screen.</value>
</data>
<data name="ThemeAppliedOnRestart" xml:space="preserve">
<value>Your theme changes will apply when the app is restarted.</value>
</data>
<data name="Capitalize" xml:space="preserve">
<value>Capitalize</value>
<comment>ex. Uppercase the first character of a word.</comment>
</data>
<data name="IncludeNumber" xml:space="preserve">
<value>Include Number</value>
</data>
<data name="Download" xml:space="preserve">
<value>Download</value>
</data>
<data name="Shared" xml:space="preserve">
<value>Shared</value>
</data>
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

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