Compare commits

...

152 Commits
3.0.2 ... 3.0.4

Author SHA1 Message Date
J-Jamet
045049243c Merge branch 'release/3.0.4' 2021-12-09 20:10:57 +01:00
J-Jamet
b9813a3494 Upgrade version code 2021-12-09 19:54:12 +01:00
J-Jamet
9b42a93ce1 Change lock button 2021-12-09 19:49:38 +01:00
J-Jamet
8502bceef1 Fix search 2021-12-09 18:38:38 +01:00
J-Jamet
663387476f Change select entry min height 2021-12-09 14:33:07 +01:00
J-Jamet
daafd83df9 Better extra key implementation 2021-12-09 14:29:41 +01:00
J-Jamet
f780f2725b Fix compat inline suggestions request 2021-12-09 14:15:40 +01:00
J-Jamet
483aca871a Upgrade to version 3.0.4 and fix inline autofill suggestion #1165 2021-12-09 13:38:42 +01:00
J-Jamet
352e709c3b Merge branch 'master' into develop 2021-12-09 11:53:40 +01:00
J-Jamet
629057b2c1 Fix autofill exception #1173 2021-12-09 11:46:39 +01:00
J-Jamet
0e5f53596d Merge tag '3.0.3' into develop
3.0.3
2021-12-08 17:58:49 +01:00
J-Jamet
0d91f07646 Merge branch 'release/3.0.3' 2021-12-08 17:58:37 +01:00
J-Jamet
db882a26ab Upgrade constraint layout lib 2021-12-08 11:37:02 +01:00
J-Jamet
7f01619358 Fix notification in older android versions 2021-12-07 18:14:36 +01:00
J-Jamet
ee109b4ceb Merge branch 'feature/StartActivityResult' into develop 2021-12-07 16:36:52 +01:00
J-Jamet
7a398e5453 Fix activity result for advanced unlocking 2021-12-07 16:20:57 +01:00
J-Jamet
d4655d7034 Fix search in activity 2021-12-02 13:31:41 +01:00
J-Jamet
9feb96b541 Fix start autofill service 2021-12-02 13:30:02 +01:00
J-Jamet
e939278193 Suppress deprecation with setTargetFragment 2021-12-02 13:21:25 +01:00
J-Jamet
d4ef1a2617 Fix small warnings 2021-12-02 11:39:42 +01:00
J-Jamet
5f8746ced3 Fix result with entry edit 2021-12-01 17:16:19 +01:00
J-Jamet
40a063e94f Fix result exit lock 2021-11-30 11:50:07 +01:00
J-Jamet
8f5439b958 Icon selection with activity result launcher 2021-11-30 11:20:09 +01:00
J-Jamet
e347f57d8b Refactor FileHelper and fix key file selection 2021-11-30 10:47:31 +01:00
J-Jamet
2efb8e8b8c Merge branch 'develop' into feature/StartActivityResult 2021-11-23 18:28:08 +01:00
J-Jamet
e5bb69ea5f Fix startActivityResult for Autofill 2021-11-23 18:28:01 +01:00
J-Jamet
6ae186b2af Fix exported and pending intent 2021-11-23 12:10:57 +01:00
J-Jamet
71fdd2d92d Fix lowercase and uppercase 2021-11-22 13:22:18 +01:00
J-Jamet
3656689ff3 Fix kotlin code warning 2021-11-22 13:10:42 +01:00
J-Jamet
7d78406db6 Fix pending intent 2021-11-22 12:24:22 +01:00
J-Jamet
ac47748e41 Fix pending intent 2021-11-22 11:51:30 +01:00
J-Jamet
80f9b46479 New lock icon in notification 2021-11-20 13:30:26 +01:00
J-Jamet
999f1bf47a New lock icon in notification 2021-11-20 13:10:23 +01:00
J-Jamet
9e114eb2b8 Add lock button in database notification 2021-11-20 12:30:58 +01:00
J-Jamet
d89b6529ef Upgrade kotlin and fragment versions 2021-11-16 16:51:06 +01:00
J-Jamet
5caf11556a Remove unused translation 2021-11-16 16:15:08 +01:00
J-Jamet
78cc6f0f40 Merge branch 'translations' into develop 2021-11-16 16:10:58 +01:00
J-Jamet
0007cd4668 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2021-11-16 16:09:26 +01:00
J-Jamet
05195e41de Upgrade appcompat and material libs 2021-11-16 14:53:54 +01:00
J-Jamet
66f44ef87d Encapsulate lib version through modules 2021-11-16 12:48:38 +01:00
J-Jamet
a0585d9b11 Upgrade repo and libs 2021-11-16 12:07:48 +01:00
J-Jamet
5067946b13 Change backup configuration #1144 2021-11-16 11:12:07 +01:00
J-Jamet
f52241d5a8 Change backup configuration #1144 2021-11-16 11:09:14 +01:00
J-Jamet
04ccb25fa3 Catch key file out of memory exception 2021-11-15 12:25:27 +01:00
J-Jamet
5a3f4b60b8 Catch style exception 2021-11-15 12:19:36 +01:00
J-Jamet
4408b2e488 Catch exception in run action 2021-11-15 12:04:46 +01:00
J-Jamet
9a26acee35 Change version to 3.0.3 and add issue tag 2021-11-15 11:35:08 +01:00
Rsec
6ac377348b Translated using Weblate (Romanian)
Currently translated at 63.2% (359 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2021-11-10 18:49:56 +01:00
I. Musthafa
daeb88d4f4 Translated using Weblate (Indonesian)
Currently translated at 79.0% (449 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2021-11-08 07:50:09 +01:00
J-Jamet
47bf199f52 Merge branch 'feature/Biometric_Refactor' into develop 2021-11-03 14:46:55 +01:00
J-Jamet
91540b022d Use strong box to store in security chip #1145 2021-11-02 17:55:30 +01:00
J-Jamet
505a51b6b5 Update CHANGELOG 2021-11-02 17:31:09 +01:00
J-Jamet
28400488aa Fix advanced unlock menu button 2021-11-02 17:22:23 +01:00
J-Jamet
45ae600289 Change biometric views 2021-11-02 17:10:06 +01:00
J-Jamet
8be6874651 Auto remove all biometric keys when invalidated 2021-11-02 16:27:29 +01:00
Milo Ivir
f694a500e0 Translated using Weblate (Croatian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2021-10-30 00:37:53 +02:00
J-Jamet
c415fa01fc Update Autofill compatibility list #725 2021-10-29 17:40:16 +02:00
J-Jamet
5225a9459c Fix template chars limit 2021-10-27 20:01:47 +02:00
J-Jamet
2974b150af Fix template spinner 2021-10-27 19:55:14 +02:00
J-Jamet
cf353c8067 Fix template icons 2021-10-27 19:27:21 +02:00
Serdar Sağlam
3aacb5d8b3 Translated using Weblate (Turkish)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-10-27 01:38:42 +02:00
abidin toumi
114fbdbe01 Translated using Weblate (Arabic)
Currently translated at 77.4% (440 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2021-10-27 01:38:42 +02:00
abidin toumi
f1f83cbec4 Translated using Weblate (Arabic)
Currently translated at 76.5% (435 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2021-10-25 20:38:00 +02:00
J-Jamet
335c28c2c9 Fix save default username 2021-10-25 15:57:01 +02:00
J-Jamet
daf12cbcce Change Wifi to Wi-Fi 2021-10-25 15:44:18 +02:00
J-Jamet
bf56eca003 Fix templates #1128 2021-10-25 15:41:20 +02:00
J-Jamet
a12b7fd58a Merge branch 'SUPERYAO541-feature/Complete_zh-rTW' into develop 2021-10-25 11:30:33 +02:00
J-Jamet
98d004edbf Merge branch 'feature/Complete_zh-rTW' of git://github.com/SUPERYAO541/KeePassDX into SUPERYAO541-feature/Complete_zh-rTW 2021-10-25 11:30:18 +02:00
random r
67586a98b3 Translated using Weblate (Italian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-10-23 11:41:24 +02:00
Milo Ivir
c6d8911883 Translated using Weblate (Croatian)
Currently translated at 99.8% (567 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2021-10-21 21:36:24 +02:00
Neko Nekowazarashi
12e398ce9b Translated using Weblate (Indonesian)
Currently translated at 78.1% (444 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2021-10-16 13:40:45 +02:00
abidin toumi
5c4a202616 Translated using Weblate (Arabic)
Currently translated at 76.4% (434 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2021-10-14 23:34:38 +02:00
solokot
adc1ec8444 Translated using Weblate (Russian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-10-14 23:34:36 +02:00
Darin Avdeyeva
0227d2fcb4 Translated using Weblate (Russian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-10-13 19:02:33 +02:00
abidin toumi
95abe3b5ac Translated using Weblate (Arabic)
Currently translated at 67.6% (384 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2021-10-10 18:02:20 +02:00
abidin toumi
133a902c54 Translated using Weblate (Arabic)
Currently translated at 61.4% (349 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2021-10-09 17:05:15 +02:00
Stephan Paternotte
aacb03d9ef Translated using Weblate (Dutch)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2021-10-07 20:03:28 +02:00
J-Jamet
fbeaa1781f Add no internet connection required in description 2021-10-06 18:45:14 +02:00
J-Jamet
ca73aad538 Add dynamic templates in description 2021-10-06 18:09:54 +02:00
André Marcelo Alvarenga
4a4bfefd17 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-10-05 10:08:58 +02:00
J-Jamet
6796b0cd2a Upgrade gradle 2021-10-04 16:49:15 +02:00
abidin toumi
29e1f824b0 Translated using Weblate (Arabic)
Currently translated at 61.2% (348 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2021-10-03 17:19:42 +02:00
hokonch
51263a2911 Translated using Weblate (Japanese)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-09-30 06:36:59 +02:00
Kunzisoft
dd7f857475 Translated using Weblate (French)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2021-09-30 06:36:59 +02:00
J-Jamet
f0fdd4a537 Add & edit custom icon name #976 2021-09-28 18:12:49 +02:00
J-Jamet
9b847a0561 Change default Argon2 parameters #1098
and upgrade to 3.1.0
2021-09-28 12:57:14 +02:00
Hosted Weblate
469e76b80a Merge branch 'origin/develop' into Weblate. 2021-09-28 09:56:49 +02:00
SC
9c6994b476 Translated using Weblate (Portuguese)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2021-09-27 19:36:47 +02:00
Oğuz Ersen
52ba487617 Translated using Weblate (Turkish)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-09-27 19:36:47 +02:00
Eric
21c57c9484 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-09-27 19:36:47 +02:00
Ihor Hordiichuk
9b1ea6a07a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-09-27 19:36:47 +02:00
solokot
f7d7bb0ea3 Translated using Weblate (Russian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-09-27 19:36:46 +02:00
SC
dd96b9ef53 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2021-09-27 19:36:46 +02:00
Matthaiks
b6b01893ba Translated using Weblate (Polish)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-09-27 19:36:46 +02:00
Óscar Fernández Díaz
ad531d793d Translated using Weblate (Spanish)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-09-27 19:36:45 +02:00
Retrial
a9dd11e24a Translated using Weblate (Greek)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2021-09-27 19:36:45 +02:00
VfBFan
115983830b Translated using Weblate (German)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-09-27 19:36:45 +02:00
zeritti
442cece081 Translated using Weblate (Czech)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2021-09-27 19:36:45 +02:00
SUPERYAO
14e08457b9 Complete and improve zh-rTW 2021-09-25 21:59:59 +08:00
J-Jamet
fabcc08cd5 Merge tag '3.0.2' into develop
3.0.2
2021-09-24 19:35:07 +02:00
Hosted Weblate
c03188e976 Merge branch 'origin/develop' into Weblate. 2021-09-24 13:54:09 +02:00
Braja Yudhistira
34bbd8f439 Translated using Weblate (Indonesian)
Currently translated at 77.8% (442 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2021-09-23 21:34:44 +02:00
Milo Ivir
053f57cff5 Translated using Weblate (Croatian)
Currently translated at 99.8% (567 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2021-09-23 21:34:44 +02:00
Balázs Meskó
365d2e2844 Translated using Weblate (Hungarian)
Currently translated at 82.5% (469 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hu/
2021-09-23 21:34:43 +02:00
Stephan Paternotte
22c0bc0adb Translated using Weblate (Dutch)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2021-09-17 18:36:29 +02:00
VfBFan
c4cbf07d78 Translated using Weblate (German)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-09-15 07:34:50 +02:00
Long Nguyễn Khánh
dbb2c10bba Translated using Weblate (Vietnamese)
Currently translated at 16.1% (92 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/vi/
2021-09-13 12:46:09 +02:00
Ihor Hordiichuk
67a5eef7d6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-09-12 00:23:20 +02:00
HARADA Hiroyuki
979f651251 Translated using Weblate (Japanese)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-09-12 00:23:19 +02:00
SC
3b93cbb009 Translated using Weblate (Portuguese)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2021-09-11 04:18:42 +02:00
SC
30c63bfc4b Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2021-09-11 04:18:42 +02:00
SC
bed40324a1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-11 04:18:37 +02:00
Wilker Santana da Silva
bc035de377 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-11 04:18:37 +02:00
SC
4db3cb6936 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.8% (567 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-11 01:07:20 +02:00
Wilker Santana da Silva
ed4b91f4bd Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.8% (567 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-11 01:07:20 +02:00
SC
24c7151276 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.6% (566 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-11 01:00:00 +02:00
Wilker Santana da Silva
804a9c07b8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.6% (566 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-11 00:59:58 +02:00
SC
528ea56821 Translated using Weblate (Portuguese)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2021-09-11 00:01:53 +02:00
SC
7ba9c69ff8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 88.7% (504 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-10 20:50:55 +02:00
Wilker Santana da Silva
0fc34da08a Translated using Weblate (Portuguese (Brazil))
Currently translated at 88.7% (504 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-10 20:50:54 +02:00
SC
3fbf8cdbc8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 83.0% (472 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-10 20:36:00 +02:00
Wilker Santana da Silva
e21f20d818 Translated using Weblate (Portuguese (Brazil))
Currently translated at 83.0% (472 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-09-10 20:35:59 +02:00
Sina bagheri
9fd9a60ca3 Translated using Weblate (Persian)
Currently translated at 56.6% (322 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fa/
2021-09-09 17:32:51 +02:00
Hosted Weblate
78b683d724 Merge branch 'origin/develop' into Weblate. 2021-09-08 07:26:45 +02:00
vachan-maker
ad2f5036e1 Translated using Weblate (Malayalam)
Currently translated at 70.2% (399 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ml/
2021-09-08 07:26:45 +02:00
random r
4afbad8faa Translated using Weblate (Italian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-09-08 07:26:44 +02:00
Oliver Cervera
ae8b1c0c29 Translated using Weblate (Italian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-09-06 21:34:18 +02:00
Óscar Fernández Díaz
27978c459c Translated using Weblate (Spanish)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-09-06 21:34:17 +02:00
Martin
1dc7f5c666 Translated using Weblate (Czech)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2021-09-06 21:34:17 +02:00
zeritti
12ac870d3a Translated using Weblate (Czech)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2021-09-06 21:34:17 +02:00
Aman Kirely
dd1baa0224 Translated using Weblate (Czech)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2021-09-06 21:34:17 +02:00
Oğuz Ersen
bb27ef41cc Translated using Weblate (Turkish)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-09-04 17:34:43 +02:00
Allan Nordhøy
2d35ac1df8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 74.8% (425 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2021-09-04 17:34:43 +02:00
Eric
589ffc0c06 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-09-04 17:34:42 +02:00
Ihor Hordiichuk
1f7f38c7d3 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-09-04 17:34:41 +02:00
solokot
83817a2dc0 Translated using Weblate (Russian)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-09-04 17:34:41 +02:00
Matthaiks
11af9da66f Translated using Weblate (Polish)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-09-04 17:34:40 +02:00
Retrial
af3926acf3 Translated using Weblate (Greek)
Currently translated at 100.0% (568 of 568 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2021-09-04 17:34:40 +02:00
Hosted Weblate
ab40c2b3fd Merge branch 'origin/develop' into Weblate. 2021-09-03 16:07:31 +02:00
Oliver Cervera
fd05670dbc Translated using Weblate (Italian)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-09-03 00:32:28 +02:00
Óscar Fernández Díaz
1ac094bfae Translated using Weblate (Spanish)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-09-03 00:32:27 +02:00
Oğuz Ersen
fdf052cddb Translated using Weblate (Turkish)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-09-01 10:34:02 +02:00
Eric
9a8d50ba6f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-09-01 10:34:02 +02:00
Ihor Hordiichuk
136c97c312 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-09-01 10:34:02 +02:00
solokot
bf00b88ef3 Translated using Weblate (Russian)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-09-01 10:34:02 +02:00
Matthaiks
bafd1ea549 Translated using Weblate (Polish)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-09-01 10:34:01 +02:00
Hisikawa Mizuki
982618511b Translated using Weblate (Japanese)
Currently translated at 99.2% (561 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-09-01 10:34:01 +02:00
Oliver Cervera
a4ad7ca3b1 Translated using Weblate (Italian)
Currently translated at 99.4% (562 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-09-01 10:34:01 +02:00
Éfrit
99d71b57a4 Translated using Weblate (French)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2021-09-01 10:34:00 +02:00
Retrial
1b2d8502e0 Translated using Weblate (Greek)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2021-09-01 10:34:00 +02:00
VfBFan
53e4ea9334 Translated using Weblate (German)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-09-01 10:34:00 +02:00
zeritti
3ce704155c Translated using Weblate (Czech)
Currently translated at 100.0% (565 of 565 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2021-09-01 10:33:59 +02:00
133 changed files with 3525 additions and 1944 deletions

View File

@@ -1,3 +1,16 @@
KeePassDX(3.0.4)
* Fix autofill inline bugs #1173 #1165
* Small UI change
KeePassDX(3.0.3)
* Change default Argon2 parameters #1098
* Add & edit custom icon name #976
* Fix templates #1128 #1133 #1138
* Update Autofill compatibility list #725 #1154
* Improve fingerprint usage #1137 #1145
* Change backup configuration #1144
* Add lock button in database notification
KeePassDX(3.0.2)
* Samsung DeX mode #1114 #245 (Thx @chenxiaolong)

View File

@@ -15,6 +15,7 @@
- Material design with **themes**.
- **Auto-Fill** and Integration.
- Field filling **keyboard**.
- Dynamic **templates**
- **History** of each entry.
- Precise management of **settings**.
- Code written in **native languages** *(Kotlin / Java / JNI / C)*.

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 30
versionCode = 89
versionName = "3.0.2"
versionCode = 91
versionName = "3.0.4"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -99,33 +99,33 @@ android {
}
}
def room_version = "2.2.6"
def room_version = "2.3.0"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.appcompat:appcompat:$android_appcompat_version"
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.1.0'
implementation 'androidx.media:media:1.4.3'
// Lifecycle - LiveData - ViewModel - Coroutines
implementation "androidx.core:core-ktx:1.3.2"
implementation 'androidx.fragment:fragment-ktx:1.2.5'
// WARNING: Don't upgrade because slowdown https://github.com/Kunzisoft/KeePassDX/issues/923
implementation 'com.google.android.material:material:1.1.0'
implementation "androidx.core:core-ktx:$android_core_version"
implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation "com.google.android.material:material:$android_material_version"
// Database
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// Autofill
implementation "androidx.autofill:autofill:1.1.0"
// Time
implementation 'joda-time:joda-time:2.10.6'
implementation 'joda-time:joda-time:2.10.13'
// Color
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.4'
// Education
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.0'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
// Apache Commons
implementation 'commons-io:commons-io:2.8.0'
implementation 'commons-codec:commons-codec:1.15'
@@ -136,6 +136,6 @@ dependencies {
implementation project(path: ':icon-pack-material')
// Tests
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation "androidx.test:runner:$android_test_version"
androidTestImplementation "androidx.test:rules:$android_test_version"
}

View File

@@ -44,6 +44,7 @@
android:theme="@style/KeepassDXStyle.SplashScreen"
android:label="@string/app_name"
android:launchMode="singleTop"
android:exported="true"
android:configChanges="keyboardHidden"
android:windowSoftInputMode="stateHidden|stateAlwaysHidden" >
<intent-filter>
@@ -53,6 +54,7 @@
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.PasswordActivity"
android:exported="true"
android:configChanges="keyboardHidden"
android:windowSoftInputMode="adjustResize|stateUnchanged">
<intent-filter>
@@ -111,6 +113,7 @@
<!-- Main Activity -->
<activity
android:name="com.kunzisoft.keepass.activities.GroupActivity"
android:exported="false"
android:configChanges="keyboardHidden"
android:windowSoftInputMode="adjustPan">
<meta-data
@@ -154,7 +157,8 @@
android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
<activity
android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity"
android:theme="@style/Theme.Transparent">
android:theme="@style/Theme.Transparent"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
@@ -173,7 +177,8 @@
android:theme="@style/Theme.Transparent" />
<activity
android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
android:label="@string/keyboard_setting_label">
android:label="@string/keyboard_setting_label"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
@@ -199,6 +204,7 @@
<service
android:name="com.kunzisoft.keepass.autofill.KeeAutofillService"
android:label="@string/autofill_service_name"
android:exported="true"
android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<meta-data
android:name="android.autofill"
@@ -210,6 +216,7 @@
<service
android:name="com.kunzisoft.keepass.magikeyboard.MagikeyboardService"
android:label="@string/keyboard_label"
android:exported="true"
android:permission="android.permission.BIND_INPUT_METHOD" >
<meta-data android:name="android.view.im"
android:resource="@xml/keyboard_method"/>

View File

@@ -23,28 +23,33 @@ import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.os.Build
import android.view.inputmethod.InlineSuggestionsRequest
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.RequiresApi
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.EXTRA_INLINE_SUGGESTIONS_REQUEST
import com.kunzisoft.keepass.autofill.CompatInlineSuggestionsRequest
import com.kunzisoft.keepass.autofill.KeeAutofillService
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.LOCK_ACTION
@RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : DatabaseModeActivity() {
private var mAutofillActivityResultLauncher: ActivityResultLauncher<Intent>? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
AutofillHelper.buildActivityResultLauncher(this, true)
else null
override fun applyCustomStyle(): Boolean {
return false
}
@@ -60,17 +65,37 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
EntrySelectionHelper.retrieveSpecialModeFromIntent(intent).let { specialMode ->
when (specialMode) {
SpecialMode.SELECTION -> {
// Build search param
val searchInfo = SearchInfo().apply {
applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
webScheme = intent.getStringExtra(KEY_SEARCH_SCHEME)
manualSelection = intent.getBooleanExtra(KEY_MANUAL_SELECTION, false)
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
launchSelection(database, searchInfo)
intent.getBundleExtra(KEY_SELECTION_BUNDLE)?.let { bundle ->
// To pass extra inline request
var compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
compatInlineSuggestionsRequest = bundle.getParcelable(KEY_INLINE_SUGGESTION)
}
// Build search param
bundle.getParcelable<SearchInfo>(KEY_SEARCH_INFO)?.let { searchInfo ->
SearchInfo.getConcreteWebDomain(
this,
searchInfo.webDomain
) { concreteWebDomain ->
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
val assistStructure = AutofillHelper
.retrieveAutofillComponent(intent)
?.assistStructure
val newAutofillComponent = if (assistStructure != null) {
AutofillComponent(
assistStructure,
compatInlineSuggestionsRequest
)
} else {
null
}
searchInfo.webDomain = concreteWebDomain
launchSelection(database, newAutofillComponent, searchInfo)
}
}
}
// Remove bundle
intent.removeExtra(KEY_SELECTION_BUNDLE)
}
SpecialMode.REGISTRATION -> {
// To register info
@@ -91,10 +116,8 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
}
private fun launchSelection(database: Database?,
autofillComponent: AutofillComponent?,
searchInfo: SearchInfo) {
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
val autofillComponent = AutofillHelper.retrieveAutofillComponent(intent)
if (autofillComponent == null) {
setResult(Activity.RESULT_CANCELED)
finish()
@@ -119,6 +142,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
// Show the database UI to select the entry
GroupActivity.launchForAutofillResult(this,
openedDatabase,
mAutofillActivityResultLauncher,
autofillComponent,
searchInfo,
false)
@@ -126,6 +150,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
{
// If database not open
FileDatabaseSelectActivity.launchForAutofillResult(this,
mAutofillActivityResultLauncher,
autofillComponent,
searchInfo)
}
@@ -186,55 +211,49 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
Toast.makeText(this.applicationContext, R.string.autofill_read_only_save, Toast.LENGTH_LONG).show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
if (PreferencesUtil.isAutofillCloseDatabaseEnable(this)) {
// Close the database
sendBroadcast(Intent(LOCK_ACTION))
}
super.onActivityResult(requestCode, resultCode, data)
}
companion object {
private const val KEY_MANUAL_SELECTION = "KEY_MANUAL_SELECTION"
private const val KEY_SEARCH_APPLICATION_ID = "KEY_SEARCH_APPLICATION_ID"
private const val KEY_SEARCH_DOMAIN = "KEY_SEARCH_DOMAIN"
private const val KEY_SEARCH_SCHEME = "KEY_SEARCH_SCHEME"
private const val KEY_SELECTION_BUNDLE = "KEY_SELECTION_BUNDLE"
private const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
private const val KEY_INLINE_SUGGESTION = "KEY_INLINE_SUGGESTION"
private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"
fun getPendingIntentForSelection(context: Context,
searchInfo: SearchInfo? = null,
inlineSuggestionsRequest: InlineSuggestionsRequest? = null): PendingIntent {
compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null): PendingIntent {
return PendingIntent.getActivity(context, 0,
// Doesn't work with Parcelable (don't know why?)
Intent(context, AutofillLauncherActivity::class.java).apply {
searchInfo?.let {
putExtra(KEY_SEARCH_APPLICATION_ID, it.applicationId)
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
putExtra(KEY_SEARCH_SCHEME, it.webScheme)
putExtra(KEY_MANUAL_SELECTION, it.manualSelection)
}
// Doesn't work with direct extra Parcelable (don't know why?)
// Wrap into a bundle to bypass the problem
Intent(context, AutofillLauncherActivity::class.java).apply {
putExtra(KEY_SELECTION_BUNDLE, Bundle().apply {
putParcelable(KEY_SEARCH_INFO, searchInfo)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlineSuggestionsRequest?.let {
putExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST, it)
}
putParcelable(KEY_INLINE_SUGGESTION, compatInlineSuggestionsRequest)
}
},
PendingIntent.FLAG_CANCEL_CURRENT)
})
},
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// TODO Mutable
PendingIntent.FLAG_CANCEL_CURRENT
} else {
PendingIntent.FLAG_CANCEL_CURRENT
})
}
fun getPendingIntentForRegistration(context: Context,
registerInfo: RegisterInfo): PendingIntent {
return PendingIntent.getActivity(context, 0,
Intent(context, AutofillLauncherActivity::class.java).apply {
EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION)
putExtra(KEY_REGISTER_INFO, registerInfo)
},
PendingIntent.FLAG_CANCEL_CURRENT)
Intent(context, AutofillLauncherActivity::class.java).apply {
EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION)
putExtra(KEY_REGISTER_INFO, registerInfo)
},
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// TODO Mutable
PendingIntent.FLAG_CANCEL_CURRENT
} else {
PendingIntent.FLAG_CANCEL_CURRENT
})
}
fun launchForRegistration(context: Context,

View File

@@ -32,6 +32,7 @@ import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
@@ -62,7 +63,6 @@ import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.EntryViewModel
import java.util.*
import kotlin.collections.HashMap
class EntryActivity : DatabaseLockActivity() {
@@ -84,8 +84,13 @@ class EntryActivity : DatabaseLockActivity() {
private var mEntryLoaded = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
private var mExternalFileHelper: ExternalFileHelper? = null
private var mAttachmentSelected: Attachment? = null
private var mEntryActivityResultLauncher = EntryEditActivity.registerForEntryResult(this) {
// Reload the current id from database
mEntryViewModel.loadDatabase(mDatabase)
}
private var mIcon: IconImage? = null
private var mIconColor: Int = 0
@@ -133,6 +138,15 @@ class EntryActivity : DatabaseLockActivity() {
// Init SAF manager
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildCreateDocument { createdFileUri ->
mAttachmentSelected?.let { attachment ->
if (createdFileUri != null) {
mAttachmentFileBinderManager
?.startDownloadAttachment(createdFileUri, attachment)
}
mAttachmentSelected = null
}
}
// Init attachment service binder manager
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
@@ -209,9 +223,8 @@ class EntryActivity : DatabaseLockActivity() {
}
mEntryViewModel.attachmentSelected.observe(this) { attachmentSelected ->
mExternalFileHelper?.createDocument(attachmentSelected.name)?.let { requestCode ->
mAttachmentsToDownload[requestCode] = attachmentSelected
}
mAttachmentSelected = attachmentSelected
mExternalFileHelper?.createDocument(attachmentSelected.name)
}
mEntryViewModel.historySelected.observe(this) { historySelected ->
@@ -220,7 +233,8 @@ class EntryActivity : DatabaseLockActivity() {
this,
database,
historySelected.nodeId,
historySelected.historyPosition
historySelected.historyPosition,
mEntryActivityResultLauncher
)
}
}
@@ -290,26 +304,6 @@ class EntryActivity : DatabaseLockActivity() {
super.onPause()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
// Reload the current id from database
mEntryViewModel.loadDatabase(mDatabase)
}
}
mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
if (mEntryLoaded) {
@@ -391,7 +385,8 @@ class EntryActivity : DatabaseLockActivity() {
EntryEditActivity.launchToUpdate(
this,
database,
entryId
entryId,
mEntryActivityResultLauncher
)
}
}
@@ -432,7 +427,7 @@ class EntryActivity : DatabaseLockActivity() {
// Transit data in previous Activity after an update
Intent().apply {
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mMainEntryId)
setResult(EntryEditActivity.ADD_OR_UPDATE_ENTRY_RESULT_CODE, this)
setResult(Activity.RESULT_OK, this)
}
super.finish()
}
@@ -450,15 +445,13 @@ class EntryActivity : DatabaseLockActivity() {
*/
fun launch(activity: Activity,
database: Database,
entryId: NodeId<UUID>) {
entryId: NodeId<UUID>,
activityResultLauncher: ActivityResultLauncher<Intent>) {
if (database.loaded) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
activity.startActivityForResult(
intent,
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE
)
activityResultLauncher.launch(intent)
}
}
}
@@ -469,16 +462,14 @@ class EntryActivity : DatabaseLockActivity() {
fun launch(activity: Activity,
database: Database,
entryId: NodeId<UUID>,
historyPosition: Int) {
historyPosition: Int,
activityResultLauncher: ActivityResultLauncher<Intent>) {
if (database.loaded) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
activity.startActivityForResult(
intent,
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE
)
activityResultLauncher.launch(intent)
}
}
}

View File

@@ -33,12 +33,17 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.*
@@ -96,6 +101,7 @@ class EntryEditActivity : DatabaseLockActivity(),
private var mTemplate: Template? = null
private var mIsTemplate: Boolean = false
private var mEntryLoaded: Boolean = false
private var mTemplatesSelectorAdapter: TemplatesSelectorAdapter? = null
private var mAllowCustomFields = false
private var mAllowOTP = false
@@ -106,6 +112,10 @@ class EntryEditActivity : DatabaseLockActivity(),
// Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null
private var mIconSelectionActivityResultLauncher = IconPickerActivity.registerIconSelectionForResult(this) { icon ->
mEntryEditViewModel.selectIcon(icon)
}
// To ask data lost only one time
private var backPressedAlreadyApproved = false
@@ -154,6 +164,21 @@ class EntryEditActivity : DatabaseLockActivity(),
// To retrieve attachment
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let { attachmentToUploadUri ->
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile ->
documentFile.name?.let { fileName ->
if (documentFile.length() > MAX_WARNING_BINARY_FILE) {
FileTooBigDialogFragment.build(attachmentToUploadUri, fileName)
.show(supportFragmentManager, "fileTooBigFragment")
} else {
mEntryEditViewModel.buildNewAttachment(attachmentToUploadUri, fileName)
}
}
}
}
}
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
// Verify the education views
entryEditActivityEducation = EntryEditActivityEducation(this)
@@ -175,11 +200,13 @@ class EntryEditActivity : DatabaseLockActivity(),
templateSelectorSpinner?.apply {
// Build template selector
if (templates.isNotEmpty()) {
adapter = TemplatesSelectorAdapter(
mTemplatesSelectorAdapter = TemplatesSelectorAdapter(
this@EntryEditActivity,
mIconDrawableFactory,
templates
)
).apply {
iconDrawableFactory = mIconDrawableFactory
}
adapter = mTemplatesSelectorAdapter
val selectedTemplate = if (mTemplate != null)
mTemplate
else
@@ -213,7 +240,7 @@ class EntryEditActivity : DatabaseLockActivity(),
// View model listeners
mEntryEditViewModel.requestIconSelection.observe(this) { iconImage ->
IconPickerActivity.launch(this@EntryEditActivity, iconImage)
IconPickerActivity.launch(this@EntryEditActivity, iconImage, mIconSelectionActivityResultLauncher)
}
mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant ->
@@ -321,6 +348,10 @@ class EntryEditActivity : DatabaseLockActivity(),
mAllowCustomFields = database?.allowEntryCustomFields() == true
mAllowOTP = database?.allowOTP == true
mEntryEditViewModel.loadDatabase(database)
mTemplatesSelectorAdapter?.apply {
iconDrawableFactory = mIconDrawableFactory
notifyDataSetChanged()
}
}
override fun onDatabaseActionFinished(
@@ -472,29 +503,6 @@ class EntryEditActivity : DatabaseLockActivity(),
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
IconPickerActivity.onActivityResult(requestCode, resultCode, data) { icon ->
mEntryEditViewModel.selectIcon(icon)
}
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
uri?.let { attachmentToUploadUri ->
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile ->
documentFile.name?.let { fileName ->
if (documentFile.length() > MAX_WARNING_BINARY_FILE) {
FileTooBigDialogFragment.build(attachmentToUploadUri, fileName)
.show(supportFragmentManager, "fileTooBigFragment")
} else {
mEntryEditViewModel.buildNewAttachment(attachmentToUploadUri, fileName)
}
}
}
}
}
}
/**
* Set up OTP (HOTP or TOTP) and add it as extra field
*/
@@ -585,7 +593,7 @@ class EntryEditActivity : DatabaseLockActivity(),
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation(
attachmentView,
{
mExternalFileHelper?.openDocument()
addNewAttachment()
},
{
performedNextEducation(entryEditActivityEducation)
@@ -686,7 +694,7 @@ class EntryEditActivity : DatabaseLockActivity(),
val intentEntry = Intent()
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry.nodeId)
intentEntry.putExtras(bundle)
setResult(ADD_OR_UPDATE_ENTRY_RESULT_CODE, intentEntry)
setResult(Activity.RESULT_OK, intentEntry)
super.finish()
} catch (e: Exception) {
// Exception when parcelable can't be done
@@ -701,23 +709,46 @@ class EntryEditActivity : DatabaseLockActivity(),
// Keys for current Activity
const val KEY_ENTRY = "entry"
const val KEY_PARENT = "parent"
// Keys for callback
const val ADD_OR_UPDATE_ENTRY_RESULT_CODE = 31
const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129
const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
fun registerForEntryResult(fragment: Fragment,
entryAddedOrUpdatedListener: (NodeId<UUID>?) -> Unit): ActivityResultLauncher<Intent> {
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
entryAddedOrUpdatedListener.invoke(
result.data?.getParcelableExtra(ADD_OR_UPDATE_ENTRY_KEY)
)
} else {
entryAddedOrUpdatedListener.invoke(null)
}
}
}
fun registerForEntryResult(activity: FragmentActivity,
entryAddedOrUpdatedListener: (NodeId<UUID>?) -> Unit): ActivityResultLauncher<Intent> {
return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
entryAddedOrUpdatedListener.invoke(
result.data?.getParcelableExtra(ADD_OR_UPDATE_ENTRY_KEY)
)
} else {
entryAddedOrUpdatedListener.invoke(null)
}
}
}
/**
* Launch EntryEditActivity to update an existing entry by his [entryId]
*/
fun launchToUpdate(activity: Activity,
database: Database,
entryId: NodeId<UUID>) {
entryId: NodeId<UUID>,
activityResultLauncher: ActivityResultLauncher<Intent>) {
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, entryId)
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
activityResultLauncher.launch(intent)
}
}
}
@@ -727,12 +758,13 @@ class EntryEditActivity : DatabaseLockActivity(),
*/
fun launchToCreate(activity: Activity,
database: Database,
groupId: NodeId<*>) {
groupId: NodeId<*>,
activityResultLauncher: ActivityResultLauncher<Intent>) {
if (database.loaded && !database.isReadOnly) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, groupId)
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
activityResultLauncher.launch(intent)
}
}
}
@@ -795,8 +827,9 @@ class EntryEditActivity : DatabaseLockActivity(),
* Launch EntryEditActivity to add a new entry in autofill selection
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
fun launchForAutofillResult(activity: AppCompatActivity,
database: Database,
activityResultLauncher: ActivityResultLauncher<Intent>?,
autofillComponent: AutofillComponent,
groupId: NodeId<*>,
searchInfo: SearchInfo? = null) {
@@ -807,6 +840,7 @@ class EntryEditActivity : DatabaseLockActivity(),
AutofillHelper.startActivityForAutofillResult(
activity,
intent,
activityResultLauncher,
autofillComponent,
searchInfo
)

View File

@@ -31,8 +31,10 @@ import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.LinearLayoutManager
@@ -85,6 +87,11 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
private var mExternalFileHelper: ExternalFileHelper? = null
private var mAutofillActivityResultLauncher: ActivityResultLauncher<Intent>? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
AutofillHelper.buildActivityResultLauncher(this)
else null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -109,6 +116,22 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
// Open database button
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let {
launchPasswordActivityWithPath(uri)
}
}
mExternalFileHelper?.buildCreateDocument("application/x-keepass") { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
} else {
val error = getString(R.string.error_create_database)
Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_LONG).asError().show()
Log.e(TAG, error)
}
}
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView?.setOpenDocumentClickListener(mExternalFileHelper)
@@ -256,8 +279,9 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
* Create a new file by calling the content provider
*/
private fun createNewFile() {
mExternalFileHelper?.createDocument( getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default), "application/x-keepass")
mExternalFileHelper?.createDocument(
getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default))
}
private fun fileNoFoundAction(e: FileNotFoundException) {
@@ -274,7 +298,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
fileNoFoundAction(exception)
},
{ onCancelSpecialMode() },
{ onLaunchActivitySpecialMode() })
{ onLaunchActivitySpecialMode() },
mAutofillActivityResultLauncher)
}
private fun launchGroupActivityIfLoaded(database: Database) {
@@ -283,7 +308,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
database,
{ onValidateSpecialMode() },
{ onCancelSpecialMode() },
{ onLaunchActivitySpecialMode() })
{ onLaunchActivitySpecialMode() },
mAutofillActivityResultLauncher)
}
}
@@ -359,33 +385,6 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
override fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential) {}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
}
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
if (uri != null) {
launchPasswordActivityWithPath(uri)
}
}
// Retrieve the created URI from the file manager
mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
} else {
val error = getString(R.string.error_create_database)
Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_LONG).asError().show()
Log.e(TAG, error)
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
@@ -499,11 +498,13 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
fun launchForAutofillResult(activity: AppCompatActivity,
activityResultLauncher: ActivityResultLauncher<Intent>?,
autofillComponent: AutofillComponent,
searchInfo: SearchInfo? = null) {
AutofillHelper.startActivityForAutofillResult(activity,
Intent(activity, FileDatabaseSelectActivity::class.java),
activityResultLauncher,
autofillComponent,
searchInfo)
}

View File

@@ -33,8 +33,10 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
@@ -111,6 +113,16 @@ class GroupActivity : DatabaseLockActivity(),
private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
private var mOnSuggestionListener: SearchView.OnSuggestionListener? = null
private var mIconSelectionActivityResultLauncher = IconPickerActivity.registerIconSelectionForResult(this) { icon ->
// To create tree dialog for icon
mGroupEditViewModel.selectIcon(icon)
}
private var mAutofillActivityResultLauncher: ActivityResultLauncher<Intent>? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
AutofillHelper.buildActivityResultLauncher(this)
else null
private var mIconColor: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
@@ -211,11 +223,14 @@ class GroupActivity : DatabaseLockActivity(),
mDatabase?.let { database ->
EntrySelectionHelper.doSpecialAction(intent,
{
EntryEditActivity.launchToCreate(
this@GroupActivity,
database,
currentGroup.nodeId
)
mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher ->
EntryEditActivity.launchToCreate(
this@GroupActivity,
database,
currentGroup.nodeId,
resultLauncher
)
}
},
{
// Search not used
@@ -243,6 +258,7 @@ class GroupActivity : DatabaseLockActivity(),
EntryEditActivity.launchForAutofillResult(
this@GroupActivity,
database,
mAutofillActivityResultLauncher,
autofillComponent,
currentGroup.nodeId,
searchInfo
@@ -277,7 +293,7 @@ class GroupActivity : DatabaseLockActivity(),
}
mGroupEditViewModel.requestIconSelection.observe(this) { iconImage ->
IconPickerActivity.launch(this@GroupActivity, iconImage)
IconPickerActivity.launch(this@GroupActivity, iconImage, mIconSelectionActivityResultLauncher)
}
mGroupEditViewModel.requestDateTimeSelection.observe(this) { dateInstant ->
@@ -319,6 +335,29 @@ class GroupActivity : DatabaseLockActivity(),
return rootContainerView
}
private fun loadGroup(database: Database?) {
when {
Intent.ACTION_SEARCH == intent.action -> {
finishNodeAction()
val searchString =
intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
mGroupViewModel.loadGroupFromSearch(
database,
searchString,
PreferencesUtil.omitBackup(this)
)
}
mCurrentGroupState == null -> {
mRootGroup?.let { rootGroup ->
mGroupViewModel.loadGroup(database, rootGroup, 0)
}
}
else -> {
mGroupViewModel.loadGroup(database, mCurrentGroupState)
}
}
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
@@ -328,13 +367,7 @@ class GroupActivity : DatabaseLockActivity(),
&& database?.isRecycleBinEnabled == true
mRootGroup = database?.rootGroup
if (mCurrentGroupState == null) {
mRootGroup?.let { rootGroup ->
mGroupViewModel.loadGroup(database, rootGroup, 0)
}
} else {
mGroupViewModel.loadGroup(database, mCurrentGroupState)
}
loadGroup(database)
// Search suggestion
database?.let {
@@ -447,16 +480,7 @@ class GroupActivity : DatabaseLockActivity(),
}
// To transform KEY_SEARCH_INFO in ACTION_SEARCH
transformSearchInfoIntent(intent)
if (Intent.ACTION_SEARCH == intent.action) {
finishNodeAction()
val searchString =
intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
mGroupViewModel.loadGroupFromSearch(
mDatabase,
searchString,
PreferencesUtil.omitBackup(this)
)
}
loadGroup(mDatabase)
}
}
@@ -594,11 +618,14 @@ class GroupActivity : DatabaseLockActivity(),
val entryVersioned = node as Entry
EntrySelectionHelper.doSpecialAction(intent,
{
EntryActivity.launch(
this@GroupActivity,
database,
entryVersioned.nodeId
)
mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher ->
EntryActivity.launch(
this@GroupActivity,
database,
entryVersioned.nodeId,
resultLauncher
)
}
},
{
// Nothing here, a search is simply performed
@@ -795,11 +822,16 @@ class GroupActivity : DatabaseLockActivity(),
GroupEditDialogFragment.TAG_CREATE_GROUP
)
}
Type.ENTRY -> EntryEditActivity.launchToUpdate(
this@GroupActivity,
database,
(node as Entry).nodeId
)
Type.ENTRY -> {
mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher ->
EntryEditActivity.launchToUpdate(
this@GroupActivity,
database,
(node as Entry).nodeId,
resultLauncher
)
}
}
}
reloadGroupIfSearch()
return true
@@ -984,7 +1016,7 @@ class GroupActivity : DatabaseLockActivity(),
if (!sortMenuEducationPerformed) {
// lockMenuEducationPerformed
val lockButtonView = findViewById<View>(R.id.lock_button_icon)
val lockButtonView = findViewById<View>(R.id.lock_button)
lockButtonView != null
&& groupActivityEducation.checkAndPerformedLockMenuEducation(
lockButtonView,
@@ -1057,37 +1089,6 @@ class GroupActivity : DatabaseLockActivity(),
}
}
override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
/*
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
*/
if (Intent.ACTION_SEARCH == intent.action) {
var flags = intent.flags
flags = flags and Intent.FLAG_ACTIVITY_NEW_TASK.inv()
intent.flags = flags
}
super.startActivityForResult(intent, requestCode, options)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// To create tree dialog for icon
IconPickerActivity.onActivityResult(requestCode, resultCode, data) { icon ->
mGroupEditViewModel.selectIcon(icon)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
}
// Directly used the onActivityResult in fragment
mGroupFragment?.onActivityResult(requestCode, resultCode, data)
}
private fun removeSearch() {
intent.removeExtra(AUTO_SEARCH_KEY)
if (Intent.ACTION_SEARCH == intent.action) {
@@ -1292,8 +1293,9 @@ class GroupActivity : DatabaseLockActivity(),
* -------------------------
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
fun launchForAutofillResult(activity: AppCompatActivity,
database: Database,
activityResultLaunch: ActivityResultLauncher<Intent>?,
autofillComponent: AutofillComponent,
searchInfo: SearchInfo? = null,
autoSearch: Boolean = false) {
@@ -1303,6 +1305,7 @@ class GroupActivity : DatabaseLockActivity(),
AutofillHelper.startActivityForAutofillResult(
activity,
intent,
activityResultLaunch,
autofillComponent,
searchInfo
)
@@ -1335,11 +1338,12 @@ class GroupActivity : DatabaseLockActivity(),
* Global Launch
* -------------------------
*/
fun launch(activity: Activity,
fun launch(activity: AppCompatActivity,
database: Database,
onValidateSpecialMode: () -> Unit,
onCancelSpecialMode: () -> Unit,
onLaunchActivitySpecialMode: () -> Unit) {
onLaunchActivitySpecialMode: () -> Unit,
autofillActivityResultLauncher: ActivityResultLauncher<Intent>?) {
EntrySelectionHelper.doSpecialAction(activity.intent,
{
GroupActivity.launch(
@@ -1451,6 +1455,7 @@ class GroupActivity : DatabaseLockActivity(),
// Here no search info found, disable auto search
GroupActivity.launchForAutofillResult(activity,
database,
autofillActivityResultLauncher,
autofillComponent,
searchInfo,
false)

View File

@@ -27,12 +27,16 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.IconEditDialogFragment
import com.kunzisoft.keepass.activities.fragments.IconPickerFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
@@ -81,6 +85,9 @@ class IconPickerActivity : DatabaseLockActivity() {
coordinatorLayout = findViewById(R.id.icon_picker_coordinator)
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
addCustomIcon(uri)
}
uploadButton = findViewById(R.id.icon_picker_upload)
@@ -139,6 +146,16 @@ class IconPickerActivity : DatabaseLockActivity() {
}
uploadButton.isEnabled = true
}
iconPickerViewModel.customIconUpdated.observe(this) { iconCustomUpdated ->
if (iconCustomUpdated.error && !iconCustomUpdated.errorConsumed) {
Snackbar.make(coordinatorLayout, iconCustomUpdated.errorStringId, Snackbar.LENGTH_LONG).asError().show()
iconCustomUpdated.errorConsumed = true
}
iconCustomUpdated.iconCustom?.let {
mDatabase?.updateCustomIcon(it)
}
iconPickerViewModel.deselectAllCustomIcons()
}
}
override fun viewToInvalidateTimeout(): View? {
@@ -197,6 +214,10 @@ class IconPickerActivity : DatabaseLockActivity() {
}
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
menu?.findItem(R.id.menu_edit)?.apply {
isEnabled = mIconsSelected.size == 1
isVisible = isEnabled
}
menu?.findItem(R.id.menu_delete)?.apply {
isEnabled = mCustomIconsSelectionMode
isVisible = isEnabled
@@ -213,6 +234,9 @@ class IconPickerActivity : DatabaseLockActivity() {
onBackPressed()
}
}
R.id.menu_edit -> {
updateCustomIcon(mIconsSelected[0])
}
R.id.menu_delete -> {
mIconsSelected.forEach { iconToRemove ->
removeCustomIcon(iconToRemove)
@@ -277,6 +301,11 @@ class IconPickerActivity : DatabaseLockActivity() {
}
}
private fun updateCustomIcon(iconImageCustom: IconImageCustom) {
IconEditDialogFragment.update(iconImageCustom)
.show(supportFragmentManager, IconEditDialogFragment.TAG_UPDATE_ICON)
}
private fun removeCustomIcon(iconImageCustom: IconImageCustom) {
uploadButton.isEnabled = false
iconPickerViewModel.deselectAllCustomIcons()
@@ -286,14 +315,6 @@ class IconPickerActivity : DatabaseLockActivity() {
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
addCustomIcon(uri)
}
}
private fun setResult() {
setResult(Activity.RESULT_OK, Intent().apply {
putExtra(EXTRA_ICON, mIconImage)
@@ -308,30 +329,28 @@ class IconPickerActivity : DatabaseLockActivity() {
companion object {
private const val ICON_PICKER_FRAGMENT_TAG = "ICON_PICKER_FRAGMENT_TAG"
private const val ICON_SELECTED_REQUEST = 15861
private const val EXTRA_ICON = "EXTRA_ICON"
private const val MAX_ICON_SIZE = 5242880
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?, listener: (icon: IconImage) -> Unit) {
if (requestCode == ICON_SELECTED_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
listener.invoke(data?.getParcelableExtra(EXTRA_ICON) ?: IconImage())
fun registerIconSelectionForResult(context: FragmentActivity,
listener: (icon: IconImage) -> Unit): ActivityResultLauncher<Intent> {
return context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
listener.invoke(result.data?.getParcelableExtra(EXTRA_ICON) ?: IconImage())
}
}
}
fun launch(context: Activity,
previousIcon: IconImage?) {
fun launch(context: FragmentActivity,
previousIcon: IconImage?,
resultLauncher: ActivityResultLauncher<Intent>) {
// Create an instance to return the picker icon
context.startActivityForResult(
Intent(context,
IconPickerActivity::class.java).apply {
resultLauncher.launch(
Intent(context, IconPickerActivity::class.java).apply {
if (previousIcon != null)
putExtra(EXTRA_ICON, previousIcon)
},
ICON_SELECTED_REQUEST)
}
)
}
}
}

View File

@@ -35,9 +35,10 @@ import android.view.KeyEvent.KEYCODE_ENTER
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.view.inputmethod.InputMethodManager
import android.widget.*
import android.widget.TextView.OnEditorActionListener
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat
@@ -71,11 +72,12 @@ import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import java.io.FileNotFoundException
open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
// Views
private var toolbar: Toolbar? = null
@@ -89,7 +91,8 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
private lateinit var coordinatorLayout: CoordinatorLayout
private var advancedUnlockFragment: AdvancedUnlockFragment? = null
private val databaseFileViewModel: DatabaseFileViewModel by viewModels()
private val mDatabaseFileViewModel: DatabaseFileViewModel by viewModels()
private val mAdvancedUnlockViewModel: AdvancedUnlockViewModel by viewModels()
private var mDefaultDatabase: Boolean = false
private var mDatabaseFileUri: Uri? = null
@@ -111,7 +114,10 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
field = value
}
private var mAllowAutoOpenBiometricPrompt: Boolean = true
private var mAutofillActivityResultLauncher: ActivityResultLauncher<Intent>? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
AutofillHelper.buildActivityResultLauncher(this)
else null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -142,6 +148,12 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
mExternalFileHelper?.buildOpenDocument { uri ->
if (uri != null) {
mDatabaseKeyFileUri = uri
populateKeyFileTextView(uri)
}
}
keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
passwordView?.setOnEditorActionListener(onEditorActionListener)
@@ -170,9 +182,6 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE))
}
if (savedInstanceState?.containsKey(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT) == true) {
mAllowAutoOpenBiometricPrompt = savedInstanceState.getBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT)
}
// Init Biometric elements
advancedUnlockFragment = supportFragmentManager
@@ -188,17 +197,17 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
// Listen password checkbox to init advanced unlock and confirmation button
checkboxPasswordView?.setOnCheckedChangeListener { _, _ ->
advancedUnlockFragment?.checkUnlockAvailability()
mAdvancedUnlockViewModel.checkUnlockAvailability()
enableOrNotTheConfirmationButton()
}
// Observe if default database
databaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
mDefaultDatabase = isDefaultDatabase
}
// Observe database file change
databaseFileViewModel.databaseFileLoaded.observe(this) { databaseFile ->
mDatabaseFileViewModel.databaseFileLoaded.observe(this) { databaseFile ->
// Force read only if the file does not exists
mForceReadOnly = databaseFile?.let {
!it.databaseFileExists
@@ -232,12 +241,12 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
}
// Don't allow auto open prompt if lock become when UI visible
mAllowAutoOpenBiometricPrompt = if (DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true)
false
else
mAllowAutoOpenBiometricPrompt
if (DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true) {
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false
}
mDatabaseFileUri?.let { databaseFileUri ->
databaseFileViewModel.loadDatabaseFile(databaseFileUri)
mDatabaseFileViewModel.loadDatabaseFile(databaseFileUri)
}
checkPermission()
@@ -263,7 +272,7 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error
advancedUnlockFragment?.initAdvancedUnlockMode()
mAdvancedUnlockViewModel.initAdvancedUnlockMode()
if (result.isSuccess) {
launchGroupActivityIfLoaded(database)
@@ -311,7 +320,7 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
is FileNotFoundDatabaseException -> {
// Remove this default database inaccessible
if (mDefaultDatabase) {
databaseFileViewModel.removeDefaultDatabase()
mDatabaseFileViewModel.removeDefaultDatabase()
}
}
}
@@ -344,7 +353,7 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
}
mDatabaseFileUri?.let {
databaseFileViewModel.checkIfIsDefaultDatabase(it)
mDatabaseFileViewModel.checkIfIsDefaultDatabase(it)
}
}
@@ -361,7 +370,8 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
database,
{ onValidateSpecialMode() },
{ onCancelSpecialMode() },
{ onLaunchActivitySpecialMode() }
{ onLaunchActivitySpecialMode() },
mAutofillActivityResultLauncher
)
}
}
@@ -435,8 +445,7 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
} else {
// Init Biometric elements
advancedUnlockFragment?.loadDatabase(databaseFileUri,
mAllowAutoOpenBiometricPrompt)
mAdvancedUnlockViewModel.databaseFileLoaded(databaseFileUri)
}
enableOrNotTheConfirmationButton()
@@ -496,7 +505,6 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
override fun onPause() {
// Reinit locking activity UI variable
DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
mAllowAutoOpenBiometricPrompt = true
super.onPause()
}
@@ -507,7 +515,6 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
outState.putString(KEY_KEYFILE, it.toString())
}
outState.putBoolean(KEY_READ_ONLY, mReadOnly)
outState.putBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT, false)
super.onSaveInstanceState(outState)
}
@@ -709,45 +716,6 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
return super.onOptionsItemSelected(item)
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mAllowAutoOpenBiometricPrompt = false
// To get device credential unlock result
advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data)
// To get entry in result
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
}
var keyFileResult = false
mExternalFileHelper?.let {
keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
if (uri != null) {
mDatabaseKeyFileUri = uri
populateKeyFileTextView(uri)
}
}
}
if (!keyFileResult) {
// this block if not a key file response
when (resultCode) {
DatabaseLockActivity.RESULT_EXIT_LOCK -> {
clearCredentialsViews()
closeDatabase()
}
Activity.RESULT_CANCELED -> {
clearCredentialsViews()
}
}
}
}
companion object {
private val TAG = PasswordActivity::class.java.name
@@ -764,8 +732,6 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647
private const val ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT = "ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT"
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, PasswordActivity::class.java)
@@ -855,15 +821,17 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
@RequiresApi(api = Build.VERSION_CODES.O)
@Throws(FileNotFoundException::class)
fun launchForAutofillResult(activity: Activity,
fun launchForAutofillResult(activity: AppCompatActivity,
databaseFile: Uri,
keyFile: Uri?,
activityResultLauncher: ActivityResultLauncher<Intent>?,
autofillComponent: AutofillComponent,
searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
AutofillHelper.startActivityForAutofillResult(
activity,
intent,
activityResultLauncher,
autofillComponent,
searchInfo)
}
@@ -891,12 +859,13 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
* Global Launch
* -------------------------
*/
fun launch(activity: Activity,
fun launch(activity: AppCompatActivity,
databaseUri: Uri,
keyFile: Uri?,
fileNoFoundAction: (exception: FileNotFoundException) -> Unit,
onCancelSpecialMode: () -> Unit,
onLaunchActivitySpecialMode: () -> Unit) {
onLaunchActivitySpecialMode: () -> Unit,
autofillActivityResultLauncher: ActivityResultLauncher<Intent>?) {
try {
EntrySelectionHelper.doSpecialAction(activity.intent,
@@ -926,6 +895,7 @@ open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bui
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PasswordActivity.launchForAutofillResult(activity,
databaseUri, keyFile,
autofillActivityResultLauncher,
autofillComponent,
searchInfo)
onLaunchActivitySpecialMode()

View File

@@ -22,7 +22,6 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.Editable
@@ -133,6 +132,18 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
keyFileSelectionView?.error = null
keyFileCheckBox?.isChecked = true
keyFileSelectionView?.uri = pathUri
if (lengthFile <= 0L) {
showEmptyKeyFileConfirmationDialog()
}
}
}
}
keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
val dialog = builder.create()
@@ -208,7 +219,11 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match)
}
if (mMasterPassword == null || mMasterPassword!!.isEmpty()) {
if ((mMasterPassword == null
|| mMasterPassword!!.isEmpty())
&& (keyFileCheckBox == null
|| !keyFileCheckBox!!.isChecked
|| keyFileSelectionView?.uri == null)) {
error = true
showEmptyPasswordConfirmationDialog()
}
@@ -282,23 +297,6 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
keyFileSelectionView?.error = null
keyFileCheckBox?.isChecked = true
keyFileSelectionView?.uri = pathUri
if (lengthFile <= 0L) {
showEmptyKeyFileConfirmationDialog()
}
}
}
}
}
companion object {
private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG"

View File

@@ -29,6 +29,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
}
}
@Suppress("DEPRECATION")
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
class IconEditDialogFragment : DatabaseDialogFragment() {
private val mIconPickerViewModel: IconPickerViewModel by activityViewModels()
private var mPopulateIconMethod: ((ImageView, IconImage) -> Unit)? = null
private lateinit var iconView: ImageView
private lateinit var nameTextLayoutView: TextInputLayout
private lateinit var nameTextView: TextView
private var mCustomIcon: IconImageCustom? = null
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
mPopulateIconMethod = { imageView, icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon)
}
mCustomIcon?.let { customIcon ->
populateViewsWithCustomIcon(customIcon)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_icon_edit, null)
iconView = root.findViewById(R.id.icon_edit_image)
nameTextLayoutView = root.findViewById(R.id.icon_edit_name_container)
nameTextView = root.findViewById(R.id.icon_edit_name)
if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_CUSTOM_ICON_ID)) {
mCustomIcon = savedInstanceState.getParcelable(KEY_CUSTOM_ICON_ID) ?: mCustomIcon
} else {
arguments?.apply {
if (containsKey(KEY_CUSTOM_ICON_ID)) {
mCustomIcon = getParcelable(KEY_CUSTOM_ICON_ID) ?: mCustomIcon
}
}
}
val builder = AlertDialog.Builder(activity)
builder.setView(root)
.setPositiveButton(android.R.string.ok) { _, _ ->
retrieveIconInfoFromViews()
mCustomIcon?.let { customIcon ->
mIconPickerViewModel.updateCustomIcon(
IconPickerViewModel.IconCustomState(customIcon, false)
)
}
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
// Do nothing
mIconPickerViewModel.updateCustomIcon(
IconPickerViewModel.IconCustomState(null, false)
)
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun populateViewsWithCustomIcon(customIcon: IconImageCustom) {
mPopulateIconMethod?.invoke(iconView, customIcon.getIconImageToDraw())
nameTextView.text = customIcon.name
}
private fun retrieveIconInfoFromViews() {
mCustomIcon?.name = nameTextView.text.toString()
mCustomIcon?.lastModificationTime = DateInstant()
}
override fun onSaveInstanceState(outState: Bundle) {
retrieveIconInfoFromViews()
outState.putParcelable(KEY_CUSTOM_ICON_ID, mCustomIcon)
super.onSaveInstanceState(outState)
}
companion object {
const val TAG_UPDATE_ICON = "TAG_UPDATE_ICON"
const val KEY_CUSTOM_ICON_ID = "KEY_CUSTOM_ICON_ID"
fun update(customIcon: IconImageCustom): IconEditDialogFragment {
val bundle = Bundle()
bundle.putParcelable(KEY_CUSTOM_ICON_ID, IconImageCustom(customIcon))
val fragment = IconEditDialogFragment()
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -309,7 +309,7 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
override fun afterTextChanged(s: Editable?) {
s?.toString()?.let { userString ->
try {
mOtpElement.setBase32Secret(userString.toUpperCase(Locale.ENGLISH))
mOtpElement.setBase32Secret(userString.uppercase(Locale.ENGLISH))
otpSecretContainer?.error = null
} catch (exception: Exception) {
otpSecretContainer?.error = getString(R.string.error_otp_secret_key)

View File

@@ -74,6 +74,19 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
private var mRecycleBinEnable: Boolean = false
private var mRecycleBin: Group? = null
var mEntryActivityResultLauncher = EntryEditActivity.registerForEntryResult(this) { entryId ->
entryId?.let {
// Simply refresh the list
rebuildList()
// Scroll to the new entry
mDatabase?.getEntryById(it)?.let { entry ->
mAdapter?.indexOf(entry)?.let { position ->
mNodesRecyclerView?.scrollToPosition(position)
}
}
} ?: Log.e(this.javaClass.name, "Entry cannot be retrieved in Activity Result")
}
private var mRecycleViewScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
@@ -399,27 +412,6 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
if (resultCode == EntryEditActivity.ADD_OR_UPDATE_ENTRY_RESULT_CODE) {
data?.getParcelableExtra<NodeId<UUID>>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let {
// Simply refresh the list
rebuildList()
// Scroll to the new entry
mDatabase?.getEntryById(it)?.let { entry ->
mAdapter?.indexOf(entry)?.let { position ->
mNodesRecyclerView?.scrollToPosition(position)
}
}
} ?: Log.e(this.javaClass.name, "Entry cannot be retrieved in Activity Result")
}
}
}
}
/**
* Callback listener to redefine to do an action when a node is click
*/

View File

@@ -55,8 +55,10 @@ class IconCustomFragment : IconFragment<IconImageCustom>() {
iconCustomAdded?.iconCustom?.let { icon ->
iconPickerAdapter.addIcon(icon)
iconCustomAdded.iconCustom = null
try {
iconsGridView.smoothScrollToPosition(iconPickerAdapter.lastPosition)
} catch (ignore: Exception) {}
}
iconsGridView.smoothScrollToPosition(iconPickerAdapter.lastPosition)
}
}
iconPickerViewModel.customIconRemoved.observe(viewLifecycleOwner) { iconCustomRemoved ->
@@ -67,6 +69,14 @@ class IconCustomFragment : IconFragment<IconImageCustom>() {
}
}
}
iconPickerViewModel.customIconUpdated.observe(viewLifecycleOwner) { iconCustomUpdated ->
if (!iconCustomUpdated.error) {
iconCustomUpdated?.iconCustom?.let { icon ->
iconPickerAdapter.updateIcon(icon)
iconCustomUpdated.iconCustom = null
}
}
}
}
override fun onIconClickListener(icon: IconImageCustom) {

View File

@@ -20,14 +20,16 @@
package com.kunzisoft.keepass.activities.helpers
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.View
import androidx.annotation.RequiresApi
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
@@ -38,6 +40,10 @@ class ExternalFileHelper {
private var activity: FragmentActivity? = null
private var fragment: Fragment? = null
private var getContentResultLauncher: ActivityResultLauncher<String>? = null
private var openDocumentResultLauncher: ActivityResultLauncher<Array<String>>? = null
private var createDocumentResultLauncher: ActivityResultLauncher<String>? = null
constructor(context: FragmentActivity) {
this.activity = context
this.fragment = null
@@ -48,94 +54,81 @@ class ExternalFileHelper {
this.fragment = context
}
fun buildOpenDocument(onFileSelected: ((uri: Uri?) -> Unit)?) {
val resultCallback = ActivityResultCallback<Uri> { result ->
result?.let { uri ->
UriUtil.takeUriPermission(activity?.contentResolver, uri)
onFileSelected?.invoke(uri)
}
}
getContentResultLauncher = if (fragment != null) {
fragment?.registerForActivityResult(
GetContent(),
resultCallback
)
} else {
activity?.registerForActivityResult(
GetContent(),
resultCallback
)
}
openDocumentResultLauncher = if (fragment != null) {
fragment?.registerForActivityResult(
OpenDocument(),
resultCallback
)
} else {
activity?.registerForActivityResult(
OpenDocument(),
resultCallback
)
}
}
fun buildCreateDocument(typeString: String = "application/octet-stream",
onFileCreated: (fileCreated: Uri?)->Unit) {
val resultCallback = ActivityResultCallback<Uri> { result ->
onFileCreated.invoke(result)
}
createDocumentResultLauncher = if (fragment != null) {
fragment?.registerForActivityResult(
CreateDocument(typeString),
resultCallback
)
} else {
activity?.registerForActivityResult(
CreateDocument(typeString),
resultCallback
)
}
}
fun openDocument(getContent: Boolean = false,
typeString: String = "*/*") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
if (getContent) {
openActivityWithActionGetContent(typeString)
} else {
openActivityWithActionOpenDocument(typeString)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to open document", e)
showFileManagerDialogFragment()
try {
if (getContent) {
getContentResultLauncher?.launch(typeString)
} else {
openDocumentResultLauncher?.launch(arrayOf(typeString))
}
} else {
} catch (e: Exception) {
Log.e(TAG, "Unable to open document", e)
showFileManagerDialogFragment()
}
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun openActivityWithActionOpenDocument(typeString: String) {
val intentOpenDocument = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
fun createDocument(titleString: String) {
try {
createDocumentResultLauncher?.launch(titleString)
} catch (e: Exception) {
Log.e(TAG, "Unable to create document", e)
showFileManagerDialogFragment()
}
if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
else
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun openActivityWithActionGetContent(typeString: String) {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
else
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param onFileSelected Callback retrieve from data
* @return true if requestCode was captured, false elsewhere
*/
fun onOpenDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
onFileSelected: ((uri: Uri?) -> Unit)?): Boolean {
when (requestCode) {
FILE_BROWSE -> {
if (resultCode == RESULT_OK) {
val filename = data?.dataString
var keyUri: Uri? = null
if (filename != null) {
keyUri = UriUtil.parse(filename)
}
onFileSelected?.invoke(keyUri)
}
return true
}
GET_CONTENT, OPEN_DOC -> {
if (resultCode == RESULT_OK) {
if (data != null) {
val uri = data.data
if (uri != null) {
UriUtil.takeUriPermission(activity?.contentResolver, uri)
onFileSelected?.invoke(uri)
}
}
}
return true
}
}
return false
}
/**
@@ -155,62 +148,50 @@ class ExternalFileHelper {
}
}
fun createDocument(titleString: String,
typeString: String = "application/octet-stream"): Int? {
val idCode = getUnusedCreateFileRequestCode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
putExtra(Intent.EXTRA_TITLE, titleString)
class OpenDocument : ActivityResultContracts.OpenDocument() {
@SuppressLint("InlinedApi")
override fun createIntent(context: Context, input: Array<out String>): Intent {
return super.createIntent(context, input).apply {
addCategory(Intent.CATEGORY_OPENABLE)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intent, idCode)
else
activity?.startActivityForResult(intent, idCode)
return idCode
} catch (e: Exception) {
Log.e(TAG, "Unable to create document", e)
showFileManagerDialogFragment()
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
} else {
showFileManagerDialogFragment()
}
return null
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param onFileCreated Callback retrieve from data
* @return true if requestCode was captured, false elsewhere
*/
fun onCreateDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
onFileCreated: (fileCreated: Uri?)->Unit) {
// Retrieve the created URI from the file manager
if (fileRequestCodes.contains(requestCode) && resultCode == RESULT_OK) {
onFileCreated.invoke(data?.data)
fileRequestCodes.remove(requestCode)
class GetContent : ActivityResultContracts.GetContent() {
@SuppressLint("InlinedApi")
override fun createIntent(context: Context, input: String): Intent {
return super.createIntent(context, input).apply {
addCategory(Intent.CATEGORY_OPENABLE)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
}
}
class CreateDocument(private val typeString: String) : ActivityResultContracts.CreateDocument() {
override fun createIntent(context: Context, input: String): Intent {
return super.createIntent(context, input).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
}
}
}
companion object {
private const val TAG = "OpenFileHelper"
private const val GET_CONTENT = 25745
private const val OPEN_DOC = 25845
private const val FILE_BROWSE = 25645
private var CREATE_FILE_REQUEST_CODE_DEFAULT = 3853
private var fileRequestCodes = ArrayList<Int>()
private fun getUnusedCreateFileRequestCode(): Int {
val newCreateFileRequestCode = CREATE_FILE_REQUEST_CODE_DEFAULT++
fileRequestCodes.add(newCreateFileRequestCode)
return newCreateFileRequestCode
}
@SuppressLint("InlinedApi")
fun allowCreateDocumentByStorageAccessFramework(packageManager: PackageManager,
typeString: String = "application/octet-stream"): Boolean {
@@ -231,7 +212,7 @@ class ExternalFileHelper {
fun View.setOpenDocumentClickListener(externalFileHelper: ExternalFileHelper?) {
externalFileHelper?.let { fileHelper ->
setOnClickListener {
fileHelper.openDocument()
fileHelper.openDocument(false)
}
setOnLongClickListener {
fileHelper.openDocument(true)

View File

@@ -100,7 +100,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
}
mDatabaseViewModel.saveDefaultUsername.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveName(it.oldValue, it.newValue, it.save)
mDatabaseTaskProvider?.startDatabaseSaveDefaultUsername(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveColor.observe(this) {
@@ -180,8 +180,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
closeDatabase(database)
if (LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == null)
LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = LOCKING_ACTIVITY_UI_VISIBLE
// Add onActivityForResult response
setResult(RESULT_EXIT_LOCK)
mExitLock = true
closeOptionsMenu()
finish()
}
@@ -353,14 +352,6 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseDeleteEntryHistory(mainEntryId, entryHistoryPosition, mAutoSaveEnable)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_EXIT_LOCK) {
mExitLock = true
lockAndExit()
}
}
private fun checkRegister() {
// If in ave or registration mode, don't allow read only
if ((mSpecialMode == SpecialMode.SAVE
@@ -440,8 +431,6 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
const val TAG = "LockingActivity"
const val RESULT_EXIT_LOCK = 1450
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
const val TIMEOUT_ENABLE_KEY_DEFAULT = true

View File

@@ -39,7 +39,11 @@ object Stylish {
*/
fun load(context: Context) {
Log.d(Stylish::class.java.name, "Attatching to " + context.packageName)
themeString = PreferencesUtil.getStyle(context)
try {
themeString = PreferencesUtil.getStyle(context)
} catch (e: Exception) {
Log.e("Stylish", "Unable to get preference style", e)
}
}
fun retrieveEquivalentSystemStyle(context: Context, styleString: String): String {

View File

@@ -23,12 +23,12 @@ import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import androidx.annotation.StyleRes
import androidx.fragment.app.Fragment
import androidx.appcompat.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StyleRes
import androidx.appcompat.view.ContextThemeWrapper
import androidx.fragment.app.Fragment
abstract class StylishFragment : Fragment() {
@@ -42,7 +42,6 @@ abstract class StylishFragment : Fragment() {
contextThemed = ContextThemeWrapper(context, themeId)
}
@Suppress("DEPRECATION")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// To fix status bar color
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@@ -58,6 +57,7 @@ abstract class StylishFragment : Fragment() {
try {
val taWindowStatusLight = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightStatusBar))
if (taWindowStatusLight?.getBoolean(0, false) == true) {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
taWindowStatusLight?.recycle()

View File

@@ -16,9 +16,9 @@ import com.kunzisoft.keepass.icons.IconDrawableFactory
class TemplatesSelectorAdapter(private val context: Context,
private val iconDrawableFactory: IconDrawableFactory?,
private var templates: List<Template>): BaseAdapter() {
var iconDrawableFactory: IconDrawableFactory? = null
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var mIconColor = Color.BLACK

View File

@@ -4,4 +4,4 @@ import android.app.assist.AssistStructure
import android.view.inputmethod.InlineSuggestionsRequest
data class AutofillComponent(val assistStructure: AssistStructure,
val inlineSuggestionsRequest: InlineSuggestionsRequest?)
val compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?)

View File

@@ -25,7 +25,6 @@ import android.app.PendingIntent
import android.app.assist.AssistStructure
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.graphics.BlendMode
import android.graphics.drawable.Icon
import android.os.Build
@@ -35,11 +34,13 @@ import android.service.autofill.InlinePresentation
import android.util.Log
import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue
import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.RemoteViews
import android.widget.Toast
import android.widget.inline.InlinePresentationSpec
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.v1.InlineSuggestionUi
import androidx.core.content.ContextCompat
@@ -49,21 +50,19 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil
import kotlin.collections.ArrayList
import com.kunzisoft.keepass.utils.LOCK_ACTION
@RequiresApi(api = Build.VERSION_CODES.O)
object AutofillHelper {
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
private const val EXTRA_ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
const val EXTRA_INLINE_SUGGESTIONS_REQUEST = "com.kunzisoft.keepass.autofill.INLINE_SUGGESTIONS_REQUEST"
private const val EXTRA_INLINE_SUGGESTIONS_REQUEST = "com.kunzisoft.keepass.autofill.INLINE_SUGGESTIONS_REQUEST"
fun retrieveAutofillComponent(intent: Intent?): AutofillComponent? {
intent?.getParcelableExtra<AssistStructure?>(EXTRA_ASSIST_STRUCTURE)?.let { assistStructure ->
@@ -112,7 +111,7 @@ object AutofillHelper {
database: Database,
entryInfo: EntryInfo,
struct: StructureParser.Result,
inlinePresentation: InlinePresentation?): Dataset? {
additionalBuild: ((build: Dataset.Builder) -> Unit)? = null): Dataset? {
val title = makeEntryTitle(entryInfo)
val views = newRemoteViews(context, database, title, entryInfo.icon)
val builder = Dataset.Builder(views)
@@ -201,11 +200,7 @@ object AutofillHelper {
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlinePresentation?.let {
builder.setInlinePresentation(it)
}
}
additionalBuild?.invoke(builder)
return try {
builder.build()
@@ -236,40 +231,51 @@ object AutofillHelper {
@SuppressLint("RestrictedApi")
private fun buildInlinePresentationForEntry(context: Context,
database: Database,
inlineSuggestionsRequest: InlineSuggestionsRequest,
compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest,
positionItem: Int,
entryInfo: EntryInfo): InlinePresentation? {
val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
val maxSuggestion = inlineSuggestionsRequest.maxSuggestionCount
compatInlineSuggestionsRequest.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
val maxSuggestion = inlineSuggestionsRequest.maxSuggestionCount
if (positionItem <= maxSuggestion - 1
&& inlinePresentationSpecs.size > positionItem) {
val inlinePresentationSpec = inlinePresentationSpecs[positionItem]
if (positionItem <= maxSuggestion - 1
&& inlinePresentationSpecs.size > positionItem
) {
val inlinePresentationSpec = inlinePresentationSpecs[positionItem]
// Make sure that the IME spec claims support for v1 UI template.
val imeStyle = inlinePresentationSpec.style
if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1))
return null
// Make sure that the IME spec claims support for v1 UI template.
val imeStyle = inlinePresentationSpec.style
if (!UiVersions.getVersions(imeStyle).contains(UiVersions.INLINE_UI_VERSION_1))
return null
// Build the content for IME UI
val pendingIntent = PendingIntent.getActivity(context,
// Build the content for IME UI
val pendingIntent = PendingIntent.getActivity(
context,
0,
Intent(context, AutofillSettingsActivity::class.java),
0)
return InlinePresentation(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
)
return InlinePresentation(
InlineSuggestionUi.newContentBuilder(pendingIntent).apply {
setContentDescription(context.getString(R.string.autofill_sign_in_prompt))
setTitle(entryInfo.title)
setSubtitle(entryInfo.username)
setStartIcon(Icon.createWithResource(context, R.mipmap.ic_launcher_round).apply {
setTintBlendMode(BlendMode.DST)
})
setStartIcon(
Icon.createWithResource(context, R.mipmap.ic_launcher_round).apply {
setTintBlendMode(BlendMode.DST)
})
buildIconFromEntry(context, database, entryInfo)?.let { icon ->
setEndIcon(icon.apply {
setTintBlendMode(BlendMode.DST)
})
}
}.build().slice, inlinePresentationSpec, false)
}.build().slice, inlinePresentationSpec, false
)
}
}
return null
}
@@ -299,7 +305,7 @@ object AutofillHelper {
database: Database,
entriesInfo: List<EntryInfo>,
parseResult: StructureParser.Result,
inlineSuggestionsRequest: InlineSuggestionsRequest?): FillResponse? {
compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?): FillResponse? {
val responseBuilder = FillResponse.Builder()
// Add Header
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@@ -320,7 +326,7 @@ object AutofillHelper {
// Add inline suggestion for new IME and dataset
var numberInlineSuggestions = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlineSuggestionsRequest?.let {
compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
numberInlineSuggestions = minOf(inlineSuggestionsRequest.maxSuggestionCount, entriesInfo.size)
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
if (entriesInfo.size >= inlineSuggestionsRequest.maxSuggestionCount) {
@@ -332,14 +338,19 @@ object AutofillHelper {
}
entriesInfo.forEachIndexed { _, entry ->
val inlinePresentation = if (numberInlineSuggestions > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlineSuggestionsRequest?.let {
buildInlinePresentationForEntry(context, database, inlineSuggestionsRequest, numberInlineSuggestions--, entry)
}
if (numberInlineSuggestions > 0
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& compatInlineSuggestionsRequest != null) {
responseBuilder.addDataset(buildDataset(context, database, entry, parseResult) { builder ->
buildInlinePresentationForEntry(context, database,
compatInlineSuggestionsRequest, numberInlineSuggestions--, entry
)?.let { inlinePresentation ->
builder.setInlinePresentation(inlinePresentation)
}
})
} else {
null
responseBuilder.addDataset(buildDataset(context, database, entry, parseResult))
}
responseBuilder.addDataset(buildDataset(context, database, entry, parseResult, inlinePresentation))
}
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
@@ -351,14 +362,14 @@ object AutofillHelper {
}
val manualSelectionView = RemoteViews(context.packageName, R.layout.item_autofill_select_entry)
val pendingIntent = AutofillLauncherActivity.getPendingIntentForSelection(context,
searchInfo, inlineSuggestionsRequest)
searchInfo, compatInlineSuggestionsRequest)
parseResult.allAutofillIds().let { autofillIds ->
autofillIds.forEach { id ->
val builder = Dataset.Builder(manualSelectionView)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
inlineSuggestionsRequest?.let {
compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
val inlinePresentationSpec = inlineSuggestionsRequest.inlinePresentationSpecs[0]
val inlinePresentation = buildInlinePresentationForManualSelection(context, inlinePresentationSpec, pendingIntent)
inlinePresentation?.let {
@@ -403,11 +414,11 @@ object AutofillHelper {
StructureParser(structure).parse()?.let { result ->
// New Response
val response = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val inlineSuggestionsRequest = activity.intent?.getParcelableExtra<InlineSuggestionsRequest?>(EXTRA_INLINE_SUGGESTIONS_REQUEST)
if (inlineSuggestionsRequest != null) {
val compatInlineSuggestionsRequest = activity.intent?.getParcelableExtra<CompatInlineSuggestionsRequest?>(EXTRA_INLINE_SUGGESTIONS_REQUEST)
if (compatInlineSuggestionsRequest != null) {
Toast.makeText(activity.applicationContext, R.string.autofill_inline_suggestions_keyboard, Toast.LENGTH_SHORT).show()
}
buildResponse(activity, database, entriesInfo, result, inlineSuggestionsRequest)
buildResponse(activity, database, entriesInfo, result, compatInlineSuggestionsRequest)
} else {
buildResponse(activity, database, entriesInfo, result, null)
}
@@ -427,37 +438,44 @@ object AutofillHelper {
}
}
fun buildActivityResultLauncher(activity: AppCompatActivity,
lockDatabase: Boolean = false): ActivityResultLauncher<Intent> {
return activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
// Utility method to loop and close each activity with return data
if (it.resultCode == Activity.RESULT_OK) {
activity.setResult(it.resultCode, it.data)
}
if (it.resultCode == Activity.RESULT_CANCELED) {
activity.setResult(Activity.RESULT_CANCELED)
}
activity.finish()
if (lockDatabase && PreferencesUtil.isAutofillCloseDatabaseEnable(activity)) {
// Close the database
activity.sendBroadcast(Intent(LOCK_ACTION))
}
}
}
/**
* Utility method to start an activity with an Autofill for result
*/
fun startActivityForAutofillResult(activity: Activity,
fun startActivityForAutofillResult(activity: AppCompatActivity,
intent: Intent,
activityResultLauncher: ActivityResultLauncher<Intent>?,
autofillComponent: AutofillComponent,
searchInfo: SearchInfo?) {
EntrySelectionHelper.addSpecialModeInIntent(intent, SpecialMode.SELECTION)
intent.putExtra(EXTRA_ASSIST_STRUCTURE, autofillComponent.assistStructure)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& PreferencesUtil.isAutofillInlineSuggestionsEnable(activity)) {
autofillComponent.inlineSuggestionsRequest?.let {
autofillComponent.compatInlineSuggestionsRequest?.let {
intent.putExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST, it)
}
}
EntrySelectionHelper.addSearchInfoInIntent(intent, searchInfo)
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
}
/**
* Utility method to loop and close each activity with return data
*/
fun onActivityResultSetResultAndFinish(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == AUTOFILL_RESPONSE_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
activity.setResult(resultCode, data)
}
if (resultCode == Activity.RESULT_CANCELED) {
activity.setResult(Activity.RESULT_CANCELED)
}
activity.finish()
}
activityResultLauncher?.launch(intent)
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.autofill
import android.annotation.TargetApi
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import android.service.autofill.FillRequest
import android.view.inputmethod.InlineSuggestionsRequest
import androidx.annotation.RequiresApi
/**
* Utility class only to prevent java.lang.NoClassDefFoundError for old Android version and new lib compilation
*/
@RequiresApi(Build.VERSION_CODES.O)
class CompatInlineSuggestionsRequest : Parcelable {
@TargetApi(Build.VERSION_CODES.R)
var inlineSuggestionsRequest: InlineSuggestionsRequest? = null
private set
constructor(fillRequest: FillRequest) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
this.inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest
} else {
this.inlineSuggestionsRequest = null
}
}
@RequiresApi(Build.VERSION_CODES.R)
constructor(inlineSuggestionsRequest: InlineSuggestionsRequest?) {
this.inlineSuggestionsRequest = inlineSuggestionsRequest
}
constructor(parcel: Parcel) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
this.inlineSuggestionsRequest =
parcel.readParcelable(FillRequest::class.java.classLoader)
}
else {
this.inlineSuggestionsRequest = null
}
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
parcel.writeParcelable(inlineSuggestionsRequest, flags)
}
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CompatInlineSuggestionsRequest> {
override fun createFromParcel(parcel: Parcel): CompatInlineSuggestionsRequest {
return CompatInlineSuggestionsRequest(parcel)
}
override fun newArray(size: Int): Array<CompatInlineSuggestionsRequest?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -109,7 +109,7 @@ class KeeAutofillService : AutofillService() {
searchInfo.webDomain = webDomainWithoutSubDomain
val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& autofillInlineSuggestionsEnabled) {
request.inlineSuggestionsRequest
CompatInlineSuggestionsRequest(request)
} else {
null
}
@@ -127,7 +127,7 @@ class KeeAutofillService : AutofillService() {
private fun launchSelection(database: Database?,
searchInfo: SearchInfo,
parseResult: StructureParser.Result,
inlineSuggestionsRequest: InlineSuggestionsRequest?,
inlineSuggestionsRequest: CompatInlineSuggestionsRequest?,
callback: FillCallback) {
SearchHelper.checkAutoSearchInfo(this,
database,
@@ -155,7 +155,7 @@ class KeeAutofillService : AutofillService() {
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
database: Database?,
searchInfo: SearchInfo,
inlineSuggestionsRequest: InlineSuggestionsRequest?,
inlineSuggestionsRequest: CompatInlineSuggestionsRequest?,
callback: FillCallback) {
parseResult.allAutofillIds().let { autofillIds ->
if (autofillIds.isNotEmpty()) {
@@ -249,7 +249,7 @@ class KeeAutofillService : AutofillService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& autofillInlineSuggestionsEnabled) {
var inlinePresentation: InlinePresentation? = null
inlineSuggestionsRequest?.let {
inlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
val inlinePresentationSpecs = inlineSuggestionsRequest.inlinePresentationSpecs
if (inlineSuggestionsRequest.maxSuggestionCount > 0
&& inlinePresentationSpecs.size > 0) {
@@ -262,9 +262,13 @@ class KeeAutofillService : AutofillService() {
inlinePresentation = InlinePresentation(
InlineSuggestionUi.newContentBuilder(
PendingIntent.getActivity(this,
0,
Intent(this, AutofillSettingsActivity::class.java),
0)
0,
Intent(this, AutofillSettingsActivity::class.java),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
})
).apply {
setContentDescription(getString(R.string.autofill_sign_in_prompt))
setTitle(getString(R.string.autofill_sign_in_prompt))
@@ -277,8 +281,9 @@ class KeeAutofillService : AutofillService() {
}
// Build response
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation)
} else {
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
}
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
callback.onSuccess(responseBuilder.build())
}
}

View File

@@ -272,12 +272,12 @@ class StructureParser(private val structure: AssistStructure) {
private fun parseNodeByHtmlAttributes(node: AssistStructure.ViewNode): Boolean {
val autofillId = node.autofillId
val nodHtml = node.htmlInfo
when (nodHtml?.tag?.toLowerCase(Locale.ENGLISH)) {
when (nodHtml?.tag?.lowercase(Locale.ENGLISH)) {
"input" -> {
nodHtml.attributes?.forEach { pairAttribute ->
when (pairAttribute.first.toLowerCase(Locale.ENGLISH)) {
when (pairAttribute.first.lowercase(Locale.ENGLISH)) {
"type" -> {
when (pairAttribute.second.toLowerCase(Locale.ENGLISH)) {
when (pairAttribute.second.lowercase(Locale.ENGLISH)) {
"tel", "email" -> {
result?.usernameId = autofillId
result?.usernameValue = node.autofillValue

View File

@@ -19,6 +19,7 @@
*/
package com.kunzisoft.keepass.biometric
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
@@ -27,9 +28,11 @@ import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.getkeepsafe.taptargetview.TapTargetView
import com.kunzisoft.keepass.R
@@ -39,6 +42,7 @@ import com.kunzisoft.keepass.database.exception.IODatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -59,9 +63,12 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
/**
* Manage setting to auto open biometric prompt
*/
private var mAutoOpenPrompt: Boolean = false
private var mAutoOpenPrompt: Boolean
get() {
return field && mAutoOpenPromptEnabled
return mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt && mAutoOpenPromptEnabled
}
set(value) {
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = value
}
// Variable to check if the prompt can be open (if the right activity is currently shown)
@@ -72,6 +79,8 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
private var cipherDatabaseListener: CipherDatabaseAction.CipherDatabaseListener? = null
private val mAdvancedUnlockViewModel: AdvancedUnlockViewModel by activityViewModels()
// Only to fix multiple fingerprint menu #332
private var mAllowAdvancedUnlockMenu = false
private var mAddBiometricMenuInProgress = false
@@ -79,6 +88,15 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
// Only keep connection when we request a device credential activity
private var keepConnection = false
private var mDeviceCredentialResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false
// To wait resume
if (keepConnection) {
mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded = result.resultCode == Activity.RESULT_OK
}
keepConnection = false
}
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -97,10 +115,21 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
setHasOptionsMenu(true)
cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext)
mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) {
initAdvancedUnlockMode()
}
mAdvancedUnlockViewModel.onUnlockAvailabilityCheckRequested.observe(this) {
checkUnlockAvailability()
}
mAdvancedUnlockViewModel.onDatabaseFileLoaded.observe(this) {
onDatabaseLoaded(it)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@@ -114,17 +143,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
return rootView
}
private data class ActivityResult(var requestCode: Int, var resultCode: Int, var data: Intent?)
private var activityResult: ActivityResult? = null
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// To wait resume
if (keepConnection) {
activityResult = ActivityResult(requestCode, resultCode, data)
}
keepConnection = false
}
override fun onResume() {
super.onResume()
context?.let {
@@ -154,32 +172,38 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
return super.onOptionsItemSelected(item)
}
fun loadDatabase(databaseUri: Uri?, autoOpenPrompt: Boolean) {
private fun onDatabaseLoaded(databaseUri: Uri?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// To get device credential unlock result, only if same database uri
if (databaseUri != null
&& mAdvancedUnlockEnabled) {
activityResult?.let {
val deviceCredentialAuthSucceeded = mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded
deviceCredentialAuthSucceeded?.let {
if (databaseUri == databaseFileUri) {
advancedUnlockManager?.onActivityResult(it.requestCode, it.resultCode)
if (deviceCredentialAuthSucceeded == true) {
advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationSucceeded()
} else {
advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationFailed()
}
} else {
disconnect()
}
} ?: run {
this.mAutoOpenPrompt = autoOpenPrompt
connect(databaseUri)
if (databaseUri != databaseFileUri) {
connect(databaseUri)
}
}
} else {
disconnect()
}
activityResult = null
mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded = null
}
}
/**
* Check unlock availability and change the current mode depending of device's state
*/
fun checkUnlockAvailability() {
private fun checkUnlockAvailability() {
context?.let { context ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
allowOpenBiometricPrompt = true
@@ -317,7 +341,8 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
if (cryptoPrompt.isDeviceCredentialOperation)
keepConnection = true
try {
advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt)
advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt,
mDeviceCredentialResultLauncher)
} catch (e: Exception) {
Log.e(TAG, "Unable to open advanced unlock prompt", e)
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
@@ -369,8 +394,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
} ?: throw Exception("AdvancedUnlockManager not initialized")
}
@Synchronized
fun initAdvancedUnlockMode() {
private fun initAdvancedUnlockMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mAllowAdvancedUnlockMenu = false
try {
@@ -444,6 +468,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M)
fun deleteEncryptedDatabaseKey() {
mAllowAdvancedUnlockMenu = false
advancedUnlockManager?.closeBiometricPrompt()
databaseFileUri?.let { databaseUri ->
cipherDatabaseAction.deleteByDatabaseUri(databaseUri) {
@@ -516,6 +541,11 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onUnrecoverableKeyException(e: Exception) {
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onInvalidKeyException(e: Exception) {
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)

View File

@@ -19,15 +19,18 @@
*/
package com.kunzisoft.keepass.biometric
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.*
@@ -35,6 +38,7 @@ import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.security.KeyStore
import java.security.UnrecoverableKeyException
@@ -136,18 +140,24 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
// and the constrains (purposes) in the constructor of the Builder
keyGenerator?.init(
KeyGenParameterSpec.Builder(
ADVANCED_UNLOCK_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
ADVANCED_UNLOCK_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.apply {
// Require the user to authenticate with a fingerprint to authorize every use
// of the key, don't use it for device credential because it's the user authentication
.apply {
if (biometricUnlockEnable) {
setUserAuthenticationRequired(true)
}
if (biometricUnlockEnable) {
setUserAuthenticationRequired(true)
}
.build())
// To store in the security chip
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& retrieveContext().packageManager.hasSystemFeature(
PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
setIsStrongBoxBacked(true)
}
}
.build())
keyGenerator?.generateKey()
}
} catch (e: Exception) {
@@ -164,8 +174,12 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
return null
}
fun initEncryptData(actionIfCypherInit
: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,) {
initEncryptData(actionIfCypherInit, true)
}
private fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
firstLaunch: Boolean) {
if (!isKeyManagerInitialized) {
return
}
@@ -185,10 +199,15 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
advancedUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
advancedUnlockCallback?.onUnrecoverableKeyException(unrecoverableKeyException)
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
if (firstLaunch) {
deleteAllEntryKeysInKeystoreForBiometric(retrieveContext())
initEncryptData(actionIfCypherInit, false)
} else {
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize encrypt data", e)
advancedUnlockCallback?.onGenericException(e)
@@ -214,8 +233,14 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
fun initDecryptData(ivSpecValue: String,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
initDecryptData(ivSpecValue, actionIfCypherInit, true)
}
private fun initDecryptData(ivSpecValue: String,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
firstLaunch: Boolean = true) {
if (!isKeyManagerInitialized) {
return
}
@@ -239,10 +264,20 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
deleteKeystoreKey()
if (firstLaunch) {
deleteKeystoreKey()
initDecryptData(ivSpecValue, actionIfCypherInit, firstLaunch)
} else {
advancedUnlockCallback?.onUnrecoverableKeyException(unrecoverableKeyException)
}
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
if (firstLaunch) {
deleteAllEntryKeysInKeystoreForBiometric(retrieveContext())
initDecryptData(ivSpecValue, actionIfCypherInit, firstLaunch)
} else {
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize decrypt data", e)
advancedUnlockCallback?.onGenericException(e)
@@ -278,9 +313,9 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
@Suppress("DEPRECATION")
@Synchronized
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt,
deviceCredentialResultLauncher: ActivityResultLauncher<Intent>
) {
// Init advanced unlock prompt
if (biometricPrompt == null) {
biometricPrompt = BiometricPrompt(retrieveContext(),
@@ -311,20 +346,10 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
else if (cryptoPrompt.isDeviceCredentialOperation) {
val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java)
retrieveContext().startActivityForResult(
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription),
REQUEST_DEVICE_CREDENTIAL)
}
}
@Synchronized
fun onActivityResult(requestCode: Int, resultCode: Int) {
if (requestCode == REQUEST_DEVICE_CREDENTIAL) {
if (resultCode == Activity.RESULT_OK) {
advancedUnlockCallback?.onAuthenticationSucceeded()
} else {
advancedUnlockCallback?.onAuthenticationFailed()
}
@Suppress("DEPRECATION")
deviceCredentialResultLauncher.launch(
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription)
)
}
}
@@ -333,6 +358,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
interface AdvancedUnlockErrorCallback {
fun onUnrecoverableKeyException(e: Exception)
fun onInvalidKeyException(e: Exception)
fun onGenericException(e: Exception)
}
@@ -355,8 +381,6 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
private const val ADVANCED_UNLOCK_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
private const val ADVANCED_UNLOCK_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
private const val REQUEST_DEVICE_CREDENTIAL = 556
@RequiresApi(api = Build.VERSION_CODES.M)
fun canAuthenticate(context: Context): Int {
return try {
@@ -449,6 +473,10 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
override fun handleDecryptedResult(decryptedValue: String) {}
override fun onUnrecoverableKeyException(e: Exception) {
advancedCallback.onUnrecoverableKeyException(e)
}
override fun onInvalidKeyException(e: Exception) {
advancedCallback.onInvalidKeyException(e)
}
@@ -460,6 +488,33 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
deleteKeystoreKey()
}
}
fun deleteAllEntryKeysInKeystoreForBiometric(activity: FragmentActivity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deleteEntryKeyInKeystoreForBiometric(
activity,
object : AdvancedUnlockErrorCallback {
fun showException(e: Exception) {
Toast.makeText(activity,
activity.getString(R.string.advanced_unlock_scanning_error, e.localizedMessage),
Toast.LENGTH_SHORT).show()
}
override fun onUnrecoverableKeyException(e: Exception) {
showException(e)
}
override fun onInvalidKeyException(e: Exception) {
showException(e)
}
override fun onGenericException(e: Exception) {
showException(e)
}
})
}
CipherDatabaseAction.getInstance(activity.applicationContext).deleteAll()
}
}
}

View File

@@ -192,17 +192,17 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
private val MIN_VERSION = UnsignedInt(0x10)
private val MAX_VERSION = UnsignedInt(0x13)
private val DEFAULT_ITERATIONS = UnsignedLong(2L)
private val DEFAULT_ITERATIONS = UnsignedLong(3L)
private val MIN_ITERATIONS = UnsignedLong(1L)
private val MAX_ITERATIONS = UnsignedLong(4294967295L)
private val DEFAULT_MEMORY = UnsignedLong((1024L * 1024L))
private val DEFAULT_MEMORY = UnsignedLong((1024L * 1024L * 16L))
private val MIN_MEMORY = UnsignedLong(1024L * 8L)
private val MAX_MEMORY = UnsignedInt.MAX_VALUE.toKotlinLong()
private const val MEMORY_BLOCK_SIZE: Long = 1024L
private val DEFAULT_PARALLELISM = UnsignedInt(2)
private val DEFAULT_PARALLELISM = UnsignedInt(4)
private val MIN_PARALLELISM = UnsignedInt.fromKotlinLong(1L)
private val MAX_PARALLELISM = UnsignedInt.fromKotlinLong(((1 shl 24) - 1))
private val MAX_PARALLELISM = UnsignedInt.fromKotlinLong(((1 shl 24) - 1).toLong())
}
}

View File

@@ -68,7 +68,7 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
pwEntry.notes = getString(getColumnIndex(COLUMN_INDEX_NOTES))
pwEntry.expiryTime = DateInstant(getString(getColumnIndex(COLUMN_INDEX_EXPIRY_TIME)))
pwEntry.expires = getString(getColumnIndex(COLUMN_INDEX_EXPIRES))
.toLowerCase(Locale.ENGLISH) != "false"
.lowercase(Locale.ENGLISH) != "false"
}
companion object {

View File

@@ -147,6 +147,10 @@ class Database {
iconsManager.removeCustomIcon(binaryCache, customIcon.uuid)
}
fun updateCustomIcon(customIcon: IconImageCustom) {
iconsManager.getIcon(customIcon.uuid).updateWith(customIcon)
}
fun getTemplates(templateCreation: Boolean): List<Template> {
return mDatabaseKDBX?.getTemplates(templateCreation) ?: listOf()
}

View File

@@ -31,6 +31,10 @@ class CustomIconPool(private val binaryCache: BinaryCache) : BinaryPool<UUID>(bi
return newUUID
}
fun getCustomIcon(key: UUID): IconImageCustom? {
return customIcons[key]
}
fun any(predicate: (IconImageCustom)-> Boolean): Boolean {
return customIcons.any { predicate(it.value) }
}

View File

@@ -41,7 +41,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
override val version: String
get() = "KeePass 1"
get() = "V1"
init {
kdfListV3.add(KdfFactory.aesKdf)

View File

@@ -156,7 +156,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
FILE_VERSION_41 -> "4.1"
else -> "UNKNOWN"
}
return "KeePass 2 - KDBX$kdbxStringVersion"
return "V2 - KDBX$kdbxStringVersion"
}
override val kdfEngine: KdfEngine?

View File

@@ -124,25 +124,29 @@ abstract class DatabaseVersioned<
@Throws(IOException::class)
protected fun getFileKey(keyInputStream: InputStream): ByteArray {
val keyData = keyInputStream.readBytes()
try {
val keyData = keyInputStream.readBytes()
// Check XML key file
val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
if (xmlKeyByteArray != null) {
return xmlKeyByteArray
}
// Check 32 bytes key file
when (keyData.size) {
32 -> return keyData
64 -> try {
return Hex.decodeHex(String(keyData).toCharArray())
} catch (ignoredException: Exception) {
// Key is not base 64, treat it as binary data
// Check XML key file
val xmlKeyByteArray = loadXmlKeyFile(ByteArrayInputStream(keyData))
if (xmlKeyByteArray != null) {
return xmlKeyByteArray
}
// Check 32 bytes key file
when (keyData.size) {
32 -> return keyData
64 -> try {
return Hex.decodeHex(String(keyData).toCharArray())
} catch (ignoredException: Exception) {
// Key is not base 64, treat it as binary data
}
}
// Hash file as binary data
return HashManager.hashSha256(keyData)
} catch (outOfMemoryError: OutOfMemoryError) {
throw IOException("Keyfile data is too large", outOfMemoryError)
}
// Hash file as binary data
return HashManager.hashSha256(keyData)
}
protected open fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {

View File

@@ -32,6 +32,16 @@ class IconImageCustom : IconImageDraw {
var name: String = ""
var lastModificationTime: DateInstant? = null
fun updateWith(icon: IconImageCustom) {
this.name = icon.name
this.lastModificationTime = icon.lastModificationTime
}
constructor(copy: IconImageCustom) {
this.uuid = copy.uuid
updateWith(copy)
}
constructor(name: String = "", lastModificationTime: DateInstant? = null) {
this.uuid = DatabaseVersioned.UUID_ZERO
this.name = name

View File

@@ -65,7 +65,7 @@ class IconsManager(binaryCache: BinaryCache) {
}
fun getIcon(iconUuid: UUID): IconImageCustom {
return IconImageCustom(iconUuid)
return customCache.getCustomIcon(iconUuid) ?: IconImageCustom(iconUuid)
}
fun isCustomIconBinaryDuplicate(binaryData: BinaryData): Boolean {

View File

@@ -208,16 +208,8 @@ class TemplateEngineCompatible(database: DatabaseKDBX): TemplateEngine(database)
when (attribute.type) {
TemplateAttributeType.TEXT -> {
try {
when (attribute.options.getNumberLines()) {
1 -> {
// If one line, default attribute option is number of chars
attribute.options.setNumberChars(defaultOption.toInt())
}
else -> {
// else it's number of lines
attribute.options.setNumberLines(defaultOption.toInt())
}
}
// It's always a number of lines...
attribute.options.setNumberLines(defaultOption.toInt())
} catch (e: Exception) {
Log.e(TAG, "Unable to transform default text option", e)
}

View File

@@ -27,7 +27,7 @@ object TemplateField {
const val LABEL_DATE_OF_ISSUE = "Date of issue"
const val LABEL_EMAIL = "Email"
const val LABEL_EMAIL_ADDRESS = "Email address"
const val LABEL_WIRELESS = "Wifi"
const val LABEL_WIRELESS = "Wi-Fi"
const val LABEL_SSID = "SSID"
const val LABEL_TYPE = "Type"
const val LABEL_CRYPTOCURRENCY = "Cryptocurrency wallet"

View File

@@ -130,6 +130,6 @@ constructor(private val databaseKDBX: DatabaseKDBX,
}
companion object {
private val EndHeaderValue = byteArrayOf('\r'.toByte(), '\n'.toByte(), '\r'.toByte(), '\n'.toByte())
private val EndHeaderValue = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte(), '\r'.code.toByte(), '\n'.code.toByte())
}
}

View File

@@ -765,7 +765,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
var character: Char
for (element in text) {
character = element
val hexChar = character.toInt()
val hexChar = character.code
if (
hexChar in 0x20..0xD7FF ||
hexChar == 0x9 ||

View File

@@ -119,7 +119,7 @@ class GroupActivityEducation(activity: Activity)
.outerCircleColorInt(getCircleColor())
.outerCircleAlpha(getCircleAlpha())
.textColorInt(getTextColor())
.tintTarget(true)
.tintTarget(false)
.cancelable(true),
object : TapTargetView.Listener() {
override fun onTargetClick(view: TapTargetView) {

View File

@@ -234,7 +234,7 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
fun replaceBase32Chars(parameter: String): String {
// Add padding '=' at end if not Base32 length
var parameterNewSize = parameter.toUpperCase(Locale.ENGLISH).removeSpaceChars()
var parameterNewSize = parameter.uppercase(Locale.ENGLISH).removeSpaceChars()
while (parameterNewSize.length % 8 != 0) {
parameterNewSize += '='
}
@@ -264,7 +264,7 @@ enum class OtpTokenType {
companion object {
fun getFromString(tokenType: String): OtpTokenType {
return when (tokenType.toLowerCase(Locale.ENGLISH)) {
return when (tokenType.lowercase(Locale.ENGLISH)) {
"s", "steam" -> STEAM
"hotp" -> RFC4226
else -> RFC6238

View File

@@ -143,7 +143,7 @@ object OtpEntryFields {
if (otpPlainText != null && otpPlainText.isNotEmpty() && isOTPUri(otpPlainText)) {
val uri = Uri.parse(otpPlainText.removeSpaceChars())
if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase(Locale.ENGLISH)) {
if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.lowercase(Locale.ENGLISH)) {
Log.e(TAG, "Invalid or missing scheme in uri")
return false
}
@@ -309,7 +309,7 @@ object OtpEntryFields {
}
if (algorithmField != null) {
otpElement.algorithm =
when (algorithmField.toUpperCase(Locale.ENGLISH)) {
when (algorithmField.uppercase(Locale.ENGLISH)) {
TIMEOTP_ALGORITHM_SHA1_VALUE -> HashAlgorithm.SHA1
TIMEOTP_ALGORITHM_SHA256_VALUE -> HashAlgorithm.SHA256
TIMEOTP_ALGORITHM_SHA512_VALUE -> HashAlgorithm.SHA512
@@ -417,7 +417,7 @@ object OtpEntryFields {
val output = HashMap<String, String>()
for (element in elements) {
val pair = element.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
output[pair[0].toLowerCase(Locale.ENGLISH)] = pair[1]
output[pair[0].lowercase(Locale.ENGLISH)] = pair[1]
}
return output
}

View File

@@ -1,9 +1,11 @@
package com.kunzisoft.keepass.services
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.*
import android.net.Uri
import android.os.Binder
import android.os.Build
import android.os.IBinder
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
@@ -50,11 +52,20 @@ class AdvancedUnlockNotificationService : NotificationService() {
mTempCipherDao = ArrayList()
}
// It's simpler to use pendingIntent to perform REMOVE_ADVANCED_UNLOCK_KEY_ACTION
// because can be directly broadcast to another module or app
@SuppressLint("LaunchActivityFromNotification")
override fun onBind(intent: Intent): IBinder {
super.onBind(intent)
val pendingDeleteIntent = PendingIntent.getBroadcast(this,
4577, Intent(REMOVE_ADVANCED_UNLOCK_KEY_ACTION), 0)
4577,
Intent(REMOVE_ADVANCED_UNLOCK_KEY_ACTION),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
})
val biometricUnlockEnabled = PreferencesUtil.isBiometricUnlockEnable(this)
val notificationBuilder = buildNewNotification().apply {
setSmallIcon(if (biometricUnlockEnabled) {

View File

@@ -24,6 +24,7 @@ import android.content.ContentResolver
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.util.Log
import com.kunzisoft.keepass.R
@@ -188,20 +189,30 @@ class AttachmentFileNotificationService: LockNotificationService() {
private fun newNotification(attachmentNotification: AttachmentNotification) {
val pendingContentIntent = PendingIntent.getActivity(this,
0,
Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(attachmentNotification.uri,
contentResolver.getType(attachmentNotification.uri))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}, PendingIntent.FLAG_CANCEL_CURRENT)
0,
Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(attachmentNotification.uri,
contentResolver.getType(attachmentNotification.uri))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
} else {
PendingIntent.FLAG_CANCEL_CURRENT
}
)
val pendingDeleteIntent = PendingIntent.getService(this,
0,
Intent(this, AttachmentFileNotificationService::class.java).apply {
// No action to delete the service
putExtra(FILE_URI_KEY, attachmentNotification.uri)
}, PendingIntent.FLAG_CANCEL_CURRENT)
}, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
} else {
PendingIntent.FLAG_CANCEL_CURRENT
}
)
val fileName = UriUtil.getFileData(this, attachmentNotification.uri)?.name
?: attachmentNotification.uri.path

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.services
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.EntryInfo
@@ -112,7 +113,13 @@ class ClipboardEntryNotificationService : LockNotificationService() {
putParcelableArrayListExtra(EXTRA_CLIPBOARD_FIELDS, fieldsToAdd)
}
return PendingIntent.getService(
this, 0, copyIntent, PendingIntent.FLAG_UPDATE_CURRENT)
this, 0, copyIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
)
}
private fun newNotification(title: String?, fieldsToAdd: ArrayList<ClipboardEntryNotificationField>) {
@@ -162,7 +169,13 @@ class ClipboardEntryNotificationService : LockNotificationService() {
val cleanIntent = Intent(this, ClipboardEntryNotificationService::class.java)
cleanIntent.action = ACTION_CLEAN_CLIPBOARD
val cleanPendingIntent = PendingIntent.getService(
this, 0, cleanIntent, PendingIntent.FLAG_UPDATE_CURRENT)
this, 0, cleanIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
)
builder.setDeleteIntent(cleanPendingIntent)
//Get settings

View File

@@ -24,6 +24,7 @@ import android.content.Intent
import android.net.Uri
import android.os.*
import android.util.Log
import androidx.media.app.NotificationCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
@@ -407,11 +408,21 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
this,
0,
Intent(this, GroupActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
)
val pendingDeleteIntent = PendingIntent.getBroadcast(
this,
4576, Intent(LOCK_ACTION), 0
4576,
Intent(LOCK_ACTION),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
)
// Add actions in notifications
notificationBuilder.apply {
@@ -420,9 +431,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
// Unfortunately swipe is disabled in lollipop+
setDeleteIntent(pendingDeleteIntent)
addAction(
R.drawable.ic_lock_white_24dp, getString(R.string.lock),
R.drawable.ic_lock_database_white_32dp, getString(R.string.lock),
pendingDeleteIntent
)
// Won't work with Xiaomi and Kitkat
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
setStyle(
NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0)
)
}
}
}
}

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.services
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.preference.PreferenceManager
@@ -93,7 +94,13 @@ class KeyboardEntryNotificationService : LockNotificationService() {
val deleteIntent = Intent(this, KeyboardEntryNotificationService::class.java).apply {
action = ACTION_CLEAN_KEYBOARD_ENTRY
}
pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
)
val builder = buildNewNotification()
.setSmallIcon(R.drawable.notification_ic_keyboard_key_24dp)

View File

@@ -57,6 +57,7 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() {
}
if (dialogFragment != null) {
@Suppress("DEPRECATION")
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_AUTOFILL_PREF_FRAGMENT)
}

View File

@@ -48,6 +48,7 @@ class MagikeyboardSettingsFragment : PreferenceFragmentCompat() {
}
if (dialogFragment != null) {
@Suppress("DEPRECATION")
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}

View File

@@ -40,7 +40,6 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.ProFeatureDialogFragment
import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.education.Education
@@ -157,7 +156,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
intent.data = Uri.parse("package:com.kunzisoft.keepass.autofill.KeeAutofillService")
Log.d(javaClass.name, "Autofill enable service: intent=$intent")
startActivityForResult(intent, REQUEST_CODE_AUTOFILL)
startActivity(intent)
} else {
Log.d(javaClass.name, "Autofill service already enabled.")
}
@@ -366,26 +365,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
) { _, _ ->
validate?.invoke()
deleteKeysAlertDialog?.setOnDismissListener(null)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AdvancedUnlockManager.deleteEntryKeyInKeystoreForBiometric(
activity,
object : AdvancedUnlockManager.AdvancedUnlockErrorCallback {
fun showException(e: Exception) {
Toast.makeText(context,
getString(R.string.advanced_unlock_scanning_error, e.localizedMessage),
Toast.LENGTH_SHORT).show()
}
override fun onInvalidKeyException(e: Exception) {
showException(e)
}
override fun onGenericException(e: Exception) {
showException(e)
}
})
}
CipherDatabaseAction.getInstance(activity.applicationContext).deleteAll()
AdvancedUnlockManager.deleteAllEntryKeysInKeystoreForBiometric(activity)
}
.setNegativeButton(resources.getString(android.R.string.cancel)
) { _, _ ->}
@@ -494,6 +474,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
if (dialogFragment != null) {
@Suppress("DEPRECATION")
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}
@@ -533,7 +514,6 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
companion object {
private const val REQUEST_CODE_AUTOFILL = 5201
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
var DATABASE_APPEARANCE_PREFERENCE_CHANGED = false

View File

@@ -57,7 +57,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
private var dbNamePref: InputTextPreference? = null
private var dbDescriptionPref: InputTextPreference? = null
private var dbDefaultUsername: InputTextPreference? = null
private var dbDefaultUsernamePref: InputTextPreference? = null
private var dbCustomColorPref: DialogColorPreference? = null
private var dbDataCompressionPref: Preference? = null
private var recycleBinGroupPref: DialogListExplanationPreference? = null
@@ -164,11 +164,11 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
}
// Database default username
dbDefaultUsername = findPreference(getString(R.string.database_default_username_key))
dbDefaultUsernamePref = findPreference(getString(R.string.database_default_username_key))
if (database.allowDefaultUsername) {
dbDefaultUsername?.summary = database.defaultUsername
dbDefaultUsernamePref?.summary = database.defaultUsername
} else {
dbDefaultUsername?.isEnabled = false
dbDefaultUsernamePref?.isEnabled = false
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
}
@@ -416,7 +416,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
mDatabase?.defaultUsername = oldDefaultUsername
oldDefaultUsername
}
dbDefaultUsername?.summary = defaultUsernameToShow
dbDefaultUsernamePref?.summary = defaultUsernameToShow
}
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_COLOR_TASK -> {
val oldColor = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!!
@@ -632,6 +632,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
}
if (dialogFragment != null && !mDatabaseReadOnly) {
@Suppress("DEPRECATION")
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}

View File

@@ -49,7 +49,6 @@ open class SettingsActivity
private var backupManager: BackupManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var appPropertiesFileCreationRequestCode: Int? = null
private var coordinatorLayout: CoordinatorLayout? = null
private var toolbar: Toolbar? = null
@@ -64,6 +63,41 @@ open class SettingsActivity
toolbar = findViewById(R.id.toolbar)
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { selectedFileUri ->
// Import app properties result
try {
selectedFileUri?.let { uri ->
val appProperties = Properties()
contentResolver?.openInputStream(uri)?.use { inputStream ->
appProperties.load(inputStream)
}
PreferencesUtil.setAppProperties(this, appProperties)
// Restart the current activity
reloadActivity()
Toast.makeText(this, R.string.success_import_app_properties, Toast.LENGTH_LONG).show()
}
} catch (e: Exception) {
Toast.makeText(this, R.string.error_import_app_properties, Toast.LENGTH_LONG).show()
Log.e(TAG, "Unable to import app properties", e)
}
}
mExternalFileHelper?.buildCreateDocument { createdFileUri ->
// Export app properties result
try {
createdFileUri?.let { uri ->
contentResolver?.openOutputStream(uri)?.use { outputStream ->
PreferencesUtil
.getAppProperties(this)
.store(outputStream, getString(R.string.description_app_properties))
}
Toast.makeText(this, R.string.success_export_app_properties, Toast.LENGTH_LONG).show()
}
} catch (e: Exception) {
Toast.makeText(this, R.string.error_export_app_properties, Toast.LENGTH_LONG).show()
Log.e(DatabaseLockActivity.TAG, "Unable to export app properties", e)
}
}
if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty())
toolbar?.setTitle(R.string.settings)
@@ -217,54 +251,10 @@ open class SettingsActivity
}
fun exportAppProperties() {
appPropertiesFileCreationRequestCode = mExternalFileHelper?.createDocument(getString(R.string.app_properties_file_name,
mExternalFileHelper?.createDocument(getString(R.string.app_properties_file_name,
DateTime.now().toLocalDateTime().toString("yyyy-MM-dd'_'HH-mm")))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Import app properties result
try {
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { selectedFileUri ->
selectedFileUri?.let { uri ->
val appProperties = Properties()
contentResolver?.openInputStream(uri)?.use { inputStream ->
appProperties.load(inputStream)
}
PreferencesUtil.setAppProperties(this, appProperties)
// Restart the current activity
reloadActivity()
Toast.makeText(this, R.string.success_import_app_properties, Toast.LENGTH_LONG).show()
}
}
} catch (e: Exception) {
Toast.makeText(this, R.string.error_import_app_properties, Toast.LENGTH_LONG).show()
Log.e(TAG, "Unable to import app properties", e)
}
// Export app properties result
try {
if (requestCode == appPropertiesFileCreationRequestCode) {
mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
createdFileUri?.let { uri ->
contentResolver?.openOutputStream(uri)?.use { outputStream ->
PreferencesUtil
.getAppProperties(this)
.store(outputStream, getString(R.string.description_app_properties))
}
Toast.makeText(this, R.string.success_export_app_properties, Toast.LENGTH_LONG).show()
}
}
appPropertiesFileCreationRequestCode = null
}
} catch (e: Exception) {
Toast.makeText(this, R.string.error_export_app_properties, Toast.LENGTH_LONG).show()
Log.e(DatabaseLockActivity.TAG, "Unable to export app properties", e)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)

View File

@@ -31,9 +31,13 @@ abstract class ActionRunnable: Runnable {
var result: Result = Result()
override fun run() {
onStartRun()
onActionRun()
onFinishRun()
try {
onStartRun()
onActionRun()
onFinishRun()
} catch (runException: Exception) {
setError(runException)
}
}
abstract fun onStartRun()

View File

@@ -43,9 +43,14 @@ object TimeoutHelper {
private fun getLockPendingIntent(context: Context): PendingIntent {
return PendingIntent.getBroadcast(context.applicationContext,
REQUEST_ID,
Intent(LOCK_ACTION),
PendingIntent.FLAG_CANCEL_CURRENT)
REQUEST_ID,
Intent(LOCK_ACTION),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
} else {
PendingIntent.FLAG_CANCEL_CURRENT
}
)
}
/**

View File

@@ -60,11 +60,16 @@ class LockReceiver(var lockAction: () -> Unit) : BroadcastReceiver() {
Intent.ACTION_SCREEN_OFF -> {
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(context)) {
mLockPendingIntent = PendingIntent.getBroadcast(context,
4575,
Intent(intent).apply {
action = LOCK_ACTION
},
0)
4575,
Intent(intent).apply {
action = LOCK_ACTION
},
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
)
// Launch the effective action after a small time
val first: Long = System.currentTimeMillis() + context.getString(R.string.timeout_screen_off).toLong()
val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager?

View File

@@ -86,7 +86,7 @@ object UriUtil {
private fun isFileScheme(fileUri: Uri): Boolean {
val scheme = fileUri.scheme
if (scheme == null || scheme.isEmpty() || scheme.toLowerCase(Locale.ENGLISH) == "file") {
if (scheme == null || scheme.isEmpty() || scheme.lowercase(Locale.ENGLISH) == "file") {
return true
}
return false
@@ -94,7 +94,7 @@ object UriUtil {
private fun isContentScheme(fileUri: Uri): Boolean {
val scheme = fileUri.scheme
if (scheme != null && scheme.toLowerCase(Locale.ENGLISH) == "content") {
if (scheme != null && scheme.lowercase(Locale.ENGLISH) == "content") {
return true
}
return false

View File

@@ -104,10 +104,12 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
return unlockMessageTextView?.text?.toString() ?: ""
}
set(value) {
if (value == null || value.isEmpty())
if (value == null || value.isEmpty()) {
unlockMessageTextView?.visibility = GONE
else
} else {
unlockMessageTextView?.visibility = VISIBLE
stopIconViewAnimation()
}
unlockMessageTextView?.text = value ?: ""
}

View File

@@ -2,6 +2,7 @@ package com.kunzisoft.keepass.view
import android.content.Context
import android.net.Uri
import android.os.Parcelable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.TextView
@@ -9,6 +10,9 @@ import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
import android.os.Parcel
import android.os.Parcelable.Creator
class KeyFileSelectionView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
@@ -54,4 +58,45 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
UriUtil.getFileData(context, value)?.name ?: value.path
} ?: ""
}
override fun onSaveInstanceState(): Parcelable {
val superState = super.onSaveInstanceState()
val saveState = SavedState(superState)
saveState.mUri = this.mUri
return saveState
}
override fun onRestoreInstanceState(state: Parcelable?) {
if (state !is SavedState) {
super.onRestoreInstanceState(state)
return
}
super.onRestoreInstanceState(state.superState)
this.mUri = state.mUri
}
internal class SavedState : BaseSavedState {
var mUri: Uri? = null
constructor(superState: Parcelable?) : super(superState) {}
private constructor(parcel: Parcel) : super(parcel) {
mUri = parcel.readParcelable(Uri::class.java.classLoader)
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeParcelable(mUri, flags)
}
companion object CREATOR : Creator<SavedState> {
override fun createFromParcel(parcel: Parcel): SavedState {
return SavedState(parcel)
}
override fun newArray(size: Int): Array<SavedState?> {
return arrayOfNulls(size)
}
}
}
}

View File

@@ -86,7 +86,8 @@ abstract class TemplateAbstractView<
if (mTemplate != template) {
mTemplate = template
if (mEntryInfo != null) {
populateEntryInfoWithViews(true)
populateEntryInfoWithViews(templateFieldNotEmpty = true,
retrieveDefaultValues = false)
}
buildTemplateAndPopulateInfo()
clearFocus()
@@ -203,9 +204,7 @@ abstract class TemplateAbstractView<
setNumberLines(20)
},
TemplateAttributeAction.CUSTOM_EDITION
).apply {
default = field.protectedValue.stringValue
}
)
return buildViewForTemplateField(customFieldTemplateAttribute, field, FIELD_CUSTOM_TAG)
}
@@ -390,7 +389,8 @@ abstract class TemplateAbstractView<
return emptyList()
}
protected open fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean) {
protected open fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean,
retrieveDefaultValues: Boolean) {
if (mEntryInfo == null)
mEntryInfo = EntryInfo()
@@ -429,11 +429,12 @@ abstract class TemplateAbstractView<
mEntryInfo?.notes = it
}
retrieveCustomFieldsFromView(templateFieldNotEmpty)
retrieveCustomFieldsFromView(templateFieldNotEmpty, retrieveDefaultValues)
}
fun getEntryInfo(): EntryInfo {
populateEntryInfoWithViews(true)
populateEntryInfoWithViews(templateFieldNotEmpty = true,
retrieveDefaultValues = true)
return mEntryInfo ?: EntryInfo()
}
@@ -479,23 +480,31 @@ abstract class TemplateAbstractView<
return mViewFields.indexOfFirst { it.field.name.equals(name, true) }
}
private fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false) {
private fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false,
retrieveDefaultValues: Boolean = false) {
mEntryInfo?.customFields = mViewFields.mapNotNull {
getCustomField(it.field.name, templateFieldNotEmpty)
getCustomField(it.field.name, templateFieldNotEmpty, retrieveDefaultValues)
}.toMutableList()
}
protected fun getCustomField(fieldName: String): Field {
return getCustomField(fieldName, false)
?: Field(fieldName, ProtectedString(false))
return getCustomField(fieldName,
templateFieldNotEmpty = false,
retrieveDefaultValues = false
) ?: Field(fieldName, ProtectedString(false))
}
private fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? {
private fun getCustomField(fieldName: String,
templateFieldNotEmpty: Boolean,
retrieveDefaultValues: Boolean): Field? {
getViewFieldByName(fieldName)?.let { fieldId ->
val editView: View? = fieldId.view
val editView: View = fieldId.view
if (editView is GenericFieldView) {
// Do not return field with a default value
val defaultViewValue = if (editView.value == editView.default) "" else editView.value
val defaultViewValue =
if (retrieveDefaultValues || editView.value != editView.default) {
editView.value
} else ""
if (!templateFieldNotEmpty
|| (editView.tag == FIELD_CUSTOM_TAG && defaultViewValue.isNotEmpty())) {
return Field(
@@ -641,7 +650,8 @@ abstract class TemplateAbstractView<
override fun onSaveInstanceState(): Parcelable {
val superSave = super.onSaveInstanceState()
val saveState = SavedState(superSave)
populateEntryInfoWithViews(false)
populateEntryInfoWithViews(templateFieldNotEmpty = false,
retrieveDefaultValues = false)
saveState.template = this.mTemplate
saveState.entryInfo = this.mEntryInfo
onSaveEntryInstanceState(saveState)

View File

@@ -64,6 +64,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
TextEditFieldView(it).apply {
// hiddenProtectedValue (mHideProtectedValue) don't work with TextInputLayout
setProtection(field.protectedValue.isProtected)
default = templateAttribute.default
setMaxChars(templateAttribute.options.getNumberChars())
setMaxLines(templateAttribute.options.getNumberLines())
setActionClick(templateAttribute, field, this)
@@ -79,7 +80,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
return context?.let {
TextSelectFieldView(it).apply {
setItems(templateAttribute.options.getListItems())
default = field.protectedValue.stringValue
default = templateAttribute.default
setActionClick(templateAttribute, field, this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO
@@ -198,8 +199,9 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
return super.populateViewsWithEntryInfo(showEmptyFields)
}
override fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean) {
super.populateEntryInfoWithViews(templateFieldNotEmpty)
override fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean,
retrieveDefaultValues: Boolean) {
super.populateEntryInfoWithViews(templateFieldNotEmpty, retrieveDefaultValues)
mEntryInfo?.otpModel = OtpEntryFields.parseFields { key ->
getCustomField(key).protectedValue.toString()
}?.otpModel

View File

@@ -194,6 +194,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
get() = valueSpinnerAdapter.getItem(mDefaultPosition)
set(value) {
mDefaultPosition = valueSpinnerAdapter.getPosition(value)
valueSpinnerAdapter.notifyDataSetChanged()
}
override fun setOnActionClickListener(onActionClickListener: OnClickListener?,

View File

@@ -0,0 +1,32 @@
package com.kunzisoft.keepass.viewmodels
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
class AdvancedUnlockViewModel : ViewModel() {
var allowAutoOpenBiometricPrompt : Boolean = true
var deviceCredentialAuthSucceeded: Boolean? = null
val onInitAdvancedUnlockModeRequested : LiveData<Void?> get() = _onInitAdvancedUnlockModeRequested
private val _onInitAdvancedUnlockModeRequested = SingleLiveEvent<Void?>()
val onUnlockAvailabilityCheckRequested : LiveData<Void?> get() = _onUnlockAvailabilityCheckRequested
private val _onUnlockAvailabilityCheckRequested = SingleLiveEvent<Void?>()
val onDatabaseFileLoaded : LiveData<Uri?> get() = _onDatabaseFileLoaded
private val _onDatabaseFileLoaded = SingleLiveEvent<Uri?>()
fun initAdvancedUnlockMode() {
_onInitAdvancedUnlockModeRequested.call()
}
fun checkUnlockAvailability() {
_onUnlockAvailabilityCheckRequested.call()
}
fun databaseFileLoaded(databaseUri: Uri?) {
_onDatabaseFileLoaded.value = databaseUri
}
}

View File

@@ -2,6 +2,7 @@ package com.kunzisoft.keepass.viewmodels
import android.os.Parcel
import android.os.Parcelable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
@@ -30,6 +31,10 @@ class IconPickerViewModel: ViewModel() {
MutableLiveData<IconCustomState>()
}
val customIconUpdated : MutableLiveData<IconCustomState> by lazy {
MutableLiveData<IconCustomState>()
}
fun pickStandardIcon(icon: IconImageStandard) {
standardIconPicked.value = icon
}
@@ -54,6 +59,10 @@ class IconPickerViewModel: ViewModel() {
customIconRemoved.value = customIcon
}
fun updateCustomIcon(customIcon: IconCustomState) {
customIconUpdated.value = customIcon
}
data class IconCustomState(var iconCustom: IconImageCustom? = null,
var error: Boolean = true,
var errorStringId: Int = -1,

View File

@@ -144,7 +144,7 @@ internal class PublicSuffixListData(
}
companion object {
val WILDCARD_LABEL = byteArrayOf('*'.toByte())
val WILDCARD_LABEL = byteArrayOf('*'.code.toByte())
val PREVAILING_RULE = listOf("*")
val EMPTY_RULE = listOf<String>()
const val EXCEPTION_MARKER = '!'

View File

@@ -36,7 +36,7 @@ internal fun ByteArray.binarySearch(labels: List<ByteArray>, labelIndex: Int): S
while (true) {
val byte0 = if (expectDot) {
expectDot = false
'.'.toByte()
'.'.code.toByte()
} else {
labels[currentLabelIndex][currentLabelByteIndex] and BITMASK
}
@@ -103,7 +103,7 @@ internal fun ByteArray.binarySearch(labels: List<ByteArray>, labelIndex: Int): S
*/
private fun ByteArray.findStartOfLineFromIndex(start: Int): Int {
var index = start
while (index > -1 && this[index] != '\n'.toByte()) {
while (index > -1 && this[index] != '\n'.code.toByte()) {
index--
}
index++
@@ -115,7 +115,7 @@ private fun ByteArray.findStartOfLineFromIndex(start: Int): Int {
*/
private fun ByteArray.findEndOfLineFromIndex(start: Int): Int {
var end = 1
while (this[start + end] != '\n'.toByte()) {
while (this[start + end] != '\n'.code.toByte()) {
end++
}
return end

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorAccentLight" android:state_pressed="true" />
<item android:color="@color/white_grey_darker" android:state_enabled="false" />
<item android:color="?attr/colorAccent" android:state_enabled="true" />
</selector>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="@color/white"
tools:targetApi="lollipop">
<item>
<shape>
<corners
android:topLeftRadius="0dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"/>
<solid android:color="?attr/colorAccent"/>
</shape>
</item>
</ripple>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="@color/white"
tools:targetApi="lollipop">
<item>
<shape>
<stroke
android:width="1dp"
android:color="?attr/textColorInverse" />
<corners
android:topLeftRadius="0dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"/>
<solid
android:color="@color/transparent"/>
</shape>
</item>
</ripple>

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true">
<shape>
<corners
android:topLeftRadius="0dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"/>
<padding
android:left="4dp"
android:right="12dp"
android:top="18dp"
android:bottom="8dp"/>
<solid android:color="@color/orange_light"/>
</shape>
</item>
<item>
<shape>
<corners
android:topLeftRadius="0dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"/>
<padding
android:left="4dp"
android:right="12dp"
android:top="18dp"
android:bottom="8dp"/>
<solid android:color="@color/orange"/>
</shape>
</item>
</selector>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/white">
<item>
<shape>
<stroke
android:width="1dp"
android:color="@color/white_grey" />
<corners
android:topLeftRadius="0dp"
android:topRightRadius="40dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"/>
<solid
android:color="@color/transparent"/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M 11 2.0566406 C 6.762335 2.4220229 3.0067094 5.7987155 2.203125 9.9785156 C 1.3601753 13.960549 3.1781148 18.394742 6.7089844 20.480469 C 9.6237318 22.368157 13.514425 22.492178 16.582031 20.892578 C 17.959775 20.180473 19.316015 19.099467 20.087891 17.808594 L 18.402344 16.791016 C 16.277892 19.724364 12.039121 20.844605 8.7519531 19.306641 C 5.481064 17.911182 3.4461934 14.150571 4.109375 10.648438 C 4.6649664 7.2806969 7.5784749 4.4226117 11 4.0839844 L 11 2.0566406 z M 13 2.0644531 L 13 4.09375 C 16.367309 4.4801387 19.308002 7.2166099 19.861328 10.574219 C 20.123352 12.069186 19.935398 13.632674 19.367188 15.037109 C 19.94644 15.387646 20.527063 15.73602 21.105469 16.087891 C 22.671737 12.714066 22.120988 8.4920871 19.708984 5.6542969 C 18.063396 3.6246553 15.604973 2.2995704 13 2.0644531 z M 12 6.7148438 C 10.737143 6.7148437 9.7148438 7.737143 9.7148438 9 L 9.7148438 10.142578 L 9.1425781 10.142578 C 8.5140068 10.142578 8 10.656585 8 11.285156 L 8 15.857422 C 8 16.491709 8.5140068 17 9.1425781 17 L 14.857422 17 C 15.491709 17 16 16.491709 16 15.857422 L 16 11.285156 C 16 10.656585 15.491709 10.142578 14.857422 10.142578 L 14.285156 10.142578 L 14.285156 9 C 14.285156 7.737143 13.262857 6.7148438 12 6.7148438 z M 12 7.8574219 C 12.634286 7.8574219 13.142578 8.3714294 13.142578 9 L 13.142578 10.142578 L 10.857422 10.142578 L 10.857422 9 C 10.857422 8.3714294 11.371429 7.8574219 12 7.8574219 z M 12 12.427734 C 12.634286 12.427734 13.142578 12.943693 13.142578 13.572266 C 13.142578 14.20655 12.634286 14.714844 12 14.714844 C 11.371429 14.714844 10.857422 14.20655 10.857422 13.572266 C 10.857422 12.943693 11.371429 12.427734 12 12.427734 z" />
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<group
android:scaleX="0.8"
android:scaleY="0.8"
android:pivotX="12"
android:pivotY="12">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</group>
</vector>

View File

@@ -52,8 +52,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/biometric_message"
tools:text="@string/advanced_unlock_prompt_store_credential_title"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary"
android:textSize="14sp"
style="@style/KeepassDXStyle.TextAppearance.Secondary.TextOnPrimary"
android:gravity="center" />
<TextView
@@ -67,7 +66,6 @@
app:layout_constraintTop_toBottomOf="@+id/biometric_title"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="Sample error"
style="@style/KeepassDXStyle.TextAppearance.Secondary.TextOnPrimary"
android:textSize="12sp"
style="@style/KeepassDXStyle.TextAppearance.Warning.TextOnPrimary"
android:gravity="center" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -138,8 +138,8 @@
<include
layout="@layout/view_button_lock"
android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|bottom" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -100,8 +100,8 @@
<include
layout="@layout/view_button_lock"
android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />

View File

@@ -132,6 +132,7 @@
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_below="@+id/toolbar">
<FrameLayout
android:id="@+id/nodes_list_fragment_container"
android:layout_width="match_parent"
@@ -168,8 +169,8 @@
<include
layout="@layout/view_button_lock"
android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
</RelativeLayout>

View File

@@ -59,8 +59,8 @@
<include
layout="@layout/view_button_lock"
android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -23,6 +23,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
tools:targetApi="o">
<com.kunzisoft.keepass.view.SpecialModeView

View File

@@ -42,8 +42,8 @@
<include
layout="@layout/view_button_lock"
android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="start|bottom" />

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2021 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
KeePassDX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePassDX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/default_margin"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon_edit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginEnd="@dimen/default_margin"
android:src="@drawable/ic_blank_32dp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/icon_edit_name_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/icon_edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
android:hint="@string/hint_icon_name"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -56,7 +56,7 @@
android:layout_marginRight="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle"/>
style="@style/KeepassDXStyle.TextAppearance.Warning"/>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_dialog_bar"

View File

@@ -63,7 +63,7 @@
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
android:layout_marginRight="@dimen/card_view_margin_horizontal"
android:text="@string/error_otp_type"
style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle"/>
style="@style/KeepassDXStyle.TextAppearance.Warning"/>
<androidx.cardview.widget.CardView
android:id="@+id/card_view_otp_selection"

View File

@@ -18,7 +18,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
android:minHeight="36dp"
android:minHeight="48dp"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"

View File

@@ -1,48 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout
android:id="@+id/lock_button"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/lock_button_background"
android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size"
android:layout_marginStart="-2dp"
android:layout_marginLeft="-2dp"
android:layout_marginBottom="-2dp"
tools:targetApi="lollipop"
android:elevation="4dp"
style="@style/KeepassDXStyle.Special.Button.Background"
android:layout_gravity="bottom|start"
android:contentDescription="@string/menu_lock" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/lock_button_stroke"
android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size"
android:layout_marginStart="-2dp"
android:layout_marginLeft="-2dp"
android:layout_marginBottom="-2dp"
tools:targetApi="lollipop"
android:elevation="4dp"
style="@style/KeepassDXStyle.Special.Button.Stroke"
android:layout_gravity="bottom|start"
android:contentDescription="@string/menu_lock" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/lock_button_icon"
android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size"
android:paddingBottom="6dp"
android:paddingTop="16dp"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingEnd="12dp"
android:paddingRight="12dp"
tools:targetApi="lollipop"
android:elevation="4dp"
android:src="@drawable/ic_lock_white_24dp"
android:tint="@color/white"
android:layout_gravity="bottom|start"
android:contentDescription="@string/menu_lock" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_button"
style="@style/KeepassDXStyle.Fab.Special"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:layout_margin="8dp"
android:contentDescription="@string/lock"
android:src="@drawable/ic_lock_white_padding_24dp"
xmlns:app="http://schemas.android.com/apk/res-auto" />
</FrameLayout>

View File

@@ -19,6 +19,12 @@
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_edit"
android:icon="@drawable/ic_mode_edit_white_24dp"
android:title="@string/menu_edit"
android:orderInCategory="5"
app:iconTint="?attr/colorControlNormal"
app:showAsAction="ifRoom" />
<item android:id="@+id/menu_delete"
android:icon="@drawable/ic_delete_forever_white_24dp"
android:title="@string/menu_delete"

View File

@@ -79,7 +79,7 @@
<string name="no_url_handler">ثبت متصفح لزيارة هذا الرابط.</string>
<string name="progress_create">إنشاء قاعدة بيانات جديدة …</string>
<string name="protection">الحماية</string>
<string name="read_only">للقراءة فقط</string>
<string name="read_only">محمي من التعديل</string>
<string name="content_description_remove_from_list">حذف</string>
<string name="root">الجذر</string>
<string name="memory_usage">استخدام الذاكرة</string>
@@ -99,10 +99,10 @@
<string name="underline">تسطير</string>
<string name="uppercase">حروف كبيرة</string>
<string name="warning">تحذير</string>
<string name="warning_empty_password">هل تريد حقاً استخدام سلسلة فارغة ككلمة سرية ؟</string>
<string name="warning_no_encryption_key">هل أنت متأكد من أنك لا تريد استخدام أي مفتاح تشفير ؟</string>
<string name="warning_empty_password">هل تريد المتابعة دون حماية قاعدة البيانات بكلمة سر ؟</string>
<string name="warning_no_encryption_key">أمتأكد أنك لا تريد استخدام أي مفتاح لتشفير ؟</string>
<string name="version_label">الإصدار %1$s</string>
<string name="education_new_node_title">أضف عناصر جديدة إلى قاعدتك</string>
<string name="education_new_node_title">أضف عناصر إلى قاعدة البيانات</string>
<string name="education_entry_new_field_title">إضافة حقول مخصصة</string>
<string name="education_field_copy_title">نسخ حقل</string>
<string name="education_lock_title">تأمين قاعدة البيانات</string>
@@ -111,7 +111,7 @@
<string name="add_entry">إضافة مدخلة</string>
<string name="edit_entry">تحرير مدخلة</string>
<string name="key_derivation_function">وظيفة اشتقاق المفتاح</string>
<string name="app_timeout">مهلة التطبيق</string>
<string name="app_timeout">انتهت المهلة</string>
<string name="app_timeout_summary">مدة الانتظار قبل إقفال قاعدة البيانات</string>
<string name="file_manager_install_description">المحرر الذي يمتلك صلاحتي ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT ضروري لانشاء, وفتح وحفض قواعد البيانات.</string>
<string name="clipboard_error">بعض الأجهزة لا تسمح للتطبيقات باستعمال الحافظة.</string>
@@ -120,8 +120,8 @@
<string name="select_to_copy">اختر لنسخ %1$s إلى الحافظة</string>
<string name="retrieving_db_key">يجلب مفتاح قاعدة البيانات…</string>
<string name="default_checkbox">استخدامها كقاعدة بيانات افتراضية</string>
<string name="html_about_licence">KeePassDX © %1$d كونزيسوفت &lt;strong&gt;مفتوح المصدر&lt;/strong&gt; و &lt;strong&gt;بدون اعلانات&lt;/strong&gt;.
\n يوزع كما هو، بدون ضمان, تحت ترخيص &lt;strong&gt;GPLv3&lt;/strong&gt;</string>
<string name="html_about_licence">KeePassDX © %1$d كونزيسوفت <strong>مفتوح المصدر</strong> و <strong>بدون اعلانات</strong>.
\n يوزع كما هو، بدون ضمان, تحت ترخيص <strong>GPLv3</strong>.</string>
<string name="entry_accessed">نُفذ إليه</string>
<string name="entry_expires">تنتهي صلاحيته في</string>
<string name="entry_keyfile">ملف المفتاح</string>
@@ -133,7 +133,7 @@
<string name="error_load_database">تعذر تحميل قاعدة البيانات.</string>
<string name="error_load_database_KDF_memory">لا يمكن تحميل المفتاح، حاول تقليل \"الذاكرة المستخدمة\" من قبل KDF.</string>
<string name="error_pass_gen_type">يجب تحديد نوع واحد على الأقل لتوليد كلمة السر.</string>
<string name="error_rounds_too_large">\"جولات\" كبيرة جداً. الإعداد إلى 2147483648.</string>
<string name="error_rounds_too_large">\"جولات التحويل\" كثيرة جداً. الإعداد إلى 2147483648.</string>
<string name="error_string_key">يجب أن يكون لكل سلسلة اسم حقل.</string>
<string name="error_wrong_length">أدخل عددًا صحيحًا موجبًا في حقل «الطول».</string>
<string name="error_autofill_enable_service">تعذر تمكين خدمة الملء التلقائي.</string>
@@ -147,7 +147,7 @@
<string name="hint_generated_password">كلمة السر الموَلدة</string>
<string name="hint_keyfile">الملف المفتاحي</string>
<string name="hide_password_title">اخفاء كلمات السر</string>
<string name="copy_field">نُسخ %1$s</string>
<string name="copy_field">نُسخة من %1$s</string>
<string name="menu_copy">نسخ</string>
<string name="menu_move">نقل</string>
<string name="menu_paste">لصق</string>
@@ -166,8 +166,8 @@
<string name="unsupported_db_version">قاعدة بيانات غير مدعومة.</string>
<string name="build_label">بناء %1$s</string>
<string name="encrypted_value_stored">تم حفظ كلمة السر المشفرة</string>
<string name="no_credentials_stored">قاعدة البيانات لا تمتلك كلمة سر.</string>
<string name="menu_appearance_settings">مظهر</string>
<string name="no_credentials_stored">قاعدة البيانات لا تمتلك بيانات اعتماد.</string>
<string name="menu_appearance_settings">المظهر</string>
<string name="general">عام</string>
<string name="autofill">ملأ تلقائي</string>
<string name="autofill_sign_in_prompt">سجل باستخدام KeePassDX</string>
@@ -187,7 +187,7 @@
<string name="file_name">اسم الملف</string>
<string name="path">مسار</string>
<string name="database_history">تأريخ</string>
<string name="clipboard_notifications_summary">مكن اشعارات الحافظة لنسخ الحقول</string>
<string name="clipboard_notifications_summary">أظهر اشعارات الحافظة لنسخ الحقول عند عرض مدخل</string>
<string name="advanced_unlock">البصمة</string>
<string name="biometric_unlock_enable_title">فحص البصمة</string>
<string name="biometric_unlock_enable_summary">يسمح بفحص البصمة لفتح قاعدة البيانات</string>
@@ -210,23 +210,23 @@
<string name="keyboard_notification_entry_summary">أظهر إشعار عند توفر مدخل</string>
<string name="keyboard_notification_entry_content_title_text">مدخل</string>
<string name="keyboard_notification_entry_clear_close_title">إمسح عند الخروج</string>
<string name="keyboard_notification_entry_clear_close_summary">إمسح مدخل الحافظة عند إغلاق الإشعار</string>
<string name="keyboard_notification_entry_clear_close_summary">أغلق قاعدة البيانات عند إغلاق الإشعار</string>
<string name="keyboard_appearance_category">مظهر</string>
<string name="keyboard_theme_title">سمة لوحة المفاتيح</string>
<string name="keyboard_keys_category">مفاتيح</string>
<string name="keyboard_key_vibrate_title">إهتز عند اللمس</string>
<string name="keyboard_key_vibrate_title">إهتزاز عند اللمس</string>
<string name="keyboard_key_sound_title">صوت عند اللمس</string>
<string name="allow_no_password_title">"إسمح بالفتح دون كلمة سر "</string>
<string name="enable_read_only_title">محمي من التعديل</string>
<string name="enable_read_only_summary">افتح قاعدة البيانات للقراءة فقط افتراضيا</string>
<string name="enable_education_screens_title">شاشات تعليمية</string>
<string name="reset_education_screens_summary">أعد عرض كل العناصر التعليمية</string>
<string name="reset_education_screens_text">إعادة تعيين الشاشات التعليمية</string>
<string name="reset_education_screens_summary">أعد عرض كل المعلومات التعليمية</string>
<string name="reset_education_screens_text">إعادة تعيين الشاشات التلميحات</string>
<string name="education_create_database_title">أنشئ قاعدة بيانات</string>
<string name="education_create_database_summary">أنشئ ملف إدارة كلمات السر.</string>
<string name="education_select_database_title">إفتح قاعدة بيانات</string>
<string name="sort_recycle_bin_bottom">سلة المحذوفات في الأسفل</string>
<string name="sort_db">قاعده بيانات طبيعية</string>
<string name="sort_db">ترتيب طبيعي</string>
<string name="sort_last_access_time">الوصول</string>
<string name="lock">إقفال</string>
<string name="assign_master_key">تعيين مفتاح رئيسي</string>
@@ -246,9 +246,9 @@
<string name="content_description_background">الخلفية</string>
<string name="rounds">دورات التحويل</string>
<string name="rounds_explanation">توفر الدورات الاضافية ضد هجوم توليد التركيبات ،لكنها تبطئ التحميل والحفظ.</string>
<string name="memory_usage_explanation">مقدار الذاكرة لاستخدامها في دالة اشتقاق المفتاح.</string>
<string name="memory_usage_explanation">مقدار الذاكرة المستخدمة في دالة اشتقاق المفتاح.</string>
<string name="parallelism_explanation">درجة التوازي (عدد العمليات) لدالة اشتقاق المفتاح.</string>
<string name="sort_groups_before">مجموعات قبل</string>
<string name="sort_groups_before">المجموعات أولًا</string>
<string name="selection_mode">نمط التحديد</string>
<string name="do_not_kill_app">لا تقتل التطبيق…</string>
<string name="content_description_node_children">العقد الفرعية</string>
@@ -264,7 +264,7 @@
<string name="content_description_update_from_list">تحديث</string>
<string name="content_description_keyboard_close_fields">أغلق الحقول</string>
<string name="error_create_database_file">لا يمكن انشاء قاعدة بيانات بكلمة السر وملف المفتاح الحاليين.</string>
<string name="menu_advanced_unlock_settings">إلغاء القفل المتقدم</string>
<string name="menu_advanced_unlock_settings">فك القفل المتقدم</string>
<string name="entry_attachments">مرفقات</string>
<string name="entry_history">السجل</string>
<string name="entry_add_attachment">أضف مرفقا</string>
@@ -283,7 +283,7 @@
<string name="otp_algorithm">الخوارزمية</string>
<string name="otp_digits">أرقام</string>
<string name="otp_counter">العداد</string>
<string name="entry_setup_otp">كلمة المرور للمرة الواحدة</string>
<string name="entry_setup_otp">عيّن كلمة مرور لمرة واحدة</string>
<string name="entry_UUID">UUID</string>
<string name="html_about_contribution">من أجل &lt;strong&gt;حماية خصوصيتا&lt;/strong&gt;٫&lt;strong&gt; إصلاح العلل&lt;/strong&gt;٫ &lt;strong&gt;إضافة مميزات&lt;/strong&gt; &lt;strong&gt;وجعلنا نشطاء دائما&lt;/strong&gt;٫ نحن نعتمد على &lt;strong&gt;مساهمتك&lt;/strong&gt;.</string>
<string name="content_description_keyfile_checkbox">خانة تأشير الملف المفتاحي</string>
@@ -295,10 +295,10 @@
<string name="hide_broken_locations_title">اِخفي روابط قواعد البيانات المعطلة</string>
<string name="show_recent_files_summary">أظهر موقع قواعد البيانات الأخيرة</string>
<string name="show_recent_files_title">أظهر الملفات الأخيرة</string>
<string name="remember_keyfile_locations_summary">تذكر موقع الملفات المفتاحية لقاعدة البيانات</string>
<string name="remember_keyfile_locations_title">احفظ موقع الملف المفتاحي</string>
<string name="remember_database_locations_summary">تذكر موقع قاعدة البيانات</string>
<string name="remember_database_locations_title">موقع تخزين قاعدة البيانات</string>
<string name="remember_keyfile_locations_summary">تعقب موقع الملفات المفتاحية لقاعدة البيانات</string>
<string name="remember_keyfile_locations_title">تذكر موقع الملف المفتاحي</string>
<string name="remember_database_locations_summary">تعقب موقع قاعدة البيانات</string>
<string name="remember_database_locations_title">تذكر موقع تخزين قاعدة البيانات</string>
<string name="contains_duplicate_uuid_procedure">للمتابعة هل تريد حل المشكلة بتوليد UUID للعناصر المكررة ؟</string>
<string name="contains_duplicate_uuid">تحتوي قاعدة البيانات على UUID مكرر.</string>
<string name="auto_focus_search_title">البحث السريع</string>
@@ -379,8 +379,8 @@
<string name="keyboard_selection_entry_title">اختيار المدخلة</string>
<string name="device_keyboard_setting_title">إعدادات لوحة مفاتيح الجهاز</string>
<string name="magic_keyboard_explanation_summary">نشِّط لوحة مفاتيح مخصصة لملأ كلمة السر وحقول معرّفك</string>
<string name="biometric_auto_open_prompt_summary">اطلب فحص البصمة ان كانت قاعدة البيانات معدّة لذلك</string>
<string name="biometric_auto_open_prompt_title">افتح محث البصمة تلقائيا</string>
<string name="biometric_auto_open_prompt_summary">اطلب فك القفل المتقدم ان كانت قاعدة البيانات معدّة لذلك</string>
<string name="biometric_auto_open_prompt_title">افتح المحث تلقائيا</string>
<string name="keystore_not_accessible">لم يُهيأ مخزن المفاتيح بشكل صحيح.</string>
<string name="warning_remove_unlinked_attachment">حذف البيانات سيقلل من حجم قاعدة البيانات لكن احذر أن تكون إحدى هذه البيانات ملحقة لكي-باس.</string>
<string name="subdomain_search_summary">البحث في نطاقات الويب التي فيها قيود النطاقات الفرعية</string>
@@ -408,4 +408,79 @@
<string name="education_generate_password_title">أنشئ كلمة سر قوية</string>
<string name="save_mode">وضع الحفظ</string>
<string name="search_mode">وضع البحث</string>
<string name="version">النسخة</string>
<string name="template_group_name">النماذج</string>
<string name="holder">الحامل</string>
<string name="number">الرقم</string>
<string name="card_verification_value">CVV</string>
<string name="personal_identification_number">PIN</string>
<string name="id_card">بطاقة الهوية</string>
<string name="type">النوع</string>
<string name="cryptocurrency">محفظة عملات مشفرة</string>
<string name="public_key">المفتاح العمومي</string>
<string name="private_key">المفتاح الخاص</string>
<string name="account">الحساب</string>
<string name="bank">مصرف</string>
<string name="bank_name">اسم المصرف</string>
<string name="secure_note">ملاحظة آمنة</string>
<string name="error_word_reserved">هذه الكلمة محجوزة ولا يمكن استخدامها.</string>
<string name="error_field_name_already_exists">اسم الحقل موجود سلفًا.</string>
<string name="error_file_to_big">الملف الذي ترفعه كبير.</string>
<string name="error_upload_file">حدث خطأ أثناء رفع الملف.</string>
<string name="error_duplicate_file">بيانات الملف موجودة سلفًا.</string>
<string name="error_remove_file">حدث خطأ أثناء إزالة بيانات الملف.</string>
<string name="error_start_database_action">حدث خطأ أثناء تنفيذ إجراء على قاعدة البيانات.</string>
<string name="content_description_otp_information">معلومات السر لمرة واحدة</string>
<string name="membership">العضوية</string>
<string name="name">الاسم</string>
<string name="email">البريد الإلكتروني</string>
<string name="email_address">البريد الإلكتروني</string>
<string name="ssid">SSID</string>
<string name="debit_credit_card">بطاقة السحب الفوري / الإئتمان</string>
<string name="error_registration_read_only">لا يمكن حفظ عنصر في قاعدة بيانات مفتوحة للقراءة فقط</string>
<string name="otp_secret">الرمز السري</string>
<string name="place_of_issue">مكان المشكلة</string>
<string name="date_of_issue">تاريخ المشكلة</string>
<string name="standard">المعيار</string>
<string name="template">النموذج</string>
<string name="error_invalid_OTP">الرمز السري لـ OTP غير صالح.</string>
<string name="error_otp_digits">يجب أن الرمز محتوًا بين %1$d و %2$d رقمًا.</string>
<string name="autofill_select_entry">اختر مُدخلًا…</string>
<string name="content">المحتوى</string>
<string name="keyboard_save_search_info_title">احفظ المعلومات المشاركة</string>
<string name="custom_fields">حقول مخصصة</string>
<string name="back_to_previous_keyboard">عُد للوحة المفاتيح السابقة</string>
<string name="select_entry">اختر مدخلًا</string>
<string name="autofill_close_database_title">أغلق قاعدة البيانات</string>
<string name="success_import_app_properties">أّستوردت خصائص التطبيق</string>
<string name="success_export_app_properties">صُدرت خصائص التطبيق</string>
<string name="warning_database_revoked">أُجهض الوصول إلى الملف بواسطة مدير الملفات ، أغلق قاعدة البيانات ثم أعد فتحها.</string>
<string name="properties">الخصائص</string>
<string name="token">الرمز</string>
<string name="seed">البذرة</string>
<string name="error_database_uri_null">يتعذر استرداد مسار قاعدة البيانات.</string>
<string name="error_rebuild_list">يتعذر إعادة بناء القائمة بشكل صحيح.</string>
<string name="menu_keystore_remove_key">احذف رمز فك القفل المتقدم</string>
<string name="menu_form_filling_settings">تعبئة الحقول</string>
<string name="menu_reload_database">أعد تحميل قاعدة البيانات</string>
<string name="menu_external_icon">أيقونة خارجية</string>
<string name="registration_mode">وضع التسجيل</string>
<string name="import_app_properties_title">استورد خصائص التطبيق</string>
<string name="import_app_properties_summary">اختر ملفًا لاستيراد خصائص التطبيق</string>
<string name="export_app_properties_title">صدّر خصائص التطبيق</string>
<string name="export_app_properties_summary">أنشئ ملفًا لتصدير خصائص التطبيق</string>
<string name="error_import_app_properties">خطأ أثناء استيراد خصائص التطبيق</string>
<string name="error_export_app_properties">خطأ أثناء تصدير خصائص التطبيق</string>
<string name="warning_database_info_changed">غُيِّرت معلومات قاعدة البيانات من خارج هذا التطبيق.</string>
<string name="warning_database_info_changed_options">اكتب فوق التعديلات الخارجية عن طريق حفظ قاعدة البيانات أو أعد تحميلها لتضمين هذه التغييرات.</string>
<string name="open_advanced_unlock_prompt_store_credential">افتح محث فك القفل المتقدم لتخزين بيانات الاعتماد</string>
<string name="open_advanced_unlock_prompt_unlock_database">افتح محث فك القفل المتقدم لفتح قاعدة البيانات</string>
<string name="credential_before_click_advanced_unlock_button">اكتب كلمة السر، وأنقر هذا الزر.</string>
<string name="device_credential">بيانات الاعتماد للجهاز</string>
<string name="advanced_unlock_tap_delete">انفر لحذف مفاتيح فك القفل المتقدم</string>
<string name="keyboard_search_share_title">ابحث في المعلومات المشاركة</string>
<string name="keyboard_auto_go_action_title">إجراء اللمس التلقائي</string>
<string name="keyboard_previous_fill_in_title">إجراء لمس تلقائي</string>
<string name="keyboard_previous_lock_title">اقفل قاعدة البيانات</string>
<string name="education_advanced_unlock_title">فك القفل المتقدم لقاعدة البيانات</string>
</resources>

View File

@@ -197,7 +197,7 @@
<string name="clipboard_warning">Vymazat historii schránky manuálně, pokud automatické vymazání schránky selže.</string>
<string name="lock">Zamknout</string>
<string name="lock_database_screen_off_title">Zámek obrazovky</string>
<string name="lock_database_screen_off_summary">Při zhasnutí obrazovky uzamknout databázi</string>
<string name="lock_database_screen_off_summary">Několik vteřin po zhasnutí obrazovky uzamknout databázi</string>
<string name="advanced_unlock">Rozšířené odemknutí</string>
<string name="biometric_unlock_enable_title">Biometrické odemknutí</string>
<string name="biometric_unlock_enable_summary">Nechá otevřít databázi snímáním biometrického údaje</string>
@@ -224,7 +224,7 @@
<string name="application_appearance">Rozhraní</string>
<string name="other">Ostatní</string>
<string name="keyboard">Klávesnice</string>
<string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_title">Klávesnice Magikeyboard</string>
<string name="magic_keyboard_explanation_summary">Aktivovat vlastní klávesnici, která snadno vyplní hesla a další položky identity</string>
<string name="allow_no_password_title">Umožnit bez hlavního klíče</string>
<string name="allow_no_password_summary">Povolit klepnutí na \"Otevřít\", i když není vybráno žádné heslo</string>
@@ -236,7 +236,7 @@
<string name="reset_education_screens_summary">Opět zobrazit všechny vzdělávací informace</string>
<string name="reset_education_screens_text">Nastavit vzdělávací nápovědy do výchozího stavu</string>
<string name="education_create_database_title">Vytvořit databázový soubor</string>
<string name="education_create_database_summary">Založte svůj první soubor pro správu hesel.</string>
<string name="education_create_database_summary">Vytvořte svůj první soubor pro správu hesel.</string>
<string name="education_select_database_title">Otevřít existující databázi</string>
<string name="education_select_database_summary">Otevřete svou dříve používanou databázi ze správce souborů a pokračujte v jejím používání.</string>
<string name="education_new_node_title">Přidejte záznamy do databáze</string>
@@ -267,7 +267,7 @@
<string name="education_sort_summary">Vyberte řazení položek a skupin.</string>
<string name="education_donation_title">Zapojit se</string>
<string name="education_donation_summary">Zapojte se a pomozte zvýšit stabilitu, bezpečnost a doplnění dalších funkcí.</string>
<string name="html_text_ad_free">Na rozdíl od mnoha aplikací pro správu hesel je tato &lt;strong&gt;bez reklam&lt;/strong&gt;, je \u0020&lt;strong&gt;svobodný software pod copyleft licencí&lt;/strong&gt; a nesbírá žádné osobní údaje na svých serverech bez ohledu na to, jakou verzi používáte.</string>
<string name="html_text_ad_free">Na rozdíl od mnoha aplikací pro správu hesel je tato <strong>bez reklam</strong>, je <strong>svobodný software pod copyleft licencí</strong> a nesbírá žádné osobní údaje na svých serverech bez ohledu na to, jakou verzi používáte.</string>
<string name="html_text_buy_pro">Zakoupením varianty \"pro\" získáte přístup k tomuto &lt;strong&gt;vizuálnímu stylu&lt;/strong&gt; a hlavně pomůžete &lt;strong&gt;uskutečnění komunitních projektů.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Tento &lt;strong&gt;vizuální styl&lt;/strong&gt; je k dispozici díky vaší štědrosti.</string>
<string name="html_text_donation">Pro zajištění svobody nás všech a pokračování aktivity počítáme s Vaším &lt;strong&gt;přispěním.&lt;/strong&gt;</string>
@@ -285,8 +285,8 @@
<string name="icon_pack_choose_title">Sada ikon</string>
<string name="icon_pack_choose_summary">Sada ikon používaných v aplikaci</string>
<string name="build_label">Sestavení %1$s</string>
<string name="keyboard_name">Magikeyboard</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
<string name="keyboard_name">Klávesnice Magikeyboard</string>
<string name="keyboard_label">Klávesnice Magikeyboard (KeePassDX)</string>
<string name="keyboard_setting_label">Magikeyboard nastavení</string>
<string name="keyboard_entry_category">Záznam</string>
<string name="keyboard_entry_timeout_title">Časový limit</string>
@@ -315,7 +315,7 @@
<string name="delete_entered_password_title">Smazat heslo</string>
<string name="delete_entered_password_summary">Smaže heslo zadané po pokusu o připojení k databázi</string>
<string name="content_description_open_file">Otevřít soubor</string>
<string name="content_description_node_children">Podřazené prvky uzlu</string>
<string name="content_description_node_children">Podřazení uzlu</string>
<string name="content_description_add_node">Přidat uzel</string>
<string name="content_description_add_entry">Přidat záznam</string>
<string name="content_description_add_group">Přidat skupinu</string>
@@ -411,7 +411,7 @@
<string name="hide_expired_entries_summary">Propadlé záznamy nebudou ukázány</string>
<string name="contact">Kontakt</string>
<string name="contribution">Příspěvky</string>
<string name="feedback">Feedback</string>
<string name="feedback">Zpětná vazba</string>
<string name="auto_focus_search_title">Snadné hledání</string>
<string name="auto_focus_search_summary">Při otevření databáze žádat hledání</string>
<string name="remember_database_locations_title">Pamatovat si umístění databází</string>
@@ -474,13 +474,13 @@
<string name="database_data_remove_unlinked_attachments_summary">Odstraní přílohy obsažené v databázi, ale nikoli přílohy propojené se záznamem</string>
<string name="education_add_attachment_title">Přidat přílohu</string>
<string name="education_add_attachment_summary">Nahrát přílohu k záznamu pro uložení důležitých externích dat.</string>
<string name="show_uuid_summary">Ukáže UUID propojené se záznamem</string>
<string name="show_uuid_summary">Ukáže UUID propojené se záznamem nebo skupinou</string>
<string name="show_uuid_title">Ukázat UUID</string>
<string name="autofill_read_only_save">Uložení dat není povoleno, je-li databáze v režimu pouze pro čtení.</string>
<string name="autofill_ask_to_save_data_summary">Zeptat se na uložení dat, jakmile byl formulář přezkoušen</string>
<string name="autofill_ask_to_save_data_title">Zeptat se před uložením</string>
<string name="autofill_save_search_info_summary">Pokuste se uložit údaje hledání, když manuálně vybíráte položku</string>
<string name="autofill_save_search_info_title">Uložit info hledání</string>
<string name="autofill_save_search_info_title">Uložit výsledky vyhledá</string>
<string name="autofill_close_database_summary">Zavřít databázi po samovyplnění polí</string>
<string name="autofill_close_database_title">Zavřít databázi</string>
<string name="keyboard_previous_lock_summary">Po uzamknutí databáze automaticky přepnout zpět na předchozí klávesnici</string>
@@ -491,9 +491,9 @@
<string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</string>
<string name="configure_biometric">Žádné přihlašovací ani biometrické údaje nejsou registrovány.</string>
<string name="warning_empty_recycle_bin">Trvale odstranit všechny uzly z koše\?</string>
<string name="registration_mode">Režim registrace</string>
<string name="save_mode">Režim ukládání</string>
<string name="search_mode">Režim vyhledávání</string>
<string name="registration_mode">Registrace</string>
<string name="save_mode">Uložit</string>
<string name="search_mode">Vyhledávání</string>
<string name="error_field_name_already_exists">Jméno kolonky již existuje.</string>
<string name="error_registration_read_only">Uložení nové položky v režimu databáze pouze pro čtení není povoleno</string>
<string name="enter">Enter</string>
@@ -596,4 +596,10 @@
<string name="holder">Majitel</string>
<string name="debit_credit_card">Debitní / Kreditní karta</string>
<string name="template_group_name">Předlohy</string>
<string name="show_otp_token_summary">Ukáže OTP tokeny v seznamu záznamů</string>
<string name="show_otp_token_title">Ukázat OTP token</string>
<string name="menu_external_icon">Externí ikona</string>
<string name="autofill_select_entry">Vyberte položku…</string>
<string name="autofill_manual_selection_summary">Zobrazit možnosti umožňující uživateli si vybrat položku z databáze</string>
<string name="autofill_manual_selection_title">Ruční výběr</string>
</resources>

View File

@@ -75,7 +75,7 @@
<string name="error_out_of_memory">Zu wenig Speicherplatz, um die ganze Datenbank zu laden.</string>
<string name="error_pass_gen_type">Mindestens eine Art der Passwortgenerierung muss ausgewählt sein.</string>
<string name="error_pass_match">Die Passwörter stimmen nicht überein.</string>
<string name="error_rounds_too_large">Transformationsrunden“ zu hoch. Wird auf 2147483648 eingestellt.</string>
<string name="error_rounds_too_large">Schlüsseltransformationen“ zu hoch. Wird auf 2147483648 eingestellt.</string>
<string name="error_string_key">Für jede Zeichenfolge ist ein Feldname notwendig.</string>
<string name="error_wrong_length">Eine positive ganze Zahl in das Feld „Länge“ eingeben.</string>
<string name="field_name">Feldname</string>
@@ -160,7 +160,7 @@
<string name="clipboard_notifications_title">Zwischenablage-Benachrichtigung</string>
<string name="clipboard_notifications_summary">Benachrichtigungen zur Zwischenablage anzeigen, um beim Betrachten eines Eintrags Felder kopieren zu können</string>
<string name="lock_database_screen_off_title">Bildschirmsperre</string>
<string name="lock_database_screen_off_summary">Datenbank sperren, wenn der Bildschirm ausgeschaltet wird</string>
<string name="lock_database_screen_off_summary">Datenbank sperren, einige Sekunden nachdem der Bildschirm ausgeschaltet wird</string>
<string name="create_keepass_file">Neue Datenbank erstellen</string>
<string name="assign_master_key">Hauptschlüssel zuweisen</string>
<string name="path">Pfad</string>
@@ -490,7 +490,7 @@
<string name="education_add_attachment_title">Anhang hinzufügen</string>
<string name="database_data_remove_unlinked_attachments_summary">Entfernt Anhänge die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind</string>
<string name="warning_sure_add_file">Soll die Datei trotzdem hinzugefügt werden\?</string>
<string name="show_uuid_summary">Zeigt die mit einem Eintrag verknüpfte UUID an</string>
<string name="show_uuid_summary">Zeigt die mit einem Eintrag oder einer Gruppe verknüpfte UUID an</string>
<string name="show_uuid_title">UUID anzeigen</string>
<string name="autofill_read_only_save">Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.</string>
<string name="autofill_close_database_title">Datenbank schließen</string>
@@ -610,4 +610,7 @@
<string name="wireless">WLAN</string>
<string name="email_address">E-Mail-Adresse</string>
<string name="email">E-Mail</string>
<string name="menu_external_icon">Externes Symbol</string>
<string name="show_otp_token_summary">Zeigt OTP-Tokens in der Liste der Einträge an</string>
<string name="show_otp_token_title">OTP-Token anzeigen</string>
</resources>

View File

@@ -182,7 +182,7 @@
<string name="clipboard_notifications_summary">Εμφάνιση ειδοποιήσεων πρόχειρου για αντιγραφή πεδίων κατά την προβολή μιας καταχώρησης</string>
<string name="lock">Κλείδωμα</string>
<string name="lock_database_screen_off_title">Κλείδωμα Οθόνης</string>
<string name="lock_database_screen_off_summary">Κλείδωμα της βάσης δεδομένων όταν η οθόνη είναι απενεργοποιημένη</string>
<string name="lock_database_screen_off_summary">Κλείδωμα της βάση δεδομένων μετά από μερικά δευτερόλεπτα μόλις η οθόνη είναι απενεργοποιηθεί</string>
<string name="unavailable_feature_text">Δεν ήταν δυνατή η εκκίνηση αυτής της λειτουργίας.</string>
<string name="unavailable_feature_version">Η συσκευή τρέχει Android %1$s, αλλά χρειάζεται %2$s ή μεταγενέστερη έκδοση.</string>
<string name="unavailable_feature_hardware">Δεν ήταν δυνατή η εύρεση του αντίστοιχου υλικού.</string>
@@ -473,7 +473,7 @@
<string name="error_string_type">Αυτό το κείμενο δεν ταιριάζει με το ζητούμενο στοιχείο.</string>
<string name="content_description_credentials_information">Πληροφορίες Διαπιστευτηρίων</string>
<string name="content_description_add_item">Προσθήκη είδους</string>
<string name="show_uuid_summary">Εμφανίζει το UUID που είναι συνδεδεμένο σε μια καταχώριση</string>
<string name="show_uuid_summary">Εμφανίζει το UUID που είναι συνδεδεμένο σε μια καταχώρηση ή σε μια ομάδα</string>
<string name="show_uuid_title">Εμφάνιση UUID</string>
<string name="autofill_read_only_save">Δεν επιτρέπεται η αποθήκευση δεδομένων για μια βάση δεδομένων που ανοίγει ως μόνο για ανάγνωση.</string>
<string name="autofill_ask_to_save_data_summary">Ζητήστε να αποθηκεύσετε δεδομένα όταν επικυρώνεται μια φόρμα</string>
@@ -583,7 +583,7 @@
<string name="cryptocurrency">Πορτοφόλι κρυπτονομισμάτων</string>
<string name="type">Τύπος</string>
<string name="ssid">SSID</string>
<string name="wireless">Wifi</string>
<string name="wireless">Wi-Fi</string>
<string name="email_address">Διεύθυνση ηλεκτρονικού ταχυδρομείου</string>
<string name="email">Email</string>
<string name="date_of_issue">Ημερομηνία έκδοσης</string>
@@ -596,4 +596,10 @@
<string name="holder">Κάτοχος</string>
<string name="debit_credit_card">Χρεωστική / Πιστωτική Κάρτα</string>
<string name="template_group_name">Πρότυπα</string>
<string name="show_otp_token_summary">Εμφανίζει OTP tokens στη λίστα καταχωρήσεων</string>
<string name="show_otp_token_title">Εμφάνιση OTP Token</string>
<string name="menu_external_icon">Εξωτερικό εικονίδιο</string>
<string name="autofill_manual_selection_summary">Εμφάνισης επιλογής για να επιτρέψει στον χρήστη να επιλέξει την καταχώρηση βάσης δεδομένων</string>
<string name="autofill_manual_selection_title">Χειροκίνητη επιλογή</string>
<string name="autofill_select_entry">Επιλογή καταχώρησης…</string>
</resources>

View File

@@ -189,7 +189,7 @@
<string name="clipboard_notifications_summary">Mostrar las notificaciones del portapapeles para copiar campos al examinar una entrada</string>
<string name="lock">Bloquear</string>
<string name="lock_database_screen_off_title">Bloqueo de pantalla</string>
<string name="lock_database_screen_off_summary">Bloquear la base de datos cuando la pantalla esté apagada</string>
<string name="lock_database_screen_off_summary">Bloquear la base de datos después de unos segundos cuando la pantalla esté apagada</string>
<string name="advanced_unlock">Desbloqueo avanzado</string>
<string name="biometric_unlock_enable_title">Desbloqueo biométrico</string>
<string name="biometric_unlock_enable_summary">Le permite escanear sus datos biométricos para abrir la base de datos</string>
@@ -525,7 +525,7 @@
<string name="keyboard_save_search_info_summary">Luego de compartir un URL con KeePassDX, cuando se selecciona una entrada, intentar recordarla para futuros usos</string>
<string name="keyboard_save_search_info_title">Guardar información compartida</string>
<string name="keyboard_search_share_summary">Al compartir un URL con KeePassDX, filtrar las entradas utilizando el dominio de ese URL</string>
<string name="show_uuid_summary">Muestra el UUID vinculado a una entrada</string>
<string name="show_uuid_summary">Muestra el UUID vinculado a una entrada o a un grupo</string>
<string name="show_uuid_title">Mostrar UUID</string>
<string name="error_rebuild_list">No es posible reconstruir adecuadamente la lista.</string>
<string name="error_database_uri_null">La URI de la base de datos no puede ser recuperada.</string>
@@ -598,4 +598,10 @@
<string name="ssid">SSID</string>
<string name="personal_identification_number">Número de Identificación Personal</string>
<string name="card_verification_value">CVV</string>
<string name="show_otp_token_summary">Muestra los tokens OTP en la lista de entradas</string>
<string name="show_otp_token_title">Mostrar token OTP</string>
<string name="menu_external_icon">Icono externo</string>
<string name="autofill_manual_selection_summary">Mostrar opción para permitir al usuario seleccionar la entrada de la base de datos</string>
<string name="autofill_manual_selection_title">Selección manual</string>
<string name="autofill_select_entry">Seleccionar entrada…</string>
</resources>

View File

@@ -2,7 +2,7 @@
<resources>
<string name="menu_appearance_settings">ظاهر</string>
<string name="database_history">تاریخچه</string>
<string name="no_credentials_stored">این پایگاه داده هنوز اطلاعات کاربری ذخیره نشده است.</string>
<string name="no_credentials_stored">در این پایگاه داده هنوز اعتبار نامه ذخیره نشده است.</string>
<string name="encrypted_value_stored">رمز رمزگذاری شده ذخیره شده است</string>
<string name="keystore_not_accessible">فروشگاه اصلی به درستی تنظیم نشده است.</string>
<string name="build_label">%1$s را بسازید</string>
@@ -117,7 +117,7 @@
<string name="invalid_db_sig">نمی توانست فرمت پایگاه داده را تشخیص دهد.</string>
<string name="invalid_db_same_uuid">%1$s با همان UUID %2$s در حال حاضر وجود دارد.</string>
<string name="invalid_algorithm">الگوریتم اشتباه</string>
<string name="invalid_credentials">نمی توانست اعتبارنامه ها را بخواند.</string>
<string name="invalid_credentials">نمی توان اعتبار نامه را خواند.</string>
<string name="password">رمز عبور</string>
<string name="hint_pass">رمز عبور</string>
<string name="hint_length">طول</string>
@@ -138,11 +138,11 @@
<string name="error_save_database">نمی توانست پایگاه داده را ذخیره کند.</string>
<string name="error_create_database_file">قادر به ایجاد پایگاه داده با این رمز عبور و keyfile نیست.</string>
<string name="error_create_database">قادر به ایجاد فایل پایگاه داده نیست.</string>
<string name="error_copy_group_here">شما نمی توانید یک گروه را در اینجا کپی کنید.</string>
<string name="error_copy_group_here">در اینجا نمی توانید گروهی را کپی کنید.</string>
<string name="error_copy_entry_here">شما نمی توانید یک ورودی را در اینجا کپی کنید.</string>
<string name="error_move_entry_here">شما نمی توانید یک ورودی را به اینجا منتقل کنید.</string>
<string name="error_autofill_enable_service">قادر به فعال کردن سرویس پر کردن خودکار نبود.</string>
<string name="error_wrong_length">یک عدد کامل مثبت را در زمینه \"طول\" وارد کنید.</string>
<string name="error_wrong_length">در قسمت \"طول\" یک عدد صحیح مثبت وارد کنید.</string>
<string name="error_label_exists">این برچسب در حال حاضر وجود دارد.</string>
<string name="error_string_key">هر رشته باید یک نام فیلد داشته باشد.</string>
<string name="error_rounds_too_large">\"دور تحول\" بیش از حد بالا است. تنظیم به 2147483648.</string>
@@ -157,7 +157,7 @@
<string name="error_invalid_OTP">راز OTP نامعتبر.</string>
<string name="error_invalid_path">مطمئن شوید که مسیر درست است</string>
<string name="error_invalid_db">نمی توانست پایگاه داده را بخواند.</string>
<string name="error_file_not_create">نمی تواند پرونده ایجاد کند:</string>
<string name="error_file_not_create">نمی تواند پرونده ایجاد کند</string>
<string name="error_can_not_handle_uri">نمی تواند این URI در KeePassDX رسیدگی کند.</string>
<string name="error_arc4">رمز جریان Arcfour پشتیبانی نمی شود</string>
<string name="entry_user_name">نام کاربری</string>
@@ -194,12 +194,12 @@
<string name="database">پایگاه داده</string>
<string name="retrieving_db_key">بازیابی کلید پایگاه داده…</string>
<string name="select_to_copy">انتخاب برای کپی %1$s به کلیپ بورد</string>
<string name="content_description_keyboard_close_fields">زمینه های نزدیک</string>
<string name="content_description_keyboard_close_fields">بستن فیلد</string>
<string name="content_description_remove_from_list">حذف</string>
<string name="content_description_update_from_list">روز رسانی</string>
<string name="content_description_update_from_list">به روز رسانی</string>
<string name="content_description_remove_field">حذف فیلد</string>
<string name="entry_add_attachment">افزودن پیوست</string>
<string name="entry_add_field">اضافه کردن زمینه</string>
<string name="entry_add_field">اضافه کردن فیلد</string>
<string name="content_description_password_length">طول رمز عبور</string>
<string name="entry_password_generator">ژنراتور رمز عبور</string>
<string name="discard">دور انداختن</string>
@@ -224,12 +224,12 @@
<string name="clipboard_error_title">خطای کلیپ بورد</string>
<string name="clipboard_cleared">کلیپ بورد پاک شد</string>
<string name="allow">اجازه</string>
<string name="file_manager_install_description">یک مدیر پرونده که عمل Intent را می پذیرد ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT برای ایجاد، باز کردن و ذخیره فایل های پایگاه داده مورد نیاز است.</string>
<string name="file_manager_install_description">برای ایجاد ، بازکردن و ذخیره فایل های پایگاه داده ، یک مدیر فایل که عملیات ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT را می پذیرد ، مورد نیاز است.</string>
<string name="extended_ASCII">گسترش ASCII</string>
<string name="brackets">براکت</string>
<string name="application">برنامه</string>
<string name="app_timeout_summary">زمان بیکار قبل از قفل کردن پایگاه داده</string>
<string name="app_timeout">تایم آوت برنامه</string>
<string name="app_timeout">اتمام زمان</string>
<string name="key_derivation_function">تابع مشتق کلید</string>
<string name="encryption_algorithm">الگوریتم رمزنگاری</string>
<string name="encryption">رمزگذاری</string>
@@ -245,4 +245,83 @@
<string name="contribution">سهم</string>
<string name="contact">مخاطب</string>
<string name="filter">فیلتر</string>
<string name="allow_no_password_summary">در صورت عدم انتخاب اعتبار نامه ، ضربه زدن روی دکمه \"باز کردن\" امکان پذیر است</string>
<string name="keyboard_previous_database_credentials_summary">به طور خودکار به صفحه کلید قبلی در صفحه اعتبار نامه پایگاه داده برگردید</string>
<string name="keyboard_previous_database_credentials_title">صفحه اعتبار نامه پایگاه داده</string>
<string name="open_advanced_unlock_prompt_store_credential">برای ذخیره اعتبار نامه ، قفل پیشرفته را باز کنید</string>
<string name="open_advanced_unlock_prompt_unlock_database">برای باز کردن قفل پایگاه داده قفل پیشرفته را باز کنید</string>
<string name="biometric_security_update_required">به روز رسانی امنیتی بیومتریک مورد نیاز است.</string>
<string name="configure_biometric">هیچ بیومتریک یا اعتبار دستگاه ثبت نشده است.</string>
<string name="advanced_unlock_tap_delete">برای حذف کلیدهای پیشرفته باز کردن قفل ضربه بزنید</string>
<string name="advanced_unlock">باز کردن قفل پیشرفته</string>
<string name="content">محتوا</string>
<string name="warning_database_revoked">دسترسی به پرونده توسط مدیر فایل لغو شده ، پایگاه داده را ببندید و مجدداً از محل آن باز کنید.</string>
<string name="warning_database_info_changed_options">با ذخیره کردن پایگاه داده ، تغییرات خارجی را بازنویسی کنید یا با جدیدترین تغییرات آن را بارگیری کنید.</string>
<string name="warning_database_info_changed">اطلاعات موجود در فایل پایگاه داده شما خارج از برنامه دستکاری شده است.</string>
<string name="warning_empty_keyfile_explanation">محتوای فایل کلید هرگز نباید تغییر کند و در بهترین حالت باید حاوی داده های تصادفی تولید شده باشد.</string>
<string name="warning_empty_keyfile">توصیه نمی شود یک فایل کلید خالی اضافه کنید.</string>
<string name="warning_sure_remove_data">به هر حال این داده ها حذف شود؟</string>
<string name="warning_remove_unlinked_attachment">حذف داده های پیوند نشده ممکن است حجم پایگاه داده شما را کاهش دهد اما همچنین ممکن است داده های مورد استفاده برای افزونه های KeePass را حذف کند.</string>
<string name="warning_sure_add_file">به هر حال فایل اضافه شود؟</string>
<string name="warning_replace_file">بارگذاری این فایل جایگزین فایل موجود می شود.</string>
<string name="warning_file_too_big">قرار است پایگاه داده KeePass فقط حاوی فایلهای مفید کوچک (مانند فایلهای کلیدی PGP) باشد.
\n
\nممکن است پایگاه داده شما بسیار بزرگ شود و با این بارگذاری عملکرد را کاهش دهید.</string>
<string name="warning_empty_recycle_bin">آیا همه کلید ها از سطل بازیافت به طور دائم حذف می شوند؟</string>
<string name="error_export_app_properties">خطا در هنگام صادرات ویژگی های برنامه</string>
<string name="success_export_app_properties">ویژگی های برنامه صادر شد</string>
<string name="error_import_app_properties">خطا در هنگام وارد کردن ویژگی های برنامه</string>
<string name="success_import_app_properties">ویژگی های برنامه وارد شد</string>
<string name="description_app_properties">ویژگی های KeePassDX برای مدیریت تنظیمات برنامه</string>
<string name="export_app_properties_summary">یک فایل برای صادر کردن ویژگی های برنامه ایجاد کنید</string>
<string name="import_app_properties_summary">یک فایل برای وارد کردن ویژگی های برنامه انتخاب کنید</string>
<string name="import_app_properties_title">وارد کردن ویژگی های برنامه</string>
<string name="export_app_properties_title">صادر کردن ویژگی های برنامه</string>
<string name="registration_mode">حالت ثبت</string>
<string name="save_mode">حالت ذخیره</string>
<string name="search_mode">حالت جستجو</string>
<string name="menu_external_icon">نماد خارجی</string>
<string name="menu_keystore_remove_key">حذف کلید باز کردن پیشرفته</string>
<string name="menu_reload_database">بارگیری مجدد پایگاه داده</string>
<string name="error_start_database_action">هنگام انجام عملیات در پایگاه داده خطایی روی داد.</string>
<string name="error_remove_file">هنگام حذف داده های فایل خطایی روی داد.</string>
<string name="error_duplicate_file">داده های فایل قبلاً وجود دارد.</string>
<string name="error_upload_file">هنگام بارگذاری داده های فایل خطایی روی داد.</string>
<string name="error_file_to_big">فایلی که می خواهید بارگذاری کنید بسیار بزرگ است.</string>
<string name="error_rebuild_list">نمی توان لیست را به درستی بازسازی کرد.</string>
<string name="error_database_uri_null">URI پایگاه داده بازیابی نمی شود.</string>
<string name="error_field_name_already_exists">نام فیلد از قبل موجود است.</string>
<string name="error_registration_read_only">ذخیره یک مورد جدید در پایگاه داده \"فقط خواندنی\" مجاز نیست</string>
<string name="error_otp_type">نوع OTP موجود توسط این فرم شناخته نمی شود ، اعتبار آن ممکن است دیگر توکن را به درستی تولید نکند.</string>
<string name="id_card">ID Card</string>
<string name="card_verification_value">CVV</string>
<string name="error_word_reserved">این کلمه رزرو است و نمی توان از آن استفاده کرد.</string>
<string name="version">نسخه</string>
<string name="template">الگو</string>
<string name="standard">استاندارد</string>
<string name="membership">اعضا</string>
<string name="secure_note">یادداشت امن</string>
<string name="ssid">نام وای فای(SSID)</string>
<string name="bank_identifier_code">SWIFT / BIC</string>
<string name="international_bank_account_number">IBAN (شبا)</string>
<string name="bank_name">نام بانک</string>
<string name="bank">بانک</string>
<string name="account">حساب کاربری</string>
<string name="seed">بذر</string>
<string name="private_key">کلید خصوصی</string>
<string name="public_key">کلید عمومی</string>
<string name="token">توکن</string>
<string name="cryptocurrency">کیف پول ارز دیجیتال</string>
<string name="type">نوع</string>
<string name="email_address">آدرس ایمیل</string>
<string name="email">ایمیل</string>
<string name="date_of_issue">تاریخ صدور</string>
<string name="place_of_issue">محل صدور</string>
<string name="name">نام</string>
<string name="personal_identification_number">پین(رمز)</string>
<string name="number">عدد</string>
<string name="holder">صاحب</string>
<string name="template_group_name">الگو ها</string>
<string name="content_description_otp_information">اطلاعات رمز یکبار مصرف</string>
<string name="content_description_credentials_information">اطلاعات اعتبار نامه</string>
</resources>

View File

@@ -182,7 +182,7 @@
<string name="clipboard_warning">Si la suppression automatique du presse-papier échoue, supprimer son historique manuellement.</string>
<string name="lock">Verrouiller</string>
<string name="lock_database_screen_off_title">Verrouillage décran</string>
<string name="lock_database_screen_off_summary">Verrouille la base de données lorsque lécran est éteint</string>
<string name="lock_database_screen_off_summary">Verrouille la base de données après quelques secondes une fois lécran éteint</string>
<string name="advanced_unlock">Déverrouillage avancé</string>
<string name="biometric_unlock_enable_title">Déverrouillage biométrique</string>
<string name="biometric_unlock_enable_summary">Permet de numériser votre empreinte biométrique pour ouvrir la base de données</string>
@@ -487,7 +487,7 @@
<string name="database_data_remove_unlinked_attachments_title">Supprimer les données non-liées</string>
<string name="database_data_remove_unlinked_attachments_summary">Supprimer les pièces jointes contenues dans la base de données mais non-liées à une entrée</string>
<string name="data">Données</string>
<string name="show_uuid_summary">Affiche lUUID lié à une entrée</string>
<string name="show_uuid_summary">Affiche lUUID lié à une entrée ou un groupe</string>
<string name="show_uuid_title">Afficher lUUID</string>
<string name="autofill_read_only_save">Lenregistrement des données nest pas autorisé pour une base de données ouverte en lecture seule.</string>
<string name="autofill_ask_to_save_data_summary">Demande à enregistrer des données quand un formulaire est validé</string>
@@ -575,12 +575,12 @@
<string name="error_start_database_action">Une erreur s\'est produite lors de l\'exécution d\'une action sur la base de données.</string>
<string name="error_move_group_here">Vous ne pouvez pas déplacer un groupe ici.</string>
<string name="error_word_reserved">Ce mot est réservé et ne peut pas être utilisé.</string>
<string name="templates">Modèles</string>
<string name="templates_group_uuid_title">Groupe de modèles</string>
<string name="templates">Gabarits</string>
<string name="templates_group_uuid_title">Groupe de gabarits</string>
<string name="templates_group_enable_summary">Utiliser les modèles dynamiques pour remplir les champs d\'une entrée</string>
<string name="templates_group_enable_title">Utilisation des modèles</string>
<string name="templates_group_enable_title">Utilisation des gabarits</string>
<string name="version">Version</string>
<string name="template">Modèle</string>
<string name="template">Gabarit</string>
<string name="standard">Standard</string>
<string name="membership">Adhésion</string>
<string name="international_bank_account_number">IBAN</string>
@@ -602,6 +602,11 @@
<string name="name">Nom</string>
<string name="id_card">Carte d\'identité</string>
<string name="debit_credit_card">Carte de crédit</string>
<string name="template_group_name">Modèles</string>
<string name="template_group_name">Gabarits</string>
<string name="secure_note">Note sécurisée</string>
<string name="show_otp_token_summary">Affiche les jetons OTP dans la liste des entrées</string>
<string name="show_otp_token_title">Afficher le jeton OTP</string>
<string name="menu_external_icon">Icône externe</string>
<string name="autofill_manual_selection_summary">Afficher l\'option permettant à l\'utilisateur de sélectionner l\'entrée de la base de données</string>
<string name="autofill_manual_selection_title">Sélection manuelle</string>
</resources>

View File

@@ -188,7 +188,7 @@
<string name="clipboard">Međuspremnik</string>
<string name="clipboard_notifications_title">Obavijesti međuspremnika</string>
<string name="clipboard_warning">Ako automatsko brisanje međuspremnika ne uspije, izbriši njegovu povijest ručno.</string>
<string name="lock_database_screen_off_summary">Zaključaj bazu podataka kada je ekran ugašen</string>
<string name="lock_database_screen_off_summary">Zaključaj bazu podataka nakon par sekundi kad se ekran ugasi</string>
<string name="lock_database_back_root_title">Pritisni \'Natrag\' za zaključavanje</string>
<string name="advanced_unlock">Napredno otključavanje</string>
<string name="advanced_unlock_explanation_summary">Koristi napredno otključavanje za jednostavnije otvaranje baze podataka</string>
@@ -473,7 +473,7 @@
<string name="notification">Obavijest</string>
<string name="error_registration_read_only">Nije dopušteno spremati novi element u zaštićenoj bazi podataka</string>
<string name="autofill_read_only_save">Spremanje podataka nije dopušteno za bazu podataka koja je otvorena u zaštićenom stanju.</string>
<string name="show_uuid_summary">Prikazuje UUID povezan s unosom</string>
<string name="show_uuid_summary">Prikazuje UUID povezan s unosom ili grupom</string>
<string name="show_uuid_title">Prikaži UUID</string>
<string name="autofill_ask_to_save_data_summary">Zatraži spremanje podataka kad se obrazac provjeri</string>
<string name="autofill_ask_to_save_data_title">Zatraži spremanje podataka</string>
@@ -577,7 +577,7 @@
<string name="cryptocurrency">Novčanik za kriptovalute</string>
<string name="type">Vrsta</string>
<string name="ssid">SSID</string>
<string name="wireless">Wifi</string>
<string name="wireless">Wi-Fi</string>
<string name="email_address">E-mail adresa</string>
<string name="email">E-mail</string>
<string name="date_of_issue">Datum izdavanja</string>
@@ -590,4 +590,11 @@
<string name="holder">Vlasnik</string>
<string name="debit_credit_card">Debitna / kreditna kartica</string>
<string name="template_group_name">Predlošci</string>
<string name="autofill_manual_selection_summary">Opcija prikaza koja omogućuje korisniku biranje unosa baze podataka</string>
<string name="show_otp_token_summary">Prikazuje OTP tokene u popisu unosa</string>
<string name="show_otp_token_title">Prikaži OTP token</string>
<string name="autofill_manual_selection_title">Ručni odabir</string>
<string name="autofill_select_entry">Odaberi unos …</string>
<string name="menu_external_icon">Vanjska ikona</string>
<string name="seed">Tajna fraza</string>
</resources>

View File

@@ -24,12 +24,12 @@
<string name="add_entry">Bejegyzés hozzáadása</string>
<string name="add_group">Csoport hozzáadása</string>
<string name="encryption_algorithm">Titkosítási algoritmus</string>
<string name="app_timeout">Alkalmazás időkorlátja</string>
<string name="app_timeout">Időtúllépés</string>
<string name="app_timeout_summary">Tétlenség az adatbázis feloldása előtt</string>
<string name="application">Alkalmazás</string>
<string name="menu_app_settings">Alkalmazásbeállítások</string>
<string name="brackets">Zárójelek</string>
<string name="file_manager_install_description">Egy fájlkezelő, amely fogadja az ACTION_CREATE_DOCUMENT és ACTION_OPEN_DOCUMENT Intenteket, melyek az adatbázisfájlok létrehozásához, megnyitásához és mentéséhez szükségesek.</string>
<string name="file_manager_install_description">Az adatbázisfájlok létrehozásához, megnyitásához és mentéséhez szükség van egy fájlkezelőre, amely képes fogadni az ACTION_CREATE_DOCUMENT és ACTION_OPEN_DOCUMENT Intenteket.</string>
<string name="clipboard_cleared">Vágólap törölve</string>
<string name="clipboard_error_title">Vágólap hiba</string>
<string name="clipboard_error">Egyes eszközök nem engedik, hogy az alkalmazások használják a vágólapot.</string>
@@ -401,7 +401,7 @@
<string name="error_otp_period">Az időtartamnak %1$d és %2$d másodperc között kell lennie.</string>
<string name="error_otp_counter">A számlálónak %1$d és %2$d között kell lennie.</string>
<string name="error_otp_secret_key">A titkos kulcsnak Base32 formátumban kell lennie.</string>
<string name="error_copy_group_here">Csoport ide nem másolható.</string>
<string name="error_copy_group_here">Ide nem másolhat csoportot.</string>
<string name="error_disallow_no_credentials">Legalább egy hitelesíti módot be kell állítani.</string>
<string name="error_invalid_OTP">Érvénytelen OTP titok.</string>
<string name="entry_otp">OTP</string>
@@ -470,4 +470,51 @@
<string name="error_string_type">A szöveg nem egyezik a kért elemmel.</string>
<string name="content_description_credentials_information">Hitelesítő adatok információi</string>
<string name="content_description_add_item">Elem hozzáadása</string>
<string name="export_app_properties_summary">Fájl létrehozása az alkalmazástulajdonságok exportálásához</string>
<string name="export_app_properties_title">Alkalmazástulajdonságok exportálása</string>
<string name="import_app_properties_summary">Válasszon fájlt az alkalmazástulajdonságok importálásához</string>
<string name="import_app_properties_title">Alkalmazástulajdonságok importálása</string>
<string name="menu_external_icon">Külső ikon</string>
<string name="menu_keystore_remove_key">Speciális feloldási kulcs törlése</string>
<string name="menu_reload_database">Adatbázis újratöltése</string>
<string name="error_start_database_action">Hiba történt az adatbázison végzett művelet során.</string>
<string name="error_remove_file">Hiba történt a fájladatok eltávolítása során.</string>
<string name="error_duplicate_file">A fájladatok már léteznek.</string>
<string name="error_file_to_big">A feltöltendő fájl túl nagy.</string>
<string name="error_upload_file">Hiba történt a fájladatok feltöltése során.</string>
<string name="error_rebuild_list">A lista újbóli összeállítása sikertelen.</string>
<string name="error_database_uri_null">Az adatbázis URI nem kérhető le.</string>
<string name="error_field_name_already_exists">A mezőnév már létezik.</string>
<string name="error_otp_type">A meglévő OTP típus nem ismert ebben a formában, a kiértékelése nem biztos, hogy helyes tokent fog előállítani.</string>
<string name="error_word_reserved">Ez egy foglalt szó, és nem használható.</string>
<string name="version">Verzió</string>
<string name="template">Sablon</string>
<string name="standard">Szabványos</string>
<string name="membership">Tagság</string>
<string name="secure_note">Biztonságos jegyzet</string>
<string name="debit_credit_card">Bank- / hitelkártya</string>
<string name="seed">Kezdőérték</string>
<string name="international_bank_account_number">IBAN</string>
<string name="bank_identifier_code">SWIFT / BIC</string>
<string name="bank_name">Bank neve</string>
<string name="bank">Bank</string>
<string name="account">Fiók</string>
<string name="private_key">Privát kulcs</string>
<string name="public_key">Nyilvános kulcs</string>
<string name="token">Token</string>
<string name="cryptocurrency">Kriptopénztárca</string>
<string name="type">Típus</string>
<string name="ssid">SSID</string>
<string name="email_address">E-mail-cím</string>
<string name="email">E-mail</string>
<string name="date_of_issue">Kiállítás ideje</string>
<string name="place_of_issue">Kiállítás helye</string>
<string name="name">Név</string>
<string name="id_card">Személyigazolvány</string>
<string name="personal_identification_number">PIN-kód</string>
<string name="card_verification_value">Ellenőrzőszám</string>
<string name="number">Szám</string>
<string name="holder">Tulajdonos</string>
<string name="template_group_name">Sablonok</string>
<string name="content_description_otp_information">Egyszer használatos jelszó információ</string>
</resources>

View File

@@ -420,14 +420,14 @@
<string name="error_start_database_action">Timbul galat ketika melaksanakan sebuah aksi di basisdata.</string>
<string name="error_move_group_here">Anda tidak bisa memindahkan sebuah grup ke sini.</string>
<string name="device_credential_unlock_enable_title">Membuka kredensial perangkat</string>
<string name="biometric_unlock_enable_summary">Memperbolehkan pengguna memindai biometrik pengguna untuk membuka database”</string>
<string name="biometric_unlock_enable_summary">Memperbolehkan Anda memindai biometrik Anda untuk membuka basis data</string>
<string name="biometric_unlock_enable_title">Membuka kunci biometrik</string>
<string name="advanced_unlock_explanation_summary">Gunakan buka kunci lanjutan untuk membuka database dengan lebih mudah</string>
<string name="no_credentials_stored">Basis data ini belum menyimpan kredensial.</string>
<string name="advanced_unlock_not_recognized">Tidak dapat mengenali cetakan buka kunci lanjutan</string>
<string name="advanced_unlock_invalid_key">Tidak dapat membaca kunci pembuka lanjutan. Harap hapus dan ulangi prosedur pengenalan buka kunci.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Ekstrak kredensial basis data dengan membuka data lanjutan</string>
<string name="error_word_reserved">Kata ini telah dipakai dan tidak bisa digunakan</string>
<string name="advanced_unlock_prompt_extract_credential_message">Ekstrak kredensial basis data dengan pembuka kunci data lanjutan</string>
<string name="error_word_reserved">Kata ini telah dipakai dan tidak bisa digunakan.</string>
<string name="credential_before_click_advanced_unlock_button">Ketik kata sandi, lalu klik tombol ini.</string>
<string name="autofill_service_name">Isi formulir KeePassDX otomatis</string>
<string name="autofill_preference_title">Pengaturan isi otomatis</string>
@@ -440,5 +440,19 @@
<string name="biometric">Biometrik</string>
<string name="menu_appearance_settings">Tampilan</string>
<string name="database_history">Riwayat</string>
<string name="encrypted_value_stored">Enkripsi password telah disimpan.</string>
<string name="encrypted_value_stored">Enkripsi kata sandi disimpan</string>
<string name="international_bank_account_number">NRBI (IBAN)</string>
<string name="bank_name">Nama bank</string>
<string name="name">Nama</string>
<string name="debit_credit_card">Kartu Debit/Kredit</string>
<string name="type">Tipe</string>
<string name="version">Versi</string>
<string name="standard">Standar</string>
<string name="bank">Bank</string>
<string name="account">Akun</string>
<string name="cryptocurrency">Dompet kripto</string>
<string name="number">Nomor</string>
<string name="public_key">Kunci publik</string>
<string name="private_key">Kunci pribadi</string>
<string name="membership">Keanggotaan</string>
</resources>

View File

@@ -195,7 +195,7 @@
<string name="clipboard_warning">Se l\'eliminazione automatica degli appunti fallisce, cancellali manualmente.</string>
<string name="lock">Blocca</string>
<string name="lock_database_screen_off_title">Blocco schermo</string>
<string name="lock_database_screen_off_summary">Blocca il database quando lo schermo è spento</string>
<string name="lock_database_screen_off_summary">Blocca il database dopo alcuni secondi quando lo schermo è spento</string>
<string name="advanced_unlock">Impronta digitale</string>
<string name="biometric_unlock_enable_title">Scansione di impronte</string>
<string name="biometric_unlock_enable_summary">Consente la scansione biometrica per aprire il database</string>
@@ -289,7 +289,7 @@
<string name="list_entries_show_username_title">Mostra nomi utente</string>
<string name="list_entries_show_username_summary">Mostra i nomi utente negli elenchi</string>
<string name="clipboard">Appunti</string>
<string name="build_label">Costruzione %1$s</string>
<string name="build_label">Build %1$s</string>
<string name="keyboard_name">Magitastiera</string>
<string name="keyboard_label">Magitastiera (KeePassDX)</string>
<string name="keyboard_setting_label">Impostazioni Magitastiera</string>
@@ -346,7 +346,7 @@
<string name="menu_advanced_unlock_settings">Sblocco avanzato</string>
<string name="entry_history">Cronologia</string>
<string name="entry_setup_otp">Imposta password usa e getta</string>
<string name="otp_type">Tipo di password OTP</string>
<string name="otp_type">Tipo di OTP</string>
<string name="otp_secret">Segreto</string>
<string name="otp_period">Periodo (secondi)</string>
<string name="otp_counter">Contatore</string>
@@ -374,7 +374,7 @@
<string name="auto_focus_search_title">Ricerca rapida</string>
<string name="menu_delete_entry_history">Cancella cronologia</string>
<string name="menu_restore_entry_history">Ripristina cronologia</string>
<string name="html_about_contribution">Per poter &lt;strong&gt;mantenere la nostra libertà&lt;/strong&gt;, &lt;strong&gt;risolvere bug&lt;/strong&gt;, &lt;strong&gt;aggiungere funzionalità&lt;/strong&gt; ed &lt;strong&gt;essere sempre attivi&lt;/strong&gt;, facciamo affidamento sul tuo &lt;strong&gt;contributo&lt;/strong&gt;.</string>
<string name="html_about_contribution">Per poter <strong>mantenere la nostra libertà</strong>, <strong>correggere errori</strong>, <strong>aggiungere funzionalità</strong> ed <strong>essere sempre attivi</strong>, facciamo affidamento sul tuo <strong>contributo</strong>.</string>
<string name="contact">Contatto</string>
<string name="keystore_not_accessible">Il keystore non è inizializzato correttamente.</string>
<string name="menu_master_key_settings">Impostazioni della chiave principale</string>
@@ -476,7 +476,7 @@
\n
\nIl tuo database può diventare molto grande e ridurre le prestazioni con questo caricamento.</string>
<string name="content_description_credentials_information">Info credenziali</string>
<string name="show_uuid_summary">Visualizza l\'UUID collegato a una voce</string>
<string name="show_uuid_summary">Visualizza l\'UUID collegato a una voce o a un gruppo</string>
<string name="show_uuid_title">Mostra UUID</string>
<string name="autofill_read_only_save">Il salvataggio dei dati non è consentito per un database aperto in sola lettura.</string>
<string name="autofill_ask_to_save_data_summary">Chiedi di salvare i dati quando un modulo viene convalidato</string>
@@ -586,7 +586,7 @@
<string name="cryptocurrency">Portafoglio di criptovaluta</string>
<string name="type">Tipo</string>
<string name="ssid">SSID</string>
<string name="wireless">Wifi</string>
<string name="wireless">Wi-Fi</string>
<string name="email_address">Indirizzo email</string>
<string name="email">Email</string>
<string name="date_of_issue">Data di emissione</string>
@@ -599,4 +599,10 @@
<string name="holder">Titolare</string>
<string name="debit_credit_card">Carta di debito / credito</string>
<string name="template_group_name">Modelli</string>
<string name="menu_external_icon">Icona esterna</string>
<string name="show_otp_token_summary">Mostra i token OTP nella lista delle voci</string>
<string name="show_otp_token_title">Mostra token OTP</string>
<string name="autofill_manual_selection_summary">Mostra l\'opzione che permette all\'utente di scegliere la voce nel database</string>
<string name="autofill_manual_selection_title">Selezione manuale</string>
<string name="autofill_select_entry">Seleziona voce…</string>
</resources>

View File

@@ -78,7 +78,7 @@
<string name="html_about_contribution">&lt;strong&gt;自由を維持し&lt;/strong&gt;&lt;strong&gt;バグを修正し&lt;/strong&gt;&lt;strong&gt;機能を追加し&lt;/strong&gt;、そして&lt;strong&gt;活発に開発し続ける&lt;/strong&gt;ために、私たちはあなたの&lt;strong&gt;貢献&lt;/strong&gt;に期待しています。</string>
<string name="entry_accessed">最終アクセス</string>
<string name="entry_cancel">キャンセル</string>
<string name="entry_notes">備考</string>
<string name="entry_notes">メモ</string>
<string name="entry_confpassword">パスワードを確認</string>
<string name="entry_created">作成日時</string>
<string name="entry_expires">有効期限</string>
@@ -120,9 +120,9 @@
<string name="error_label_exists">このラベルはすでに存在します。</string>
<string name="error_wrong_length">[長さ] フィールドには正の整数を入力してください。</string>
<string name="error_autofill_enable_service">自動入力サービスを有効にできませんでした。</string>
<string name="error_move_entry_here">ここではエントリーを移動することはできません。</string>
<string name="error_copy_entry_here">ここではエントリーをコピーすることはできません。</string>
<string name="error_copy_group_here">ここではグループをコピーすることはできません。</string>
<string name="error_move_entry_here">エントリーをここに移動できません。</string>
<string name="error_copy_entry_here">エントリーをここにコピーできません。</string>
<string name="error_copy_group_here">グループをここにコピーできません。</string>
<string name="error_create_database">データベース ファイルを作成できません。</string>
<string name="error_create_database_file">このパスワードとキーファイルではデータベースを作成できません。</string>
<string name="error_save_database">データベースを保存できませんでした。</string>
@@ -180,7 +180,7 @@
<string name="menu_cancel">キャンセル</string>
<string name="menu_hide_password">パスワードを非表示にする</string>
<string name="menu_lock">データベースをロック</string>
<string name="menu_save_database">データベースを保存</string>
<string name="menu_save_database">保存する</string>
<string name="menu_open">開く</string>
<string name="menu_search">検索</string>
<string name="menu_showpass">パスワードを表示</string>
@@ -223,7 +223,7 @@
<string name="encryption_explanation">すべてのデータで使用するデータベース暗号化アルゴリズムです。</string>
<string name="kdf_explanation">暗号化アルゴリズム用の鍵を生成するために、マスターキーはランダムなソルト付き鍵導出関数を使用して変換されます。</string>
<string name="rounds">変換ラウンド</string>
<string name="rounds_explanation">変換ラウンドを増やすことでブルート フォース攻撃に対する保護が強化されますが、読み込みと保存が本当に遅くなる可能性があります。</string>
<string name="rounds_explanation">変換ラウンドを増やすことでブルートフォース攻撃に対する保護が強化されますが、読み込みと保存がとても遅くなる可能性があります。</string>
<string name="memory_usage">メモリ使用量</string>
<string name="memory_usage_explanation">鍵導出関数が使用するメモリの量です。</string>
<string name="parallelism">並列処理</string>
@@ -241,7 +241,7 @@
<string name="sort_db">並べ替えなし</string>
<string name="sort_title">タイトル</string>
<string name="sort_username">ユーザー名</string>
<string name="sort_creation_time">作成日</string>
<string name="sort_creation_time">作成日</string>
<string name="sort_last_modify_time">変更日時</string>
<string name="sort_last_access_time">最終アクセス</string>
<string name="special">特殊文字</string>
@@ -294,7 +294,7 @@
<string name="clipboard_warning">クリップボードの自動削除に失敗した場合は、手動でその履歴を削除してください。</string>
<string name="lock">ロック</string>
<string name="lock_database_screen_off_title">画面ロック</string>
<string name="lock_database_screen_off_summary">画面がオフのとき、データベースをロックします</string>
<string name="lock_database_screen_off_summary">画面がオフになった数秒後に、データベースをロックします</string>
<string name="lock_database_back_root_title">[戻る] を押してロック</string>
<string name="lock_database_back_root_summary">ユーザーがルート画面上で戻るボタンをタップしたとき、データベースをロックします</string>
<string name="lock_database_show_button_title">ロックボタンを表示</string>
@@ -362,13 +362,13 @@
<string name="keyboard_setting_label">Magikeyboard の設定</string>
<string name="keyboard_entry_category">エントリー</string>
<string name="keyboard_selection_entry_title">エントリーの選択</string>
<string name="keyboard_selection_entry_summary">エントリーを開いているとき、Magikeyboard に入力フィールドを表示します</string>
<string name="keyboard_selection_entry_summary">KeePassDX のエントリーを開いているとき、Magikeyboard にそのエントリーを格納します</string>
<string name="keyboard_notification_entry_title">通知情報</string>
<string name="keyboard_notification_entry_summary">エントリーが利用可能なとき、通知を表示します</string>
<string name="keyboard_search_share_title">共有情報の検索</string>
<string name="keyboard_search_share_summary">共有情報を自動的に検索して、結果をキーボードに格納します</string>
<string name="keyboard_search_share_summary">URL を KeePassDX に共有するとそのドメインを使ってエントリーを検索します</string>
<string name="keyboard_save_search_info_title">共有情報の保存</string>
<string name="keyboard_save_search_info_summary">手動でエントリー選択たとき、共有情報の保存を試みます</string>
<string name="keyboard_save_search_info_summary">KeePassDX に URL を共有して、エントリー選択されたとき、さらなる使用のためそのエントリーを記憶しておきます</string>
<string name="keyboard_notification_entry_clear_close_title">データベースを閉じて消去</string>
<string name="keyboard_notification_entry_clear_close_summary">通知を閉じるとき、データベースも閉じます</string>
<string name="keyboard_entry_timeout_title">タイムアウト</string>
@@ -489,7 +489,7 @@
<string name="hide_expired_entries_title">有効期限切れのエントリーを非表示にする</string>
<string name="hide_expired_entries_summary">有効期限切れのエントリーは非表示になります</string>
<string name="show_uuid_title">UUID を表示</string>
<string name="show_uuid_summary">エントリーにリンクされた UUID を表示します</string>
<string name="show_uuid_summary">エントリーやグループにリンクされた UUID を表示します</string>
<string name="autofill_read_only_save">データの保存は読み取り専用として開かれたデータベースでは許可されていません。</string>
<string name="save_mode">保存モード</string>
<string name="search_mode">検索モード</string>
@@ -531,7 +531,7 @@
<string name="warning_database_revoked">ファイルへのアクセス権がファイル マネージャーによって取り消されました。データベースを閉じて、ファイルの場所から再度開いてください。</string>
<string name="warning_database_info_changed_options">データベースを保存して外部の変更を上書きするか、再度読み込んで最新の変更を反映させてください。</string>
<string name="warning_database_info_changed">データベース ファイルに含まれる情報は、アプリの外部で変更されています。</string>
<string name="menu_reload_database">データベースを再度読み込む</string>
<string name="menu_reload_database">再度読み込む</string>
<string name="error_rebuild_list">リストを正しく再構築できません。</string>
<string name="unit_gibibyte">GiB</string>
<string name="unit_mebibyte">MiB</string>
@@ -561,13 +561,13 @@
<string name="error_start_database_action">データベースに対するアクションの実行中にエラーが発生しました。</string>
<string name="error_word_reserved">この単語は予約語のため使用できません。</string>
<string name="error_move_group_here">グループをここに移動できません。</string>
<string name="templates_group_enable_title">テンプレートの使用方法</string>
<string name="templates_group_enable_summary">エントリのフィールドの記入にダイナミックテンプレートを用います</string>
<string name="templates_group_enable_title">テンプレートの使用</string>
<string name="templates_group_enable_summary">エントリのフィールドへの入力に動的テンプレートを使用します</string>
<string name="ssid">SSID</string>
<string name="templates">Templates</string>
<string name="templates">テンプレート</string>
<string name="secure_note">セキュアメモ</string>
<string name="bank_identifier_code">SWIFTコード</string>
<string name="international_bank_account_number"></string>
<string name="bank_identifier_code">SWIFT / BIC</string>
<string name="international_bank_account_number">IBAN</string>
<string name="bank_name">銀行名</string>
<string name="bank">銀行</string>
<string name="account">口座</string>
@@ -581,11 +581,23 @@
<string name="date_of_issue">発行日</string>
<string name="place_of_issue">発行地</string>
<string name="name">名前</string>
<string name="id_card">IDカード</string>
<string name="id_card">ID カード</string>
<string name="personal_identification_number">PIN</string>
<string name="card_verification_value">CVV</string>
<string name="number">カード番号</string>
<string name="holder">所有者</string>
<string name="template_group_name">Templates</string>
<string name="debit_credit_card">クレジットカード/デビットカード</string>
<string name="number">番号</string>
<string name="holder">名義人</string>
<string name="template_group_name">テンプレート</string>
<string name="debit_credit_card">デビット / クレジットカード</string>
<string name="show_otp_token_summary">エントリーの一覧に OTP トークンを表示します</string>
<string name="show_otp_token_title">OTP トークンを表示</string>
<string name="version">バージョン</string>
<string name="standard">標準</string>
<string name="membership">メンバーシップ</string>
<string name="type">種類</string>
<string name="autofill_select_entry">エントリーを選択する…</string>
<string name="autofill_manual_selection_title">手動選択</string>
<string name="autofill_manual_selection_summary">ユーザーがデータベースのエントリーを選択できるようにオプションを表示します</string>
<string name="templates_group_uuid_title">テンプレート グループ</string>
<string name="menu_external_icon">外部アイコン</string>
<string name="template">テンプレート</string>
</resources>

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