Compare commits

..

158 Commits
3.3.2 ... 3.4.2

Author SHA1 Message Date
J-Jamet
1b31a46fb7 Merge branch 'release/3.4.2' 2022-04-15 13:42:43 +02:00
J-Jamet
87f19c74fc Add clean in fastlane 2022-04-15 13:23:25 +02:00
J-Jamet
bd157a9724 Fix small UI color 2022-04-15 13:18:11 +02:00
J-Jamet
5a327eb0db Catch reset app timeout for unexpected exceptions 2022-04-15 13:00:41 +02:00
J-Jamet
4b9c0b0109 Fix navigation bar color in dark mode 2022-04-15 12:54:10 +02:00
J-Jamet
df6b75cdbb Fix color 2022-04-15 12:47:50 +02:00
J-Jamet
0b4f8c122b Fix color 2022-04-15 12:46:37 +02:00
J-Jamet
2a87eaf3e5 Upgrade to 3.4.2 and fix service parameter and workflow 2022-04-15 12:21:14 +02:00
J-Jamet
c52266f5cf Merge branch 'master' of github.com:Kunzisoft/KeePassDX 2022-04-14 19:28:49 +02:00
J-Jamet
3b21f8add2 Merge tag '3.4.1' into develop
3.4.1
2022-04-14 19:28:12 +02:00
J-Jamet
6574bd10a0 Merge branch 'release/3.4.1' 2022-04-14 19:28:05 +02:00
J-Jamet
23f3335988 Update version code for deployment 2022-04-14 19:17:49 +02:00
J-Jamet
a5d7f33c82 Fix unexpected lock of the app #1294 2022-04-14 19:11:33 +02:00
J-Jamet
3782c4dac0 Fix styles 2022-04-14 17:17:38 +02:00
J-Jamet
1fc02fd2fe Small UI changes 2022-04-14 15:16:32 +02:00
J-Jamet
cc347c1dbe Update strings 2022-04-14 15:07:32 +02:00
J-Jamet
79ff20eb18 Remove irrelevant Autofill autosearch setting 2022-04-14 14:55:00 +02:00
J-Jamet
e6e8a447da Clear focus in autosearch and update CHANGELOG 2022-04-14 14:32:34 +02:00
J-Jamet
233f0c5bdb Fix another entry in selection mode with Magikeyboard #1293 2022-04-14 14:12:35 +02:00
J-Jamet
9ed4271a14 Fix search mode with Magikeyboard #1292 2022-04-14 12:50:33 +02:00
J-Jamet
470c0b6b43 Update README description 2022-04-14 11:54:16 +02:00
J-Jamet
afa8ae42b9 Upgrade gradle to 3.4.1 2022-04-14 11:45:55 +02:00
Jérémy JAMET
63d426503f Update FUNDING.yml
Fix issuehunt
2022-04-12 20:43:48 +02:00
Jérémy JAMET
ffb7f80b26 Create FUNDING.yml 2022-04-12 20:42:35 +02:00
J-Jamet
63f8826fd8 Merge branch 'master' into develop 2022-04-12 20:08:21 +02:00
J-Jamet
ef836e8b84 Change screenshots 2022-04-12 20:08:10 +02:00
J-Jamet
abc1c43a51 Merge tag '3.4.0' into develop
3.4.0
2022-04-12 19:45:17 +02:00
J-Jamet
6b54dd9e0d Merge branch 'release/3.4.0' 2022-04-12 19:45:10 +02:00
J-Jamet
1f54e7752d Disable keyboard timeout by default 2022-04-12 15:04:12 +02:00
J-Jamet
6ac941f276 Upgrade to 3.4.0 2022-04-12 14:58:13 +02:00
J-Jamet
6c308483f7 Upgrade gradle and kotlin 2022-04-12 12:31:18 +02:00
J-Jamet
9d25fb74ec Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-04-12 12:14:23 +02:00
J-Jamet
d217b52744 Upgrade to 3.4.0_beta02
Fix #1282 with workaround
2022-04-12 12:12:05 +02:00
J-Jamet
319da4b174 Rollback openOutputStream in "rwt" 2022-04-12 11:32:49 +02:00
nautilusx
9bee467942 Translated using Weblate (German)
Currently translated at 99.6% (611 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-12 07:46:27 +02:00
VfBFan
44ac70fc97 Translated using Weblate (German)
Currently translated at 99.6% (611 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-12 07:46:26 +02:00
J-Jamet
d897611d62 Change main screenshot 2022-04-10 17:21:35 +02:00
J-Jamet
c2ae251e73 Change screenshots 2022-04-10 17:10:01 +02:00
J-Jamet
35ad285864 Fix education screen 2022-04-09 16:52:25 +02:00
J-Jamet
97bdae21eb Small change 2022-04-09 16:37:11 +02:00
J-Jamet
d6dc6e43c7 Update strings 2022-04-09 16:35:20 +02:00
Oğuz Ersen
01e6e530d5 Translated using Weblate (Turkish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-04-09 16:28:35 +02:00
Kunzisoft
9ec0178beb Translated using Weblate (French)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-04-09 16:28:35 +02:00
Hosted Weblate
d66f2f6d24 Merge branch 'origin/develop' into Weblate. 2022-04-09 16:15:08 +02:00
Milo Ivir
79cd4004cc Translated using Weblate (Croatian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-04-09 16:15:08 +02:00
VfBFan
991243e2df Translated using Weblate (German)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-09 16:15:07 +02:00
J-Jamet
b91cf11d86 Upgrade to 3.4 beta01 2022-04-09 16:14:15 +02:00
J-Jamet
d182ec09fa Upgrade CHANGELOG 2022-04-09 16:12:57 +02:00
J-Jamet
8641822358 Fix small bugs 2022-04-09 16:03:12 +02:00
J-Jamet
9665cbb428 Fix small visual bug 2022-04-08 18:37:06 +02:00
J-Jamet
a280dfaf3b Better magikeyboard views 2022-04-08 18:23:02 +02:00
J-Jamet
3e56521ea8 Empty Magikeyboard memory when the main service is killed #1261 2022-04-08 16:53:03 +02:00
SC
b205230ea9 Translated using Weblate (Portuguese)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-04-08 09:31:01 +02:00
Oğuz Ersen
51645ab126 Translated using Weblate (Turkish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-04-08 09:31:00 +02:00
Eric
5d04897e75 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-08 09:30:59 +02:00
Ihor Hordiichuk
1ac0ea5cc6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-04-08 09:30:59 +02:00
solokot
a07e8b51e5 Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-08 09:30:58 +02:00
Vitor Henrique
a81f0238f4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-04-08 09:30:57 +02:00
Matthaiks
2b81eb8ec7 Translated using Weblate (Polish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-04-08 09:30:56 +02:00
Retrial
e5eb642781 Translated using Weblate (Greek)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-04-08 09:30:56 +02:00
VfBFan
a4cbe25733 Translated using Weblate (German)
Currently translated at 99.8% (612 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-08 09:30:55 +02:00
J-Jamet
2042c85b22 Ask confirmation to lock if changes without save #970 2022-04-07 18:06:47 +02:00
J-Jamet
3149f8745c Fix passkey view 2022-04-07 17:39:46 +02:00
J-Jamet
15b9f1616f Fix keyboard search and selection 2022-04-07 14:28:45 +02:00
J-Jamet
94c02b7288 Fix selection mode instance issue 2022-04-07 13:26:07 +02:00
J-Jamet
7e70b59a59 Fix selection mode instance issue 2022-04-07 13:22:19 +02:00
Hosted Weblate
2c7f5e41ed Merge branch 'origin/develop' into Weblate. 2022-04-05 18:00:37 +02:00
J-Jamet
108b8df280 Fix CHANGELOG issue number 2022-04-05 15:32:42 +02:00
J-Jamet
553098f9be Manage package name from Magikeyboard #1010 2022-04-05 15:28:15 +02:00
J-Jamet
131eb78407 Setting to change the keyboard during a search #1254 2022-04-05 13:38:36 +02:00
J-Jamet
f956a279a5 Save search parameters #1254 2022-04-05 13:07:36 +02:00
J-Jamet
7150686b92 Fix search parameter parcelable 2022-04-05 11:59:02 +02:00
J-Jamet
4b1fb2c173 Upgrade CHANGELOG 2022-04-05 11:28:58 +02:00
J-Jamet
94464bf608 Save pass generator options in app.properties 2022-04-04 15:30:57 +02:00
J-Jamet
2faa88784a Update CHANGELOG 2022-04-04 15:09:32 +02:00
J-Jamet
e6607b53d8 Better search implementation #175 2022-04-04 15:08:11 +02:00
J-Jamet
3f6a6c864a Show TOTP in 3-digit grouping #1270 2022-04-04 13:25:01 +02:00
J-Jamet
30a578257d Update CHANGELOG 2022-04-04 13:07:38 +02:00
J-Jamet
8411134adf Save files with "wt" #1282 2022-04-04 12:21:35 +02:00
J-Jamet
f86a5d1a19 Fix small bug 2022-04-01 19:19:47 +02:00
J-Jamet
be72492537 Merge branch 'feature/Mnemonics' into develop 2022-04-01 19:14:07 +02:00
J-Jamet
76f9e8ec6e Generate passphrase #218 2022-04-01 19:08:50 +02:00
Milo Ivir
8fb1c44e58 Translated using Weblate (Croatian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-04-01 13:08:19 +02:00
J-Jamet
f607b35cf3 Change max slider to 64 2022-04-01 11:01:00 +02:00
J-Jamet
0e56bec35a Key generator as tabs 2022-04-01 10:51:38 +02:00
Hosted Weblate
c890d10114 Merge branch 'origin/develop' into Weblate. 2022-03-31 12:09:22 +02:00
Milo Ivir
dee2fe5ce7 Translated using Weblate (Croatian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-03-31 12:09:21 +02:00
J-Jamet
4a4d767bce Manage setting to hide password #696 2022-03-29 17:18:49 +02:00
J-Jamet
d57e0cf601 Add setting to colorize password 2022-03-29 16:41:23 +02:00
J-Jamet
7fa141dd1b Merge branch 'develop' into feature/Mnemonics 2022-03-29 16:01:50 +02:00
J-Jamet
c261a0cbca Smaller tab buttons 2022-03-29 16:01:20 +02:00
J-Jamet
66661cbd49 Add bold and change password colors 2022-03-29 15:54:37 +02:00
J-Jamet
100c126c3d Update CHANGELOG 2022-03-29 15:32:51 +02:00
J-Jamet
d466e3077d Add color for special password chars #454 2022-03-29 15:32:29 +02:00
J-Jamet
24587dc34e Small refactorization 2022-03-29 12:42:53 +02:00
J-Jamet
32cc57dd03 Add editable chars fields #539 2022-03-28 22:14:24 +02:00
J-Jamet
a55488846b Add advanced password filters #1052 2022-03-28 21:26:26 +02:00
J-Jamet
dcf61fd4e2 Update CHANGELOG 2022-03-28 16:34:37 +02:00
J-Jamet
5bf998468a Save password preferences dynamically 2022-03-28 16:24:25 +02:00
J-Jamet
01c9625c59 Dynamic filter change and fix bugs 2022-03-28 15:08:56 +02:00
Dixon Huang
772c378922 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2022-03-28 13:09:14 +02:00
J-Jamet
ee50a91379 Move entropy to the top 2022-03-27 21:40:13 +02:00
J-Jamet
9cfda3bad8 Fix small bugs 2022-03-27 21:33:44 +02:00
J-Jamet
aa19b08bd9 Fix password entropy and add chips 2022-03-27 20:41:51 +02:00
J-Jamet
87f69bb7e2 Entropy #869 2022-03-27 18:56:12 +02:00
J-Jamet
41c0aeedbe Show visual password strength indicator #631 2022-03-27 17:22:52 +02:00
Eric
3cbe53d76f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-03-27 00:27:21 +01:00
Ihor Hordiichuk
aed60d6c1e Translated using Weblate (Ukrainian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-03-27 00:27:21 +01:00
Stephan Paternotte
be7d35490d Translated using Weblate (Dutch)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-03-27 00:27:20 +01:00
VfBFan
d0ea997c63 Translated using Weblate (German)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-03-27 00:27:20 +01:00
J-Jamet
1fecffeba2 Merge branch 'master' of github.com:Kunzisoft/KeePassDX 2022-03-26 16:28:39 +01:00
J-Jamet
76319a56a2 Merge tag '3.3.3' into develop
3.3.3
2022-03-26 16:27:23 +01:00
J-Jamet
1d71de7031 Merge branch 'release/3.3.3' 2022-03-26 16:27:15 +01:00
Oğuz Ersen
e9a1cfea11 Translated using Weblate (Turkish)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-03-26 04:15:30 +01:00
solokot
9115856d19 Translated using Weblate (Russian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-03-26 04:15:30 +01:00
Matthaiks
8c45266c18 Translated using Weblate (Polish)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-03-26 04:15:29 +01:00
Retrial
6b4130df89 Translated using Weblate (Greek)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-03-26 04:15:29 +01:00
J-Jamet
e3e10e7dfa Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-03-25 16:03:08 +01:00
Kunzisoft
cbf900004d Translated using Weblate (French)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-03-25 16:03:01 +01:00
J-Jamet
51b36dc460 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-03-25 16:02:20 +01:00
Hosted Weblate
9f8016afe2 Merge branch 'origin/develop' into Weblate. 2022-03-25 15:56:17 +01:00
abidin toumi
d5a36db50a Translated using Weblate (Arabic)
Currently translated at 77.0% (457 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-03-25 15:56:17 +01:00
J-Jamet
ecd458d8d0 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-03-25 15:52:56 +01:00
J-Jamet
9088297c41 Fix small bugs 2022-03-25 15:48:33 +01:00
J-Jamet
5282deb088 Tabs to show main and advanced content separately 2022-03-25 15:32:48 +01:00
J-Jamet
75d661f12b Add a warning to inform about KeyStore usage #1269 2022-03-25 13:09:08 +01:00
J-Jamet
83bc769d9e Upgrade CHANGELOG 2022-03-23 20:10:39 +01:00
J-Jamet
8324acadc8 Toast when trying to open another database 2022-03-23 18:31:20 +01:00
J-Jamet
6e42db41be Fix shared otpauth link if database not open #1274 2022-03-23 18:03:32 +01:00
J-Jamet
3917bfc9e6 Update CHANGELOG 2022-03-23 17:46:42 +01:00
J-Jamet
d11febb1ce Ellipsize attachment name #1253 2022-03-23 17:41:51 +01:00
J-Jamet
360eb6f9cc Fix URL color 2022-03-23 15:40:08 +01:00
J-Jamet
e4ac5d01d0 Fingerprint unlock no more by default 2022-03-23 15:01:07 +01:00
J-Jamet
6a51fc0668 Fix settings title 2022-03-23 14:18:26 +01:00
J-Jamet
ba03f07fbe Replace Libre UI logo when no biometric 2022-03-23 13:34:04 +01:00
J-Jamet
7bf7d63f64 Better UI when no biometric is available 2022-03-23 13:13:29 +01:00
J-Jamet
d3efaabc24 Rollback algorithm 2022-03-23 09:29:45 +01:00
J-Jamet
b4283ed98b Small improvement 2022-03-23 09:15:36 +01:00
J-Jamet
de407e4cf9 Remove unused package 2022-03-20 16:40:19 +01:00
J-Jamet
60ed3a9836 Upgrade to 3.3.3 2022-03-19 15:53:00 +01:00
Jérémy JAMET
7948358d85 Add spaces in issue template 2022-03-18 19:46:32 +01:00
Jérémy JAMET
96b82bb9b2 Remove "please report..." in issue template 2022-03-18 19:45:39 +01:00
Stephan Paternotte
699ccf13f0 Translated using Weblate (Dutch)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-03-18 06:55:47 +01:00
Pavel Borecki
ae88aa4e42 Translated using Weblate (Czech)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-03-18 06:55:47 +01:00
J-Jamet
bcce13b12f Merge tag '3.3.2' into develop
3.3.2
2022-03-15 20:21:53 +01:00
Stephan Paternotte
d7d68ccdeb Translated using Weblate (Dutch)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-03-11 00:00:32 +01:00
abidin toumi
5cf6362db4 Translated using Weblate (Arabic)
Currently translated at 77.5% (460 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-03-10 17:56:43 +01:00
Matthaiks
4efcc48160 Translated using Weblate (Polish)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-03-10 17:56:43 +01:00
Raghav Kabra
383274ce0f Translated using Weblate (Hindi)
Currently translated at 25.1% (149 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hi/
2022-03-07 12:00:12 +01:00
abidin toumi
c9dec3a2f7 Translated using Weblate (Arabic)
Currently translated at 77.2% (458 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-03-07 12:00:11 +01:00
Wilker Santana da Silva
2d4bf2903b Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-03-07 12:00:11 +01:00
Nextross
2b88cfbda0 Translated using Weblate (Czech)
Currently translated at 98.6% (585 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-03-07 12:00:10 +01:00
Raghav Kabra
eb6ab7a156 Translated using Weblate (Hindi)
Currently translated at 23.9% (142 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hi/
2022-03-05 17:21:22 +01:00
SC
688cbe50f2 Translated using Weblate (Portuguese)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-03-02 19:55:26 +01:00
VfBFan
e0577d1628 Translated using Weblate (German)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-03-02 19:55:25 +01:00
Stephan Paternotte
0f8dd17fde Translated using Weblate (Dutch)
Currently translated at 99.4% (590 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-02-27 22:57:39 +01:00
Óscar Fernández Díaz
4bc8a08606 Translated using Weblate (Spanish)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-02-27 19:54:08 +01:00
Retrial
cf34433186 Translated using Weblate (Greek)
Currently translated at 100.0% (593 of 593 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-02-27 19:54:07 +01:00
179 changed files with 4425 additions and 1663 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
#github: [J-Jamet] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: Kunzisoft # Replace with a single Liberapay username
issuehunt: Kunzisoft/KeePassDX # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ['https://www.keepassdx.com/#donation'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -8,9 +8,11 @@ assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
@@ -18,9 +20,11 @@ Steps to reproduce the behavior:
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**KeePass Database**
- Created with: [e.g Windows KeePass 2.42]
- Version: [e.g. 2]
- Location: [e.g. Remote file retrieved with GDrive app]
@@ -28,15 +32,18 @@ A clear and concise description of what you expected to happen.
- Size: [e.g. 150Mo]
- Contains attachment: [e.g. Yes]
**KeePassDX (please complete the following information):**
**KeePassDX:**
- Version: [e.g. 2.5.0.0beta23]
- Build: [e.g. Free]
- Language: [e.g. French]
**Android (please complete the following information):**
**Android:**
- Device: [e.g. GalaxyS8]
- Version: [e.g. 8.1]
**Additional context**
Add any other context about the problem here.
- Browser for Autofill: [e.g. Chrome version X]

View File

@@ -1,3 +1,31 @@
KeePassDX(3.4.2)
* Fix service parameter and workflow to remove notification when service is killed
* Fix color
KeePassDX(3.4.1)
* Fix search mode with Magikeyboard #1292
* Fix select another entry with Magikeyboard #1293
* Fix unexpected lock with Magikeyboard #1294
* Small UI changes
KeePassDX(3.4.0)
* Passphrase implementation #218
* Show visual password strength indicator with entropy #631 #869 #454 #1270
* Dynamically save password generator configuration #618 #696
* Add advanced password filters #1052 #448 #983 #271 #539
* Better search implementation #175 #1254 #1267
* Manage package name from Magikeyboard #1010 #1261
* Ask confirmation to lock if changes without save #970
* Fix small bugs #1282
KeePassDX(3.3.3)
* Fix shared otpauth link if database not open #1274
* Ellipsize attachment name #1253
* Add a warning to inform about KeyStore usage #1269
* Fingerprint unlock no more by default #1273
* Tabs to show main and advanced content separately
* Fix URL color
KeePassDX(3.3.2)
* Merge KeePassDX & KeePassDX Pro #1257
* Create new Contributor Pro app

View File

@@ -1,6 +1,6 @@
# Android KeePassDX
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeePassDX is a **multi-format KeePass manager for Android devices**. The app allows creating keys and passwords in a secure way by integrating with the Android design standards.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> **Lightweight password manager for Android**, KeePassDX allows editing encrypted data in a single file in KeePass format and fill in the forms in a secure way.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">

View File

@@ -12,8 +12,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 31
versionCode = 104
versionName = "3.3.2"
versionCode = 111
versionName = "3.4.2"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -127,6 +127,8 @@ dependencies {
// Apache Commons
implementation 'commons-io:commons-io:2.8.0'
implementation 'commons-codec:commons-codec:1.15'
// Password generator
implementation 'me.gosimple:nbvcxz:1.5.0'
// Encrypt lib
implementation project(path: ':crypto')
// Icon pack

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="84"
android:viewportHeight="84">
<group
android:translateX="-12"
android:translateY="-12">
<path
android:fillColor="#ffa726"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path
android:fillColor="#ffffff"
android:strokeWidth="1.99999297"
android:pathData="M63.9961,34.0059 C61.5643,34.096,59.2564,35.102,57.5352,36.8223 C53.7682,40.589,53.7682,46.6982,57.5352,50.4649 C61.3017,54.232,67.4073,54.232,71.1739,50.4649 C74.9409,46.6982,74.9409,40.589,71.1739,36.8223 C69.2766,34.9258,66.6768,33.9054,63.9962,34.0059 Z M68.1992,40.6954 C69.8278,40.6958,71.148,42.016,71.1484,43.6446 C71.148,45.2732,69.8278,46.5934,68.1992,46.5938 C66.5706,46.5934,65.2504,45.2732,65.25,43.6446 C65.2504,42.016,66.5706,40.6958,68.1992,40.6954 Z M48.3438,55.4141 L36,67.7578 L36,72 L40.2422,72 L44.7578,67.4844 L44.7578,67.5 L49,67.5 L49,63.2578 L48.9844,63.2578 L49,63.2422 L49,63.2578 L53.2578,63.2578 L53.2578,60.3281 Z" />
</group>
</vector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="84"
android:viewportHeight="84">
<group
android:translateX="-12"
android:translateY="-12">
<path
android:fillColor="#ffa726"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path
android:fillColor="#ffffff"
android:strokeWidth="1.99999297"
android:pathData="M64.501,35.0576 C63.7095,35.0576,62.918,35.3613,62.3115,35.9678 L55.0127,43.2666 C53.7998,44.4795,53.7998,46.4306,55.0127,47.6436 L62.3115,54.9424 C63.5244,56.1553,65.4775,56.1553,66.6904,54.9424 L73.9873,47.6436 C75.2002,46.4307,75.2002,44.4796,73.9873,43.2666 L66.6904,35.9678 C66.0839,35.3613,65.2924,35.0576,64.5009,35.0576 Z M67.6729,42.6006 C69.3298,42.6006,70.6729,43.9437,70.6729,45.6006 C70.6729,47.2575,69.3298,48.6006,67.6729,48.6006 C66.016,48.6006,64.6729,47.2575,64.6729,45.6006 C64.6729,43.9437,66.016,42.6006,67.6729,42.6006 Z M48.3438,55.4141 L36,67.7578 L36,72 L40.2422,72 L44.7578,67.4844 L44.7578,67.5 L49,67.5 L49,63.2578 L48.9844,63.2578 L49,63.2422 L49,63.2578 L53.2578,63.2578 L53.2578,60.3281 Z" />
</group>
</vector>

View File

@@ -131,6 +131,9 @@
<activity
android:name="com.kunzisoft.keepass.activities.IconPickerActivity"
android:configChanges="keyboardHidden" />
<activity
android:name="com.kunzisoft.keepass.activities.KeyGeneratorActivity"
android:configChanges="keyboardHidden" />
<activity
android:name="com.kunzisoft.keepass.activities.ImageViewerActivity"
android:configChanges="keyboardHidden" />
@@ -155,6 +158,7 @@
<activity
android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity"
android:theme="@style/Theme.Transparent"
android:launchMode="singleInstance"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
@@ -169,9 +173,6 @@
<data android:scheme="otpauth" android:host="hotp" />
</intent-filter>
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity"
android:theme="@style/Theme.Transparent" />
<activity
android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
android:label="@string/keyboard_setting_label"

View File

@@ -44,6 +44,7 @@ import androidx.core.graphics.ColorUtils
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.tabs.TabLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.EntryFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
@@ -83,6 +84,7 @@ class EntryActivity : DatabaseLockActivity() {
private var titleIconView: ImageView? = null
private var historyView: View? = null
private var tagsListView: RecyclerView? = null
private var entryContentTab: TabLayout? = null
private var tagsAdapter: TagsAdapter? = null
private var entryProgress: LinearProgressIndicator? = null
private var lockView: View? = null
@@ -133,6 +135,7 @@ class EntryActivity : DatabaseLockActivity() {
titleIconView = findViewById(R.id.entry_icon)
historyView = findViewById(R.id.history_container)
tagsListView = findViewById(R.id.entry_tags_list_view)
entryContentTab = findViewById(R.id.entry_content_tab)
entryProgress = findViewById(R.id.entry_progress)
lockView = findViewById(R.id.lock_button)
loadingView = findViewById(R.id.loading)
@@ -162,6 +165,19 @@ class EntryActivity : DatabaseLockActivity() {
adapter = tagsAdapter
}
// Init content tab
entryContentTab?.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
mEntryViewModel.selectSection(EntryViewModel.EntrySection.
getEntrySectionByPosition(tab?.position ?: 0)
)
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {}
})
// Get Entry from UUID
try {
intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { mainEntryId ->
@@ -193,6 +209,10 @@ class EntryActivity : DatabaseLockActivity() {
lockAndExit()
}
mEntryViewModel.sectionSelected.observe(this) { entrySection ->
entryContentTab?.getTabAt(entrySection.position)?.select()
}
mEntryViewModel.entryInfoHistory.observe(this) { entryInfoHistory ->
if (entryInfoHistory != null) {
this.mMainEntryId = entryInfoHistory.mainEntryId

View File

@@ -58,9 +58,13 @@ import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.template.*
import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.services.AttachmentFileNotificationService
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
@@ -78,11 +82,9 @@ import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import org.joda.time.DateTime
import java.util.*
import kotlin.collections.ArrayList
class EntryEditActivity : DatabaseLockActivity(),
EntryCustomFieldDialogFragment.EntryCustomFieldListener,
GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener,
DatePickerDialog.OnDateSetListener,
TimePickerDialog.OnTimeSetListener,
@@ -119,6 +121,20 @@ class EntryEditActivity : DatabaseLockActivity(),
mEntryEditViewModel.selectIcon(icon)
}
private var mPasswordField: Field? = null
private var mKeyGeneratorResultLauncher = KeyGeneratorActivity.registerForGeneratedKeyResult(this) { keyGenerated ->
keyGenerated?.let {
mPasswordField?.let {
it.protectedValue.stringValue = keyGenerated
mEntryEditViewModel.selectPassword(it)
}
}
mPasswordField = null
Handler(Looper.getMainLooper()).post {
performedNextEducation()
}
}
// To ask data lost only one time
private var backPressedAlreadyApproved = false
@@ -268,9 +284,8 @@ class EntryEditActivity : DatabaseLockActivity(),
}
mEntryEditViewModel.requestPasswordSelection.observe(this) { passwordField ->
GeneratePasswordDialogFragment
.getInstance(passwordField)
.show(supportFragmentManager, "PasswordGeneratorFragment")
mPasswordField = passwordField
KeyGeneratorActivity.launch(this, mKeyGeneratorResultLauncher)
}
mEntryEditViewModel.requestCustomFieldEdition.observe(this) { field ->
@@ -420,9 +435,10 @@ class EntryEditActivity : DatabaseLockActivity(),
private fun entryValidatedForKeyboardSelection(database: Database, entry: Entry) {
// Populate Magikeyboard with entry
populateKeyboardAndMoveAppToBackground(this,
entry.getEntryInfo(database),
intent)
MagikeyboardService.populateKeyboardAndMoveAppToBackground(
this,
entry.getEntryInfo(database)
)
onValidateSpecialMode()
// Don't keep activity history for entry edition
finishForEntryResult(entry)
@@ -656,17 +672,6 @@ class EntryEditActivity : DatabaseLockActivity(),
mEntryEditViewModel.selectTime(hours, minutes)
}
override fun acceptPassword(passwordField: Field) {
mEntryEditViewModel.selectPassword(passwordField)
Handler(Looper.getMainLooper()).post {
performedNextEducation()
}
}
override fun cancelPassword(passwordField: Field) {
// Do nothing here
}
override fun onBackPressed() {
onApprovedBackPressed {
super@EntryEditActivity.onBackPressed()

View File

@@ -19,17 +19,16 @@
*/
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -50,151 +49,164 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
var sharedWebDomain: String? = null
var otpString: String? = null
when (intent?.action) {
Intent.ACTION_SEND -> {
if ("text/plain" == intent.type) {
// Retrieve web domain or OTP
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { extra ->
if (OtpEntryFields.isOTPUri(extra))
otpString = extra
else
sharedWebDomain = Uri.parse(extra).host
val keySelectionBundle = intent.getBundleExtra(KEY_SELECTION_BUNDLE)
if (keySelectionBundle != null) {
// To manage package name
var searchInfo = SearchInfo()
keySelectionBundle.getParcelable<SearchInfo>(KEY_SEARCH_INFO)?.let { mSearchInfo ->
searchInfo = mSearchInfo
}
launch(database, searchInfo)
} else {
// To manage share
var sharedWebDomain: String? = null
var otpString: String? = null
when (intent?.action) {
Intent.ACTION_SEND -> {
if ("text/plain" == intent.type) {
// Retrieve web domain or OTP
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { extra ->
if (OtpEntryFields.isOTPUri(extra))
otpString = extra
else
sharedWebDomain = Uri.parse(extra).host
}
}
}
}
Intent.ACTION_VIEW -> {
// Retrieve OTP
intent.dataString?.let { extra ->
if (OtpEntryFields.isOTPUri(extra))
otpString = extra
Intent.ACTION_VIEW -> {
// Retrieve OTP
intent.dataString?.let { extra ->
if (OtpEntryFields.isOTPUri(extra))
otpString = extra
}
}
else -> {}
}
else -> {}
}
// Build domain search param
val searchInfo = SearchInfo().apply {
this.webDomain = sharedWebDomain
this.otpString = otpString
}
// Build domain search param
val searchInfo = SearchInfo().apply {
this.webDomain = sharedWebDomain
this.otpString = otpString
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
launch(database, searchInfo)
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
launch(database, searchInfo)
}
}
}
private fun launch(database: Database?,
searchInfo: SearchInfo) {
if (!searchInfo.containsOnlyNullValues()) {
// Setting to integrate Magikeyboard
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
// Setting to integrate Magikeyboard
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
// If database is open
val readOnly = database?.isReadOnly != false
SearchHelper.checkAutoSearchInfo(this,
database,
searchInfo,
{ openedDatabase, items ->
// Items found
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(
this,
openedDatabase,
searchInfo,
false)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (searchShareForMagikeyboard) {
if (items.size == 1) {
// Automatically populate keyboard
val entryPopulate = items[0]
populateKeyboardAndMoveAppToBackground(
this,
entryPopulate,
intent)
} else {
// Select the one we want
GroupActivity.launchForKeyboardSelectionResult(this,
openedDatabase,
searchInfo,
true)
}
} else {
GroupActivity.launchForSearchResult(this,
openedDatabase,
searchInfo,
true)
}
},
{ openedDatabase ->
// Show the database UI to select the entry
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(this,
openedDatabase,
searchInfo,
false)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (readOnly || searchShareForMagikeyboard) {
GroupActivity.launchForKeyboardSelectionResult(this,
// If database is open
val readOnly = database?.isReadOnly != false
SearchHelper.checkAutoSearchInfo(this,
database,
searchInfo,
{ openedDatabase, items ->
// Items found
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(
this,
openedDatabase,
searchInfo,
false)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (searchShareForMagikeyboard) {
MagikeyboardService.performSelection(
items,
{ entryInfo ->
// Automatically populate keyboard
MagikeyboardService.populateKeyboardAndMoveAppToBackground(
this,
entryInfo
)
},
{ autoSearch ->
GroupActivity.launchForKeyboardSelectionResult(this,
openedDatabase,
searchInfo,
autoSearch)
}
)
} else {
GroupActivity.launchForSearchResult(this,
openedDatabase,
searchInfo,
true)
}
},
{ openedDatabase ->
// Show the database UI to select the entry
if (searchInfo.otpString != null) {
if (!readOnly) {
GroupActivity.launchForSaveResult(this,
openedDatabase,
searchInfo,
false)
}
},
{
// If database not open
if (searchInfo.otpString != null) {
if (!readOnly) {
FileDatabaseSelectActivity.launchForSaveResult(this,
searchInfo)
} else {
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (searchShareForMagikeyboard) {
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this,
searchInfo)
} else {
FileDatabaseSelectActivity.launchForSearchResult(this,
searchInfo)
Toast.makeText(applicationContext,
R.string.autofill_read_only_save,
Toast.LENGTH_LONG)
.show()
}
} else if (readOnly || searchShareForMagikeyboard) {
GroupActivity.launchForKeyboardSelectionResult(this,
openedDatabase,
searchInfo,
false)
} else {
GroupActivity.launchForSearchResult(this,
openedDatabase,
searchInfo,
false)
}
)
}
},
{
// If database not open
if (searchInfo.otpString != null) {
FileDatabaseSelectActivity.launchForSaveResult(this,
searchInfo)
} else if (searchShareForMagikeyboard) {
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this,
searchInfo)
} else {
FileDatabaseSelectActivity.launchForSearchResult(this,
searchInfo)
}
}
)
finish()
}
}
fun populateKeyboardAndMoveAppToBackground(activity: Activity,
entry: EntryInfo,
intent: Intent,
toast: Boolean = true) {
// Populate Magikeyboard with entry
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
// Consume the selection mode
EntrySelectionHelper.removeModesFromIntent(intent)
activity.moveTaskToBack(true)
companion object {
private const val KEY_SELECTION_BUNDLE = "KEY_SELECTION_BUNDLE"
private const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
fun launch(context: Context,
searchInfo: SearchInfo? = null) {
val intent = Intent(context, EntrySelectionLauncherActivity::class.java).apply {
putExtra(KEY_SELECTION_BUNDLE, Bundle().apply {
putParcelable(KEY_SEARCH_INFO, searchInfo)
})
}
// New task needed because don't launch from an Activity context
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent)
}
}
}

View File

@@ -31,7 +31,6 @@ import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.annotation.RequiresApi

View File

@@ -67,6 +67,7 @@ import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.RegisterInfo
@@ -79,6 +80,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
@@ -182,6 +184,11 @@ class GroupActivity : DatabaseLockActivity(),
addSearch()
//loadGroup()
// Back to previous keyboard
if (PreferencesUtil.isKeyboardPreviousSearchEnable(this@GroupActivity)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
return true
}
@@ -200,7 +207,7 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction()
if (mSearchState == null) {
mSearchState = SearchState(searchFiltersView?.searchParameters
?: SearchParameters(), 0)
?: PreferencesUtil.getDefaultSearchParameters(this), 0)
}
}
@@ -719,7 +726,7 @@ class GroupActivity : DatabaseLockActivity(),
val stringQuery = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
intent.action = Intent.ACTION_DEFAULT
intent.removeExtra(SearchManager.QUERY)
mSearchState = SearchState(SearchParameters().apply {
mSearchState = SearchState(PreferencesUtil.getDefaultSearchParameters(this).apply {
searchQuery = stringQuery
}, mSearchState?.firstVisibleItem ?: 0)
}
@@ -884,10 +891,9 @@ class GroupActivity : DatabaseLockActivity(),
private fun entrySelectedForKeyboardSelection(database: Database, entry: Entry) {
reloadCurrentGroup()
// Populate Magikeyboard with entry
populateKeyboardAndMoveAppToBackground(
MagikeyboardService.populateKeyboardAndMoveAppToBackground(
this,
entry.getEntryInfo(database),
intent
entry.getEntryInfo(database)
)
onValidateSpecialMode()
}
@@ -1126,11 +1132,13 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction()
searchView?.setOnQueryTextListener(null)
searchFiltersView?.saveSearchParameters()
}
private fun addSearchQueryInSearchView(searchQuery: String) {
searchView?.setOnQueryTextListener(null)
searchView?.setQuery(searchQuery, false)
searchView?.clearFocus()
searchView?.setOnQueryTextListener(mOnSearchQueryTextListener)
}
@@ -1605,50 +1613,31 @@ class GroupActivity : DatabaseLockActivity(),
autofillActivityResultLauncher: ActivityResultLauncher<Intent>?) {
EntrySelectionHelper.doSpecialAction(activity.intent,
{
GroupActivity.launch(
// Default action
launch(
activity,
database,
true
)
},
{ searchInfo ->
SearchHelper.checkAutoSearchInfo(activity,
// Search action
if (database.loaded) {
launchForSearchResult(activity,
database,
searchInfo,
{ _, _ ->
// Response is build
GroupActivity.launchForSearchResult(activity,
database,
searchInfo,
true)
onLaunchActivitySpecialMode()
},
{
// Here no search info found
if (database.isReadOnly) {
GroupActivity.launchForSearchResult(activity,
database,
searchInfo,
false)
} else {
GroupActivity.launchForSaveResult(activity,
database,
searchInfo,
false)
}
onLaunchActivitySpecialMode()
},
{
// Simply close if database not opened, normally not happened
onCancelSpecialMode()
}
)
true)
onLaunchActivitySpecialMode()
} else {
// Simply close if database not opened
onCancelSpecialMode()
}
},
{ searchInfo ->
// Save info used with OTP
// Save info
if (database.loaded) {
if (!database.isReadOnly) {
GroupActivity.launchForSaveResult(
launchForSaveResult(
activity,
database,
searchInfo,
@@ -1667,28 +1656,33 @@ class GroupActivity : DatabaseLockActivity(),
}
},
{ searchInfo ->
// Keyboard selection
SearchHelper.checkAutoSearchInfo(activity,
database,
searchInfo,
{ _, items ->
// Response is build
if (items.size == 1) {
populateKeyboardAndMoveAppToBackground(activity,
items[0],
activity.intent)
onValidateSpecialMode()
} else {
// Select the one we want
GroupActivity.launchForKeyboardSelectionResult(activity,
database,
searchInfo,
true)
onLaunchActivitySpecialMode()
}
MagikeyboardService.performSelection(
items,
{ entryInfo ->
// Keyboard populated
MagikeyboardService.populateKeyboardAndMoveAppToBackground(
activity,
entryInfo
)
onValidateSpecialMode()
},
{ autoSearch ->
launchForKeyboardSelectionResult(activity,
database,
searchInfo,
autoSearch)
onLaunchActivitySpecialMode()
}
)
},
{
// Here no search info found, disable auto search
GroupActivity.launchForKeyboardSelectionResult(activity,
launchForKeyboardSelectionResult(activity,
database,
searchInfo,
false)
@@ -1701,6 +1695,7 @@ class GroupActivity : DatabaseLockActivity(),
)
},
{ searchInfo, autofillComponent ->
// Autofill selection
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SearchHelper.checkAutoSearchInfo(activity,
database,
@@ -1712,7 +1707,7 @@ class GroupActivity : DatabaseLockActivity(),
},
{
// Here no search info found, disable auto search
GroupActivity.launchForAutofillResult(activity,
launchForAutofillResult(activity,
database,
autofillActivityResultLauncher,
autofillComponent,
@@ -1730,20 +1725,21 @@ class GroupActivity : DatabaseLockActivity(),
}
},
{ registerInfo ->
// Autofill registration
if (!database.isReadOnly) {
SearchHelper.checkAutoSearchInfo(activity,
database,
registerInfo?.searchInfo,
{ _, _ ->
// No auto search, it's a registration
GroupActivity.launchForRegistration(activity,
launchForRegistration(activity,
database,
registerInfo)
onLaunchActivitySpecialMode()
},
{
// Here no search info found, disable auto search
GroupActivity.launchForRegistration(activity,
launchForRegistration(activity,
database,
registerInfo)
onLaunchActivitySpecialMode()

View File

@@ -0,0 +1,139 @@
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Intent
import android.os.Bundle
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.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.KeyGeneratorFragment
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.updateLockPaddingLeft
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class KeyGeneratorActivity : DatabaseLockActivity() {
private lateinit var toolbar: Toolbar
private lateinit var coordinatorLayout: CoordinatorLayout
private lateinit var validationButton: View
private var lockView: View? = null
private val keyGeneratorViewModel: KeyGeneratorViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_key_generator)
toolbar = findViewById(R.id.toolbar)
toolbar.title = " "
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
coordinatorLayout = findViewById(R.id.key_generator_coordinator)
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
validationButton = findViewById(R.id.key_generator_validation)
validationButton.setOnClickListener {
keyGeneratorViewModel.validateKeyGenerated()
}
supportFragmentManager.commit {
replace(R.id.key_generator_fragment, KeyGeneratorFragment.getInstance(
// Default selection tab
KeyGeneratorFragment.KeyGeneratorTab.PASSWORD
), KEY_GENERATED_FRAGMENT_TAG
)
}
keyGeneratorViewModel.keyGenerated.observe(this) { keyGenerated ->
setResult(Activity.RESULT_OK, Intent().apply {
putExtra(KEY_GENERATED, keyGenerated)
})
finish()
}
}
override fun viewToInvalidateTimeout(): View? {
return findViewById<ViewGroup>(R.id.key_generator_container)
}
override fun onResume() {
super.onResume()
// Show the lock button
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
// Padding if lock button visible
toolbar.updateLockPaddingLeft()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.key_generator, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
}
R.id.menu_generate -> {
keyGeneratorViewModel.requireKeyGeneration()
}
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED, Intent())
super.onBackPressed()
}
companion object {
private const val KEY_GENERATED = "KEY_GENERATED"
private const val KEY_GENERATED_FRAGMENT_TAG = "KEY_GENERATED_FRAGMENT_TAG"
fun registerForGeneratedKeyResult(activity: FragmentActivity,
keyGeneratedListener: (String?) -> Unit): ActivityResultLauncher<Intent> {
return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
keyGeneratedListener.invoke(
result.data?.getStringExtra(KEY_GENERATED)
)
} else {
keyGeneratedListener.invoke(null)
}
}
}
fun launch(context: FragmentActivity,
resultLauncher: ActivityResultLauncher<Intent>) {
// Create an instance to return the picker icon
resultLauncher.launch(
Intent(context, KeyGeneratorActivity::class.java)
)
}
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2020 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
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
/**
* Activity to select entry in database and populate it in Magikeyboard
*/
class MagikeyboardLauncherActivity : DatabaseModeActivity() {
override fun applyCustomStyle(): Boolean {
return false
}
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
SearchHelper.checkAutoSearchInfo(this,
database,
null,
{ _, _ ->
// Not called
// if items found directly returns before calling this activity
},
{ openedDatabase ->
// Select if not found
GroupActivity.launchForKeyboardSelectionResult(this, openedDatabase)
},
{
// Pass extra to get entry
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
}
)
finish()
}
}

View File

@@ -34,11 +34,13 @@ import android.view.ViewGroup
import android.widget.Button
import android.widget.CompoundButton
import android.widget.TextView
import android.widget.Toast
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.biometric.BiometricManager
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
@@ -52,6 +54,7 @@ import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
@@ -63,6 +66,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil
@@ -79,6 +83,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
// Views
private var toolbar: Toolbar? = null
private var filenameView: TextView? = null
private var advancedUnlockButton: View? = null
private var mainCredentialView: MainCredentialView? = null
private var confirmButtonView: Button? = null
private var infoContainerView: ViewGroup? = null
@@ -116,6 +121,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
supportActionBar?.setDisplayShowHomeEnabled(true)
filenameView = findViewById(R.id.filename)
advancedUnlockButton = findViewById(R.id.activity_password_advanced_unlock_button)
mainCredentialView = findViewById(R.id.activity_password_credentials)
confirmButtonView = findViewById(R.id.activity_password_open_button)
infoContainerView = findViewById(R.id.activity_password_info_container)
@@ -143,6 +149,11 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
getUriFromIntent(intent)
// Init Biometric elements
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockButton?.setOnClickListener {
startActivity(Intent(this, SettingsAdvancedUnlockActivity::class.java))
}
}
advancedUnlockFragment = supportFragmentManager
.findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment?
if (advancedUnlockFragment == null) {
@@ -227,6 +238,15 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
if (database != null) {
// Trying to load another database
if (mDatabaseFileUri != null
&& database.fileUri != null
&& mDatabaseFileUri != database.fileUri) {
Toast.makeText(this,
R.string.warning_database_already_opened,
Toast.LENGTH_LONG
).show()
}
launchGroupActivityIfLoaded(database)
}
}
@@ -588,15 +608,29 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
{
performedNextEducation(menu)
})
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) {
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(this)
if ((biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockButton != null) {
mPasswordActivityEducation.checkAndPerformedBiometricEducation(
advancedUnlockButton!!,
{
startActivity(
Intent(
this,
SettingsAdvancedUnlockActivity::class.java
)
)
},
{
advancedUnlockFragment?.performEducation(mPasswordActivityEducation,
readOnlyEducationPerformed,
{
performedNextEducation(menu)
},
{
performedNextEducation(menu)
})
})
}
}
} catch (ignored: Exception) {}
}
}

View File

@@ -1,224 +0,0 @@
/*
* Copyright 2019 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.content.Context
import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AlertDialog
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.view.applyFontVisibility
class GeneratePasswordDialogFragment : DatabaseDialogFragment() {
private var mListener: GeneratePasswordListener? = null
private var root: View? = null
private var lengthTextView: EditText? = null
private var passwordInputLayoutView: TextInputLayout? = null
private var passwordView: EditText? = null
private var mPasswordField: Field? = null
private var uppercaseBox: CompoundButton? = null
private var lowercaseBox: CompoundButton? = null
private var digitsBox: CompoundButton? = null
private var minusBox: CompoundButton? = null
private var underlineBox: CompoundButton? = null
private var spaceBox: CompoundButton? = null
private var specialsBox: CompoundButton? = null
private var bracketsBox: CompoundButton? = null
private var extendedBox: CompoundButton? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as GeneratePasswordListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + GeneratePasswordListener::class.java.name)
}
}
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
root = inflater.inflate(R.layout.fragment_generate_password, null)
passwordInputLayoutView = root?.findViewById(R.id.password_input_layout)
passwordView = root?.findViewById(R.id.password)
passwordView?.applyFontVisibility()
val passwordCopyView: ImageView? = root?.findViewById(R.id.password_copy_button)
passwordCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(activity))
View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(activity)
passwordCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(passwordView!!.text.toString(),
getString(R.string.copy_field,
getString(R.string.entry_password)))
}
lengthTextView = root?.findViewById(R.id.length)
uppercaseBox = root?.findViewById(R.id.cb_uppercase)
lowercaseBox = root?.findViewById(R.id.cb_lowercase)
digitsBox = root?.findViewById(R.id.cb_digits)
minusBox = root?.findViewById(R.id.cb_minus)
underlineBox = root?.findViewById(R.id.cb_underline)
spaceBox = root?.findViewById(R.id.cb_space)
specialsBox = root?.findViewById(R.id.cb_specials)
bracketsBox = root?.findViewById(R.id.cb_brackets)
extendedBox = root?.findViewById(R.id.cb_extended)
mPasswordField = arguments?.getParcelable(KEY_PASSWORD_FIELD)
assignDefaultCharacters()
val seekBar = root?.findViewById<SeekBar>(R.id.seekbar_length)
seekBar?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
lengthTextView?.setText(progress.toString())
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
context?.let { context ->
seekBar?.progress = PreferencesUtil.getDefaultPasswordLength(context)
}
root?.findViewById<Button>(R.id.generate_password_button)
?.setOnClickListener { fillPassword() }
builder.setView(root)
.setPositiveButton(R.string.accept) { _, _ ->
mPasswordField?.let { passwordField ->
passwordView?.text?.toString()?.let { passwordValue ->
passwordField.protectedValue.stringValue = passwordValue
}
mListener?.acceptPassword(passwordField)
}
dismiss()
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
mPasswordField?.let { passwordField ->
mListener?.cancelPassword(passwordField)
}
dismiss()
}
// Pre-populate a password to possibly save the user a few clicks
fillPassword()
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun assignDefaultCharacters() {
uppercaseBox?.isChecked = false
lowercaseBox?.isChecked = false
digitsBox?.isChecked = false
minusBox?.isChecked = false
underlineBox?.isChecked = false
spaceBox?.isChecked = false
specialsBox?.isChecked = false
bracketsBox?.isChecked = false
extendedBox?.isChecked = false
context?.let { context ->
PreferencesUtil.getDefaultPasswordCharacters(context)?.let { charSet ->
for (passwordChar in charSet) {
when (passwordChar) {
getString(R.string.value_password_uppercase) -> uppercaseBox?.isChecked = true
getString(R.string.value_password_lowercase) -> lowercaseBox?.isChecked = true
getString(R.string.value_password_digits) -> digitsBox?.isChecked = true
getString(R.string.value_password_minus) -> minusBox?.isChecked = true
getString(R.string.value_password_underline) -> underlineBox?.isChecked = true
getString(R.string.value_password_space) -> spaceBox?.isChecked = true
getString(R.string.value_password_special) -> specialsBox?.isChecked = true
getString(R.string.value_password_brackets) -> bracketsBox?.isChecked = true
getString(R.string.value_password_extended) -> extendedBox?.isChecked = true
}
}
}
}
}
private fun fillPassword() {
root?.findViewById<EditText>(R.id.password)?.setText(generatePassword())
}
fun generatePassword(): String {
var password = ""
try {
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
password = PasswordGenerator(resources).generatePassword(length,
uppercaseBox?.isChecked == true,
lowercaseBox?.isChecked == true,
digitsBox?.isChecked == true,
minusBox?.isChecked == true,
underlineBox?.isChecked == true,
spaceBox?.isChecked == true,
specialsBox?.isChecked == true,
bracketsBox?.isChecked == true,
extendedBox?.isChecked == true)
passwordInputLayoutView?.error = null
} catch (e: NumberFormatException) {
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
} catch (e: IllegalArgumentException) {
passwordInputLayoutView?.error = e.message
}
return password
}
interface GeneratePasswordListener {
fun acceptPassword(passwordField: Field)
fun cancelPassword(passwordField: Field)
}
companion object {
private const val KEY_PASSWORD_FIELD = "KEY_PASSWORD_FIELD"
fun getInstance(field: Field): GeneratePasswordDialogFragment {
return GeneratePasswordDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(KEY_PASSWORD_FIELD, field)
}
}
}
}
}

View File

@@ -36,8 +36,11 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.password.PasswordEntropy
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.PassKeyView
import com.kunzisoft.keepass.view.applyFontVisibility
class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
@@ -48,8 +51,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
private var passwordCheckBox: CompoundButton? = null
private var passwordTextInputLayout: TextInputLayout? = null
private var passwordView: TextView? = null
private var passKeyView: PassKeyView? = null
private var passwordRepeatTextInputLayout: TextInputLayout? = null
private var passwordRepeatView: TextView? = null
@@ -59,6 +61,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
private var mListener: AssignMainCredentialDialogListener? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var mPasswordEntropyCalculator: PasswordEntropy? = null
private var mEmptyPasswordConfirmationDialog: AlertDialog? = null
private var mNoKeyConfirmationDialog: AlertDialog? = null
@@ -100,6 +103,13 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
super.onDetach()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Create the password entropy object
mPasswordEntropyCalculator = PasswordEntropy()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
@@ -123,10 +133,10 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
}
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
passwordView = rootView?.findViewById(R.id.pass_password)
passKeyView = rootView?.findViewById(R.id.password_view)
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
passwordRepeatView = rootView?.findViewById(R.id.password_confirmation)
passwordRepeatView?.applyFontVisibility()
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
@@ -162,7 +172,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
if (allowNoMasterKey)
showNoKeyConfirmationDialog()
else {
passwordTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
passwordRepeatTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
}
}
if (!error) {
@@ -194,22 +204,22 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
super.onResume()
// To check checkboxes if a text is present
passwordView?.addTextChangedListener(passwordTextWatcher)
passKeyView?.addTextChangedListener(passwordTextWatcher)
}
override fun onPause() {
super.onPause()
passwordView?.removeTextChangedListener(passwordTextWatcher)
passKeyView?.removeTextChangedListener(passwordTextWatcher)
}
private fun verifyPassword(): Boolean {
var error = false
if (passwordCheckBox != null
&& passwordCheckBox!!.isChecked
&& passwordView != null
&& passKeyView != null
&& passwordRepeatView != null) {
mMasterPassword = passwordView!!.text.toString()
mMasterPassword = passKeyView!!.passwordString
val confPassword = passwordRepeatView!!.text.toString()
// Verify that passwords match

View File

@@ -16,7 +16,6 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo
@@ -25,6 +24,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.view.TemplateView
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.viewmodels.EntryViewModel
import java.util.*
@@ -32,6 +32,9 @@ import java.util.*
class EntryFragment: DatabaseFragment() {
private lateinit var rootView: View
private lateinit var mainSection: View
private lateinit var advancedSection: View
private lateinit var templateView: TemplateView
private lateinit var creationDateView: TextView
@@ -72,6 +75,10 @@ class EntryFragment: DatabaseFragment() {
if (savedInstanceState == null) {
view.isVisible = false
}
mainSection = view.findViewById(R.id.entry_section_main)
advancedSection = view.findViewById(R.id.entry_section_advanced)
templateView = view.findViewById(R.id.entry_template)
loadTemplateSettings()
@@ -111,6 +118,19 @@ class EntryFragment: DatabaseFragment() {
}
}
}
mEntryViewModel.sectionSelected.observe(viewLifecycleOwner) { entrySection ->
when (entrySection ?: EntryViewModel.EntrySection.MAIN) {
EntryViewModel.EntrySection.MAIN -> {
mainSection.showByFading()
advancedSection.hideByFading()
}
EntryViewModel.EntrySection.ADVANCED -> {
mainSection.hideByFading()
advancedSection.showByFading()
}
}
}
}
override fun onDatabaseRetrieved(database: Database?) {

View File

@@ -26,14 +26,14 @@ class IconPickerFragment : DatabaseFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_icon_picker, container, false)
return inflater.inflate(R.layout.fragment_tabs_pagination, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewPager = view.findViewById(R.id.icon_picker_pager)
tabLayout = view.findViewById(R.id.icon_picker_tabs)
viewPager = view.findViewById(R.id.tabs_view_pager)
tabLayout = view.findViewById(R.id.tabs_layout)
resetAppTimeoutWhenViewFocusedOrChanged(view)
arguments?.apply {

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2022 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.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.fragment.app.activityViewModels
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.KeyGeneratorPagerAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class KeyGeneratorFragment : DatabaseFragment() {
private var keyGeneratorPagerAdapter: KeyGeneratorPagerAdapter? = null
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
private var mSelectedTab = KeyGeneratorTab.PASSWORD
private var mOnPageChangeCallback: ViewPager2.OnPageChangeCallback = object:
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
mSelectedTab = KeyGeneratorTab.getKeyGeneratorTabByPosition(position)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_tabs_pagination, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
keyGeneratorPagerAdapter = KeyGeneratorPagerAdapter(this, )
viewPager = view.findViewById(R.id.tabs_view_pager)
tabLayout = view.findViewById(R.id.tabs_layout)
viewPager.adapter = keyGeneratorPagerAdapter
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = getString(KeyGeneratorTab.getKeyGeneratorTabByPosition(position).stringId)
}.attach()
viewPager.registerOnPageChangeCallback(mOnPageChangeCallback)
resetAppTimeoutWhenViewFocusedOrChanged(view)
arguments?.apply {
if (containsKey(PASSWORD_TAB_ARG)) {
viewPager.currentItem = getInt(PASSWORD_TAB_ARG)
}
remove(PASSWORD_TAB_ARG)
}
mKeyGeneratorViewModel.requireKeyGeneration.observe(viewLifecycleOwner) {
when (mSelectedTab) {
KeyGeneratorTab.PASSWORD -> {
mKeyGeneratorViewModel.requirePasswordGeneration()
}
KeyGeneratorTab.PASSPHRASE -> {
mKeyGeneratorViewModel.requirePassphraseGeneration()
}
}
}
mKeyGeneratorViewModel.keyGeneratedValidated.observe(viewLifecycleOwner) {
when (mSelectedTab) {
KeyGeneratorTab.PASSWORD -> {
mKeyGeneratorViewModel.validatePasswordGenerated()
}
KeyGeneratorTab.PASSPHRASE -> {
mKeyGeneratorViewModel.validatePassphraseGenerated()
}
}
}
}
override fun onDestroyView() {
viewPager.unregisterOnPageChangeCallback(mOnPageChangeCallback)
super.onDestroyView()
}
override fun onDatabaseRetrieved(database: Database?) {
// Nothing here
}
enum class KeyGeneratorTab(@StringRes val stringId: Int) {
PASSWORD(R.string.password), PASSPHRASE(R.string.passphrase);
companion object {
fun getKeyGeneratorTabByPosition(position: Int): KeyGeneratorTab {
return when (position) {
0 -> PASSWORD
else -> PASSPHRASE
}
}
}
}
companion object {
private const val PASSWORD_TAB_ARG = "PASSWORD_TAB_ARG"
fun getInstance(keyGeneratorTab: KeyGeneratorTab): KeyGeneratorFragment {
val fragment = KeyGeneratorFragment()
fragment.arguments = Bundle().apply {
putInt(PASSWORD_TAB_ARG, keyGeneratorTab.ordinal)
}
return fragment
}
}
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2022 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.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.activityViewModels
import com.google.android.material.slider.Slider
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.password.PassphraseGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.view.PassKeyView
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class PassphraseGeneratorFragment : DatabaseFragment() {
private lateinit var passKeyView: PassKeyView
private lateinit var sliderWordCount: Slider
private lateinit var wordCountText: EditText
private lateinit var charactersCountText: TextView
private lateinit var wordSeparator: EditText
private lateinit var wordCaseSpinner: Spinner
private var minSliderWordCount: Int = 0
private var maxSliderWordCount: Int = 0
private var wordCaseAdapter: ArrayAdapter<String>? = null
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_generate_passphrase, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
passKeyView = view.findViewById(R.id.passphrase_view)
val passphraseCopyView: ImageView? = view.findViewById(R.id.passphrase_copy_button)
sliderWordCount = view.findViewById(R.id.slider_word_count)
wordCountText = view.findViewById(R.id.word_count)
charactersCountText = view.findViewById(R.id.character_count)
wordSeparator = view.findViewById(R.id.word_separator)
wordCaseSpinner = view.findViewById(R.id.word_case)
minSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_min)
maxSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_max)
contextThemed?.let { context ->
passphraseCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context))
View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(context)
passphraseCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(passKeyView.passwordString,
getString(R.string.copy_field,
getString(R.string.entry_password)))
}
wordCaseAdapter = ArrayAdapter(context,
android.R.layout.simple_spinner_item, resources.getStringArray(R.array.word_case_array)).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
wordCaseSpinner.adapter = wordCaseAdapter
}
loadSettings()
var listenSlider = true
var listenEditText = true
sliderWordCount.addOnChangeListener { _, value, _ ->
try {
listenEditText = false
if (listenSlider) {
wordCountText.setText(value.toInt().toString())
}
} catch (e: Exception) {
Log.e(TAG, "Unable to set the word count value", e)
} finally {
listenEditText = true
}
}
sliderWordCount.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
// TODO upgrade material-components lib
// https://stackoverflow.com/questions/70873160/material-slider-onslidertouchlisteners-methods-can-only-be-called-from-within-t
@SuppressLint("RestrictedApi")
override fun onStartTrackingTouch(slider: Slider) {}
@SuppressLint("RestrictedApi")
override fun onStopTrackingTouch(slider: Slider) {
generatePassphrase()
}
})
wordCountText.doOnTextChanged { _, _, _, _ ->
if (listenEditText) {
try {
listenSlider = false
setSliderValue(getWordCount())
} catch (e: Exception) {
Log.e(TAG, "Unable to get the word count value", e)
} finally {
listenSlider = true
generatePassphrase()
}
}
}
wordSeparator.doOnTextChanged { _, _, _, _ ->
generatePassphrase()
}
wordCaseSpinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
generatePassphrase()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
generatePassphrase()
mKeyGeneratorViewModel.passphraseGeneratedValidated.observe(viewLifecycleOwner) {
mKeyGeneratorViewModel.setKeyGenerated(passKeyView.passwordString)
}
mKeyGeneratorViewModel.requirePassphraseGeneration.observe(viewLifecycleOwner) {
generatePassphrase()
}
resetAppTimeoutWhenViewFocusedOrChanged(view)
}
private fun getWordCount(): Int {
return try {
Integer.valueOf(wordCountText.text.toString())
} catch (numberException: NumberFormatException) {
minSliderWordCount
}
}
private fun setWordCount(wordCount: Int) {
setSliderValue(wordCount)
wordCountText.setText(wordCount.toString())
}
private fun setSliderValue(value: Int) {
when {
value < minSliderWordCount -> {
sliderWordCount.value = minSliderWordCount.toFloat()
}
value > maxSliderWordCount -> {
sliderWordCount.value = maxSliderWordCount.toFloat()
}
else -> {
sliderWordCount.value = value.toFloat()
}
}
}
private fun getWordSeparator(): String {
return wordSeparator.text.toString().ifEmpty { " " }
}
private fun getWordCase(): PassphraseGenerator.WordCase {
var wordCase = PassphraseGenerator.WordCase.LOWER_CASE
try {
wordCase = PassphraseGenerator.WordCase.getByOrdinal(wordCaseSpinner.selectedItemPosition)
} catch (caseException: Exception) {
Log.e(TAG, "Unable to retrieve the word case", caseException)
}
return wordCase
}
private fun setWordCase(wordCase: PassphraseGenerator.WordCase) {
wordCaseSpinner.setSelection(wordCase.ordinal)
}
private fun getSeparator(): String {
return wordSeparator.text?.toString() ?: ""
}
private fun setSeparator(separator: String) {
wordSeparator.setText(separator)
}
private fun generatePassphrase() {
var passphrase = ""
try {
passphrase = PassphraseGenerator().generatePassphrase(
getWordCount(),
getWordSeparator(),
getWordCase())
} catch (e: Exception) {
Log.e(TAG, "Unable to generate a passphrase", e)
}
passKeyView.passwordString = passphrase
charactersCountText.text = getString(R.string.character_count, passphrase.length)
}
override fun onDestroy() {
saveSettings()
super.onDestroy()
}
private fun saveSettings() {
context?.let { context ->
PreferencesUtil.setDefaultPassphraseWordCount(context, getWordCount())
PreferencesUtil.setDefaultPassphraseWordCase(context, getWordCase())
PreferencesUtil.setDefaultPassphraseSeparator(context, getSeparator())
}
}
private fun loadSettings() {
context?.let { context ->
setWordCount(PreferencesUtil.getDefaultPassphraseWordCount(context))
setWordCase(PreferencesUtil.getDefaultPassphraseWordCase(context))
setSeparator(PreferencesUtil.getDefaultPassphraseSeparator(context))
}
}
override fun onDatabaseRetrieved(database: Database?) {
// Nothing here
}
companion object {
private const val TAG = "PassphraseGnrtrFrgmt"
}
}

View File

@@ -0,0 +1,358 @@
/*
* Copyright 2019 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.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.ImageView
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.activityViewModels
import com.google.android.material.slider.Slider
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.view.PassKeyView
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class PasswordGeneratorFragment : DatabaseFragment() {
private lateinit var passKeyView: PassKeyView
private lateinit var sliderLength: Slider
private lateinit var lengthEditView: EditText
private lateinit var uppercaseCompound: CompoundButton
private lateinit var lowercaseCompound: CompoundButton
private lateinit var digitsCompound: CompoundButton
private lateinit var minusCompound: CompoundButton
private lateinit var underlineCompound: CompoundButton
private lateinit var spaceCompound: CompoundButton
private lateinit var specialsCompound: CompoundButton
private lateinit var bracketsCompound: CompoundButton
private lateinit var extendedCompound: CompoundButton
private lateinit var considerCharsEditText: EditText
private lateinit var ignoreCharsEditText: EditText
private lateinit var atLeastOneCompound: CompoundButton
private lateinit var excludeAmbiguousCompound: CompoundButton
private var minLengthSlider: Int = 0
private var maxLengthSlider: Int = 0
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_generate_password, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
passKeyView = view.findViewById(R.id.password_view)
val passwordCopyView: ImageView? = view.findViewById(R.id.password_copy_button)
sliderLength = view.findViewById(R.id.slider_length)
lengthEditView = view.findViewById(R.id.length)
uppercaseCompound = view.findViewById(R.id.upperCase_filter)
lowercaseCompound = view.findViewById(R.id.lowerCase_filter)
digitsCompound = view.findViewById(R.id.digits_filter)
minusCompound = view.findViewById(R.id.minus_filter)
underlineCompound = view.findViewById(R.id.underline_filter)
spaceCompound = view.findViewById(R.id.space_filter)
specialsCompound = view.findViewById(R.id.special_filter)
bracketsCompound = view.findViewById(R.id.brackets_filter)
extendedCompound = view.findViewById(R.id.extendedASCII_filter)
considerCharsEditText = view.findViewById(R.id.consider_chars_filter)
ignoreCharsEditText = view.findViewById(R.id.ignore_chars_filter)
atLeastOneCompound = view.findViewById(R.id.atLeastOne_filter)
excludeAmbiguousCompound = view.findViewById(R.id.excludeAmbiguous_filter)
contextThemed?.let { context ->
passwordCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context))
View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(context)
passwordCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(passKeyView.passwordString,
getString(R.string.copy_field,
getString(R.string.entry_password)))
}
}
minLengthSlider = resources.getInteger(R.integer.password_generator_length_min)
maxLengthSlider = resources.getInteger(R.integer.password_generator_length_max)
loadSettings()
uppercaseCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
lowercaseCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
digitsCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
minusCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
underlineCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
spaceCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
specialsCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
bracketsCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
extendedCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
considerCharsEditText.doOnTextChanged { _, _, _, _ ->
generatePassword()
}
ignoreCharsEditText.doOnTextChanged { _, _, _, _ ->
generatePassword()
}
atLeastOneCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
excludeAmbiguousCompound.setOnCheckedChangeListener { _, _ ->
generatePassword()
}
var listenSlider = true
var listenEditText = true
sliderLength.addOnChangeListener { _, value, _ ->
try {
listenEditText = false
if (listenSlider) {
lengthEditView.setText(value.toInt().toString())
}
} catch (e: Exception) {
Log.e(TAG, "Unable to set the length value", e)
} finally {
listenEditText = true
}
}
sliderLength.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
// TODO upgrade material-components lib
// https://stackoverflow.com/questions/70873160/material-slider-onslidertouchlisteners-methods-can-only-be-called-from-within-t
@SuppressLint("RestrictedApi")
override fun onStartTrackingTouch(slider: Slider) {}
@SuppressLint("RestrictedApi")
override fun onStopTrackingTouch(slider: Slider) {
generatePassword()
}
})
lengthEditView.doOnTextChanged { _, _, _, _ ->
if (listenEditText) {
try {
listenSlider = false
setSliderValue(getPasswordLength())
} catch (e: Exception) {
Log.e(TAG, "Unable to get the length value", e)
} finally {
listenSlider = true
generatePassword()
}
}
}
// Pre-populate a password to possibly save the user a few clicks
generatePassword()
mKeyGeneratorViewModel.passwordGeneratedValidated.observe(viewLifecycleOwner) {
mKeyGeneratorViewModel.setKeyGenerated(passKeyView.passwordString)
}
mKeyGeneratorViewModel.requirePasswordGeneration.observe(viewLifecycleOwner) {
generatePassword()
}
resetAppTimeoutWhenViewFocusedOrChanged(view)
}
private fun getPasswordLength(): Int {
return try {
Integer.valueOf(lengthEditView.text.toString())
} catch (numberException: NumberFormatException) {
minLengthSlider
}
}
private fun setPasswordLength(passwordLength: Int) {
setSliderValue(passwordLength)
lengthEditView.setText(passwordLength.toString())
}
private fun getOptions(): Set<String> {
val optionsSet = mutableSetOf<String>()
if (uppercaseCompound.isChecked)
optionsSet.add(getString(R.string.value_password_uppercase))
if (lowercaseCompound.isChecked)
optionsSet.add(getString(R.string.value_password_lowercase))
if (digitsCompound.isChecked)
optionsSet.add(getString(R.string.value_password_digits))
if (minusCompound.isChecked)
optionsSet.add(getString(R.string.value_password_minus))
if (underlineCompound.isChecked)
optionsSet.add(getString(R.string.value_password_underline))
if (spaceCompound.isChecked)
optionsSet.add(getString(R.string.value_password_space))
if (specialsCompound.isChecked)
optionsSet.add(getString(R.string.value_password_special))
if (bracketsCompound.isChecked)
optionsSet.add(getString(R.string.value_password_brackets))
if (extendedCompound.isChecked)
optionsSet.add(getString(R.string.value_password_extended))
if (atLeastOneCompound.isChecked)
optionsSet.add(getString(R.string.value_password_atLeastOne))
if (excludeAmbiguousCompound.isChecked)
optionsSet.add(getString(R.string.value_password_excludeAmbiguous))
return optionsSet
}
private fun setOptions(options: Set<String>) {
uppercaseCompound.isChecked = false
lowercaseCompound.isChecked = false
digitsCompound.isChecked = false
minusCompound.isChecked = false
underlineCompound.isChecked = false
spaceCompound.isChecked = false
specialsCompound.isChecked = false
bracketsCompound.isChecked = false
extendedCompound.isChecked = false
atLeastOneCompound.isChecked = false
excludeAmbiguousCompound.isChecked = false
for (option in options) {
when (option) {
getString(R.string.value_password_uppercase) -> uppercaseCompound.isChecked = true
getString(R.string.value_password_lowercase) -> lowercaseCompound.isChecked = true
getString(R.string.value_password_digits) -> digitsCompound.isChecked = true
getString(R.string.value_password_minus) -> minusCompound.isChecked = true
getString(R.string.value_password_underline) -> underlineCompound.isChecked = true
getString(R.string.value_password_space) -> spaceCompound.isChecked = true
getString(R.string.value_password_special) -> specialsCompound.isChecked = true
getString(R.string.value_password_brackets) -> bracketsCompound.isChecked = true
getString(R.string.value_password_extended) -> extendedCompound.isChecked = true
getString(R.string.value_password_atLeastOne) -> atLeastOneCompound.isChecked = true
getString(R.string.value_password_excludeAmbiguous) -> excludeAmbiguousCompound.isChecked = true
}
}
}
private fun getConsiderChars(): String {
return considerCharsEditText.text.toString()
}
private fun setConsiderChars(chars: String) {
considerCharsEditText.setText(chars)
}
private fun getIgnoreChars(): String {
return ignoreCharsEditText.text.toString()
}
private fun setIgnoreChars(chars: String) {
ignoreCharsEditText.setText(chars)
}
private fun generatePassword() {
var password = ""
try {
password = PasswordGenerator(resources).generatePassword(getPasswordLength(),
uppercaseCompound.isChecked,
lowercaseCompound.isChecked,
digitsCompound.isChecked,
minusCompound.isChecked,
underlineCompound.isChecked,
spaceCompound.isChecked,
specialsCompound.isChecked,
bracketsCompound.isChecked,
extendedCompound.isChecked,
getConsiderChars(),
getIgnoreChars(),
atLeastOneCompound.isChecked,
excludeAmbiguousCompound.isChecked)
} catch (e: Exception) {
Log.e(TAG, "Unable to generate a password", e)
}
passKeyView.passwordString = password
}
override fun onDestroy() {
saveSettings()
super.onDestroy()
}
override fun onDatabaseRetrieved(database: Database?) {
// Nothing here
}
private fun saveSettings() {
context?.let { context ->
PreferencesUtil.setDefaultPasswordOptions(context, getOptions())
PreferencesUtil.setDefaultPasswordLength(context, getPasswordLength())
PreferencesUtil.setDefaultPasswordConsiderChars(context, getConsiderChars())
PreferencesUtil.setDefaultPasswordIgnoreChars(context, getIgnoreChars())
}
}
private fun loadSettings() {
context?.let { context ->
setOptions(PreferencesUtil.getDefaultPasswordOptions(context))
setPasswordLength(PreferencesUtil.getDefaultPasswordLength(context))
setConsiderChars(PreferencesUtil.getDefaultPasswordConsiderChars(context))
setIgnoreChars(PreferencesUtil.getDefaultPasswordIgnoreChars(context))
}
}
private fun setSliderValue(value: Int) {
when {
value < minLengthSlider -> {
sliderLength.value = minLengthSlider.toFloat()
}
value > maxLengthSlider -> {
sliderLength.value = maxLengthSlider.toFloat()
}
else -> {
sliderLength.value = value.toFloat()
}
}
}
companion object {
private const val TAG = "PasswordGeneratorFrgmt"
}
}

View File

@@ -85,7 +85,7 @@ object EntrySelectionHelper {
return intent.getParcelableExtra(KEY_SEARCH_INFO)
}
fun addRegisterInfoInIntent(intent: Intent, registerInfo: RegisterInfo?) {
private fun addRegisterInfoInIntent(intent: Intent, registerInfo: RegisterInfo?) {
registerInfo?.let {
intent.putExtra(KEY_REGISTER_INFO, it)
}
@@ -113,7 +113,7 @@ object EntrySelectionHelper {
?: SpecialMode.DEFAULT
}
fun addTypeModeInIntent(intent: Intent, typeMode: TypeMode) {
private fun addTypeModeInIntent(intent: Intent, typeMode: TypeMode) {
intent.putExtra(KEY_TYPE_MODE, typeMode as Serializable)
}

View File

@@ -24,12 +24,15 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DatabaseDialogFragment
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
@@ -431,7 +434,19 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
}
protected fun lockAndExit() {
sendBroadcast(Intent(LOCK_ACTION))
// Ask confirmation if modification not saved
if (mDatabase?.isReadOnly == false
&& mDatabase?.dataModifiedSinceLastLoading == true
&& !PreferencesUtil.isAutoSaveDatabaseEnabled(this)) {
AlertDialog.Builder(this)
.setMessage(R.string.discard_changes)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.lock) { _, _ ->
sendBroadcast(Intent(LOCK_ACTION))
}.create().show()
} else {
sendBroadcast(Intent(LOCK_ACTION))
}
}
fun resetAppTimeout() {
@@ -467,25 +482,33 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
*/
@SuppressLint("ClickableViewAccessibility")
fun View.resetAppTimeoutWhenViewTouchedOrFocused(context: Context, databaseLoaded: Boolean?) {
// Log.d(DatabaseLockActivity.TAG, "View prepared to reset app timeout")
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Log.d(DatabaseLockActivity.TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false)
try {
// Log.d(DatabaseLockActivity.TAG, "View prepared to reset app timeout")
setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Log.d(DatabaseLockActivity.TAG, "View touched, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(
context,
databaseLoaded ?: false
)
}
}
false
}
setOnFocusChangeListener { _, _ ->
// Log.d(DatabaseLockActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(
context,
databaseLoaded ?: false
)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded)
}
}
false
}
setOnFocusChangeListener { _, _ ->
// Log.d(DatabaseLockActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded)
}
} catch (e: Exception) {
Log.e("AppTimeout", "Unable to reset app timeout", e)
}
}

View File

@@ -1,5 +1,6 @@
package com.kunzisoft.keepass.activities.legacy
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@@ -95,11 +96,9 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
private fun backToTheMainAppAndFinish() {
// To move the app in background and return to the main app
// Not visible as opened with FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
moveTaskToBack(true)
// To remove this instance in the OS app selector
Handler(Looper.getMainLooper()).postDelayed({
finish()
}, 500)
// Not finish() to prevent service kill
}
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -0,0 +1,25 @@
package com.kunzisoft.keepass.adapters
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.kunzisoft.keepass.activities.fragments.PassphraseGeneratorFragment
import com.kunzisoft.keepass.activities.fragments.PasswordGeneratorFragment
import com.kunzisoft.keepass.activities.fragments.KeyGeneratorFragment
class KeyGeneratorPagerAdapter(fragment: Fragment)
: FragmentStateAdapter(fragment) {
private val passwordGeneratorFragment = PasswordGeneratorFragment()
private val passphraseGeneratorFragment = PassphraseGeneratorFragment()
override fun getItemCount(): Int {
return KeyGeneratorFragment.KeyGeneratorTab.values().size
}
override fun createFragment(position: Int): Fragment {
return when (KeyGeneratorFragment.KeyGeneratorTab.getKeyGeneratorTabByPosition(position)) {
KeyGeneratorFragment.KeyGeneratorTab.PASSWORD -> passwordGeneratorFragment
KeyGeneratorFragment.KeyGeneratorTab.PASSPHRASE -> passphraseGeneratorFragment
}
}
}

View File

@@ -516,7 +516,7 @@ class NodesAdapter (private val context: Context,
null -> {}
}
holder?.otpToken?.apply {
text = otpElement?.token
text = otpElement?.tokenString
setTextSize(mTextSizeUnit, mOtpTokenTextDefaultDimension, mPrefSizeMultiplier)
}
holder?.otpContainer?.setOnClickListener {

View File

@@ -26,7 +26,7 @@ import kotlinx.coroutines.*
*/
class IOActionTask<T>(
private val action: () -> T ,
private val afterActionDatabaseListener: ((T?) -> Unit)? = null) {
private val afterActionListener: ((T?) -> Unit)? = null) {
private val mainScope = CoroutineScope(Dispatchers.Main)
@@ -42,7 +42,7 @@ class IOActionTask<T>(
}
}
withContext(Dispatchers.Main) {
afterActionDatabaseListener?.invoke(asyncResult.await())
afterActionListener?.invoke(asyncResult.await())
}
}
}

View File

@@ -45,6 +45,8 @@ import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.CredentialStorage
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -579,10 +581,13 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
private fun showViews(show: Boolean) {
lifecycleScope.launch(Dispatchers.Main) {
mAdvancedUnlockInfoView?.visibility = if (show)
View.VISIBLE
if (show) {
if (mAdvancedUnlockInfoView?.visibility != View.VISIBLE)
mAdvancedUnlockInfoView?.showByFading()
}
else {
View.GONE
if (mAdvancedUnlockInfoView?.visibility == View.VISIBLE)
mAdvancedUnlockInfoView?.hideByFading()
}
}
}
@@ -608,26 +613,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
}
fun performEducation(passwordActivityEducation: PasswordActivityEducation,
readOnlyEducationPerformed: Boolean,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) {
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
PreferencesUtil.isAdvancedUnlockEnable(requireContext())
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
&& mAdvancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
onEducationViewClick,
onOuterViewClick)
}
} catch (ignored: Exception) {}
}
enum class Mode {
BIOMETRIC_UNAVAILABLE,
BIOMETRIC_SECURITY_UPDATE_REQUIRED,

View File

@@ -124,7 +124,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
private fun getSecretKey(): SecretKey? {
@Synchronized private fun getSecretKey(): SecretKey? {
if (!isKeyManagerInitialized) {
return null
}
@@ -141,8 +141,8 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
KeyGenParameterSpec.Builder(
ADVANCED_UNLOCK_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setBlockModes(ADVANCED_UNLOCK_BLOCKS_MODES)
.setEncryptionPaddings(ADVANCED_UNLOCK_ENCRYPTION_PADDING)
.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
@@ -173,11 +173,11 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
return null
}
fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,) {
@Synchronized fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,) {
initEncryptData(actionIfCypherInit, true)
}
private fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
@Synchronized private fun initEncryptData(actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
firstLaunch: Boolean) {
if (!isKeyManagerInitialized) {
return
@@ -213,7 +213,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun encryptData(value: ByteArray) {
@Synchronized fun encryptData(value: ByteArray) {
if (!isKeyManagerInitialized) {
return
}
@@ -229,12 +229,12 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun initDecryptData(ivSpecValue: ByteArray,
@Synchronized fun initDecryptData(ivSpecValue: ByteArray,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
initDecryptData(ivSpecValue, actionIfCypherInit, true)
}
private fun initDecryptData(ivSpecValue: ByteArray,
@Synchronized private fun initDecryptData(ivSpecValue: ByteArray,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
firstLaunch: Boolean = true) {
if (!isKeyManagerInitialized) {
@@ -278,7 +278,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun decryptData(encryptedValue: ByteArray) {
@Synchronized fun decryptData(encryptedValue: ByteArray) {
if (!isKeyManagerInitialized) {
return
}
@@ -296,7 +296,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun deleteKeystoreKey() {
@Synchronized fun deleteKeystoreKey() {
try {
keyStore?.load(null)
keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
@@ -306,7 +306,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt,
@Synchronized fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt,
deviceCredentialResultLauncher: ActivityResultLauncher<Intent>
) {
// Init advanced unlock prompt
@@ -346,7 +346,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
fun closeBiometricPrompt() {
@Synchronized fun closeBiometricPrompt() {
biometricPrompt?.cancelAuthentication()
}

View File

@@ -248,7 +248,7 @@ class DatabaseTaskProvider {
private fun bindService() {
initServiceConnection()
serviceConnection?.let {
context.bindService(intentDatabaseTask, it, BIND_AUTO_CREATE or BIND_NOT_FOREGROUND or BIND_ABOVE_CLIENT)
context.bindService(intentDatabaseTask, it, BIND_AUTO_CREATE or BIND_IMPORTANT or BIND_ABOVE_CLIENT)
}
}

View File

@@ -913,53 +913,24 @@ class Database {
try {
val saveUri = databaseCopyUri ?: this.fileUri
if (saveUri != null) {
if (saveUri.scheme == "file") {
saveUri.path?.let { filename ->
val tempFile = File("$filename.tmp")
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(tempFile)
val pmo = mDatabaseKDB?.let { DatabaseOutputKDB(it, fileOutputStream) }
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, fileOutputStream) }
pmo?.output()
} catch (e: Exception) {
throw IOException(e)
} finally {
fileOutputStream?.close()
}
// Force data to disk before continuing
try {
fileOutputStream?.fd?.sync()
} catch (e: SyncFailedException) {
// Ignore if fsync fails. We tried.
}
if (!tempFile.renameTo(File(filename))) {
throw IOException()
}
}
} else {
var outputStream: OutputStream? = null
try {
outputStream = contentResolver.openOutputStream(saveUri, "rwt")
outputStream?.let { definedOutputStream ->
val databaseOutput =
mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
?: mDatabaseKDBX?.let {
DatabaseOutputKDBX(
it,
definedOutputStream
)
}
databaseOutput?.output()
}
} catch (e: Exception) {
throw IOException(e)
} finally {
outputStream?.close()
var outputStream: OutputStream? = null
try {
outputStream = UriUtil.getUriOutputStream(contentResolver, saveUri)
outputStream?.let { definedOutputStream ->
val databaseOutput =
mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
?: mDatabaseKDBX?.let {
DatabaseOutputKDBX(
it,
definedOutputStream
)
}
databaseOutput?.output()
}
} catch (e: Exception) {
throw IOException(e)
} finally {
outputStream?.close()
}
if (databaseCopyUri == null) {
this.dataModifiedSinceLastLoading = false

View File

@@ -60,6 +60,11 @@ object TemplateField {
const val LABEL_SECURE_NOTE = "Secure Note"
const val LABEL_MEMBERSHIP = "Membership"
fun isStandardPasswordName(context: Context, name: String): Boolean {
return name.equals(LABEL_PASSWORD, true)
|| name == getLocalizedName(context, LABEL_PASSWORD)
}
fun isStandardFieldName(name: String): Boolean {
return arrayOf(
LABEL_TITLE,

View File

@@ -148,9 +148,9 @@ class SearchHelper {
onDatabaseClosed.invoke()
} else if (TimeoutHelper.checkTime(context)) {
var searchWithoutUI = false
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
&& searchInfo != null && !searchInfo.manualSelection
&& !searchInfo.containsOnlyNullValues()) {
if (searchInfo != null
&& !searchInfo.manualSelection
&& !searchInfo.containsOnlyNullValues()) {
// If search provide results
database.createVirtualGroupFromSearchInfo(
searchInfo.toString(),
@@ -181,7 +181,7 @@ class SearchHelper {
return false
// Exclude entry expired
if (searchParameters.excludeExpired) {
if (!searchParameters.searchInExpired) {
if (entry.isCurrentlyExpires)
return false
}
@@ -237,14 +237,20 @@ class SearchHelper {
return false
return if (searchParameters.isRegex) {
val regex = if (searchParameters.caseSensitive) {
searchParameters.searchQuery.toRegex(RegexOption.DOT_MATCHES_ALL)
searchParameters.searchQuery
.toRegex(RegexOption.DOT_MATCHES_ALL)
} else {
searchParameters.searchQuery
.toRegex(setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE))
}
regex.matches(stringToCheck)
} else {
stringToCheck.contains(searchParameters.searchQuery, !searchParameters.caseSensitive)
var searchFound = true
searchParameters.searchQuery.split(" ").forEach { word ->
searchFound = searchFound
&& stringToCheck.contains(word, !searchParameters.caseSensitive)
}
searchFound
}
}
}

View File

@@ -34,7 +34,7 @@ class SearchParameters() : Parcelable{
var searchInUsernames = true
var searchInPasswords = false
var searchInUrls = true
var excludeExpired = false
var searchInExpired = false
var searchInNotes = true
var searchInOTP = false
var searchInOther = true
@@ -49,11 +49,12 @@ class SearchParameters() : Parcelable{
constructor(parcel: Parcel) : this() {
searchQuery = parcel.readString() ?: searchQuery
caseSensitive = parcel.readByte() != 0.toByte()
isRegex = parcel.readByte() != 0.toByte()
searchInTitles = parcel.readByte() != 0.toByte()
searchInUsernames = parcel.readByte() != 0.toByte()
searchInPasswords = parcel.readByte() != 0.toByte()
searchInUrls = parcel.readByte() != 0.toByte()
excludeExpired = parcel.readByte() != 0.toByte()
searchInExpired = parcel.readByte() != 0.toByte()
searchInNotes = parcel.readByte() != 0.toByte()
searchInOTP = parcel.readByte() != 0.toByte()
searchInOther = parcel.readByte() != 0.toByte()
@@ -68,11 +69,12 @@ class SearchParameters() : Parcelable{
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(searchQuery)
parcel.writeByte(if (caseSensitive) 1 else 0)
parcel.writeByte(if (isRegex) 1 else 0)
parcel.writeByte(if (searchInTitles) 1 else 0)
parcel.writeByte(if (searchInUsernames) 1 else 0)
parcel.writeByte(if (searchInPasswords) 1 else 0)
parcel.writeByte(if (searchInUrls) 1 else 0)
parcel.writeByte(if (excludeExpired) 1 else 0)
parcel.writeByte(if (searchInExpired) 1 else 0)
parcel.writeByte(if (searchInNotes) 1 else 0)
parcel.writeByte(if (searchInOTP) 1 else 0)
parcel.writeByte(if (searchInOther) 1 else 0)

View File

@@ -18,6 +18,8 @@ package com.kunzisoft.keepass.magikeyboard;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_BACK_KEYBOARD;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_CHANGE_KEYBOARD;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_ENTRY;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_ENTRY_ALT;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_OTP;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_OTP_ALT;
@@ -1049,6 +1051,9 @@ public class KeyboardView extends View implements View.OnClickListener {
if (popupKey.codes[0] == KEY_BACK_KEYBOARD) {
mKeyboardActionListener.onKey(KEY_CHANGE_KEYBOARD, popupKey.codes);
return true;
} else if (popupKey.codes[0] == KEY_ENTRY) {
mKeyboardActionListener.onKey(KEY_ENTRY_ALT, popupKey.codes);
return true;
} else if (popupKey.codes[0] == KEY_OTP) {
mKeyboardActionListener.onKey(KEY_OTP_ALT, popupKey.codes);
return true;

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.magikeyboard
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.inputmethodservice.InputMethodService
@@ -30,19 +31,28 @@ import android.view.*
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.PopupWindow
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity
import com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.adapters.FieldsAdapter
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.*
import java.util.*
class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionListener {
@@ -50,13 +60,19 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
private var mDatabase: Database? = null
private var keyboardView: KeyboardView? = null
private var entryContainer: View? = null
private var entryText: TextView? = null
private var databaseText: TextView? = null
private var databaseColorView: ImageView? = null
private var packageText: TextView? = null
private var keyboard: Keyboard? = null
private var keyboardEntry: Keyboard? = null
private var popupCustomKeys: PopupWindow? = null
private var fieldsAdapter: FieldsAdapter? = null
private var playSoundDuringCLick: Boolean = false
private var mFormPackageName: String? = null
private var lockReceiver: LockReceiver? = null
override fun onCreate() {
@@ -66,6 +82,7 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
mDatabaseTaskProvider?.registerProgressTask()
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
this.mDatabase = database
assignKeyboardView()
}
// Remove the entry and lock the keyboard when the lock signal is receive
lockReceiver = LockReceiver {
@@ -82,7 +99,11 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
override fun onCreateInputView(): View {
val rootKeyboardView = layoutInflater.inflate(R.layout.keyboard_container, null)
entryContainer = rootKeyboardView.findViewById(R.id.magikeyboard_entry_container)
entryText = rootKeyboardView.findViewById(R.id.magikeyboard_entry_text)
databaseText = rootKeyboardView.findViewById(R.id.magikeyboard_database_text)
databaseColorView = rootKeyboardView.findViewById(R.id.magikeyboard_database_color)
packageText = rootKeyboardView.findViewById(R.id.magikeyboard_package_text)
keyboardView = rootKeyboardView.findViewById(R.id.magikeyboard_view)
if (keyboardView != null) {
@@ -94,7 +115,7 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
.inflate(R.layout.keyboard_popup_fields, FrameLayout(context))
popupCustomKeys = PopupWindow(context).apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.WRAP_CONTENT
inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
contentView = popupFieldsView
@@ -104,7 +125,7 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
fieldsAdapter = FieldsAdapter(this)
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) {
currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1)
currentInputConnection.commitText(getEntryInfo()?.getGeneratedFieldValue(item.name) , 1)
actionTabAutomatically()
}
}
@@ -114,17 +135,6 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close)
closeView.setOnClickListener { popupCustomKeys?.dismiss() }
// Remove entry info if the database is not loaded
// or if entry info timestamp is before database loaded timestamp
val databaseTime = mDatabase?.loadTimestamp
val entryTime = entryInfoTimestamp
if (mDatabase == null
|| mDatabase?.loaded != true
|| databaseTime == null
|| entryTime == null
|| entryTime < databaseTime) {
removeEntryInfo()
}
assignKeyboardView()
keyboardView?.onKeyboardActionListener = this
@@ -134,17 +144,27 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
return super.onCreateInputView()
}
private fun getEntryInfo(): EntryInfo? {
var entryInfoRetrieved: EntryInfo? = null
entryUUID?.let { entryId ->
entryInfoRetrieved = mDatabase
?.getEntryById(NodeIdUUID(entryId))
?.getEntryInfo(mDatabase)
}
return entryInfoRetrieved
}
private fun assignKeyboardView() {
dismissCustomKeys()
if (keyboardView != null) {
if (entryInfoKey != null) {
val entryInfo = getEntryInfo()
populateEntryInfoInView(entryInfo)
if (entryInfo != null) {
if (keyboardEntry != null) {
populateEntryInfoInView()
keyboardView?.keyboard = keyboardEntry
}
} else {
if (keyboard != null) {
hideEntryInfo()
keyboardView?.keyboard = keyboard
}
}
@@ -153,23 +173,45 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
keyboardView?.isHapticFeedbackEnabled = PreferencesUtil.isKeyboardVibrationEnable(this)
playSoundDuringCLick = PreferencesUtil.isKeyboardSoundEnable(this)
}
setDatabaseViews()
}
private fun populateEntryInfoInView() {
entryText?.visibility = View.VISIBLE
if (entryInfoKey?.title?.isNotEmpty() == true) {
entryText?.text = entryInfoKey?.title
private fun setDatabaseViews() {
if (mDatabase == null || mDatabase?.loaded != true) {
entryContainer?.visibility = View.GONE
} else {
hideEntryInfo()
entryContainer?.visibility = View.VISIBLE
}
databaseText?.text = mDatabase?.name ?: ""
val databaseColor = mDatabase?.customColor
if (databaseColor != null) {
databaseColorView?.drawable?.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(databaseColor, BlendModeCompat.SRC_IN)
databaseColorView?.visibility = View.VISIBLE
} else {
databaseColorView?.visibility = View.GONE
}
}
private fun hideEntryInfo() {
entryText?.visibility = View.GONE
private fun populateEntryInfoInView(entryInfo: EntryInfo?) {
if (entryInfo == null) {
entryText?.text = ""
entryText?.visibility = View.GONE
} else {
entryText?.text = entryInfo.getVisualTitle()
entryText?.visibility = View.VISIBLE
}
}
override fun onStartInputView(info: EditorInfo, restarting: Boolean) {
super.onStartInputView(info, restarting)
mFormPackageName = info.packageName
if (!mFormPackageName.isNullOrEmpty()) {
packageText?.text = mFormPackageName
packageText?.visibility = View.VISIBLE
} else {
packageText?.visibility = View.GONE
}
assignKeyboardView()
}
@@ -228,16 +270,17 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
(getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?)
?.showInputMethodPicker()
}
KEY_UNLOCK -> {
}
KEY_ENTRY -> {
// Stop current service and reinit entry
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
removeEntryInfo()
val intent = Intent(this, MagikeyboardLauncherActivity::class.java)
// New task needed because don't launch from an Activity context
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
var searchInfo: SearchInfo? = null
if (mFormPackageName != null) {
searchInfo = SearchInfo().apply {
applicationId = mFormPackageName
}
}
actionKeyEntry(searchInfo)
}
KEY_ENTRY_ALT -> {
actionKeyEntry()
}
KEY_LOCK -> {
removeEntryInfo()
@@ -245,12 +288,13 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
dismissCustomKeys()
}
KEY_USERNAME -> {
entryInfoKey?.username?.let { username ->
getEntryInfo()?.username?.let { username ->
currentInputConnection.commitText(username, 1)
}
actionTabAutomatically()
}
KEY_PASSWORD -> {
val entryInfoKey = getEntryInfo()
entryInfoKey?.password?.let { password ->
currentInputConnection.commitText(password, 1)
}
@@ -258,14 +302,14 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
actionGoAutomatically(!otpFieldExists)
}
KEY_OTP -> {
entryInfoKey?.let { entryInfo ->
getEntryInfo()?.let { entryInfo ->
currentInputConnection.commitText(
entryInfo.getGeneratedFieldValue(OTP_TOKEN_FIELD), 1)
}
actionGoAutomatically()
}
KEY_OTP_ALT -> {
entryInfoKey?.let { entryInfo ->
getEntryInfo()?.let { entryInfo ->
val otpToken = entryInfo.getGeneratedFieldValue(OTP_TOKEN_FIELD)
if (otpToken.isNotEmpty()) {
// Cut to fill each digit separatelyKeyEvent.KEYCODE_TAB
@@ -282,19 +326,20 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
actionGoAutomatically()
}
KEY_URL -> {
entryInfoKey?.url?.let { url ->
getEntryInfo()?.url?.let { url ->
currentInputConnection.commitText(url, 1)
}
actionGoAutomatically()
}
KEY_FIELDS -> {
entryInfoKey?.customFields?.let { customFields ->
getEntryInfo()?.customFields?.let { customFields ->
fieldsAdapter?.apply {
setFields(customFields.filter { it.name != OTP_TOKEN_FIELD})
notifyDataSetChanged()
}
}
popupCustomKeys?.showAtLocation(keyboardView, Gravity.END or Gravity.TOP, 0, 0)
popupCustomKeys?.showAtLocation(keyboardView,
Gravity.END or Gravity.TOP, 0, 180)
}
Keyboard.KEYCODE_DELETE -> {
inputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
@@ -303,6 +348,43 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
}
}
private fun actionKeyEntry(searchInfo: SearchInfo? = null) {
SearchHelper.checkAutoSearchInfo(this,
mDatabase,
searchInfo,
{ _, items ->
performSelection(
items,
{
// Automatically populate keyboard
addEntryAndLaunchNotificationIfAllowed(
this,
items[0],
true
)
assignKeyboardView()
},
{
launchEntrySelection(searchInfo)
}
)
},
{
// Select if not found
launchEntrySelection(searchInfo)
},
{
// Select if database not opened
removeEntryInfo()
launchEntrySelection(searchInfo)
}
)
}
private fun launchEntrySelection(searchInfo: SearchInfo?) {
EntrySelectionLauncherActivity.launch(this, searchInfo)
}
private fun actionTabAutomatically() {
if (PreferencesUtil.isAutoGoActionEnable(this))
currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB))
@@ -359,23 +441,20 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
const val KEY_BACK_KEYBOARD = 600
const val KEY_CHANGE_KEYBOARD = 601
private const val KEY_UNLOCK = 610
private const val KEY_LOCK = 611
private const val KEY_ENTRY = 620
private const val KEY_USERNAME = 500
private const val KEY_PASSWORD = 510
const val KEY_LOCK = 611
const val KEY_ENTRY = 620
const val KEY_ENTRY_ALT = 621
const val KEY_USERNAME = 500
const val KEY_PASSWORD = 510
const val KEY_OTP = 515
const val KEY_OTP_ALT = 516
private const val KEY_URL = 520
private const val KEY_FIELDS = 530
const val KEY_URL = 520
const val KEY_FIELDS = 530
// TODO Retrieve entry info from id and service when database is open
private var entryInfoKey: EntryInfo? = null
private var entryInfoTimestamp: Long? = null
private var entryUUID: UUID? = null
private fun removeEntryInfo() {
entryInfoKey = null
entryInfoTimestamp = null
entryUUID = null
}
fun removeEntry(context: Context) {
@@ -384,10 +463,47 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
fun addEntryAndLaunchNotificationIfAllowed(context: Context, entry: EntryInfo, toast: Boolean = false) {
// Add a new entry
entryInfoKey = entry
entryInfoTimestamp = System.currentTimeMillis()
entryUUID = entry.id
// Launch notification if allowed
KeyboardEntryNotificationService.launchNotificationIfAllowed(context, entry, toast)
}
fun activatedInSettings(context: Context): Boolean {
return ContextCompat.getSystemService(context, InputMethodManager::class.java)
?.enabledInputMethodList
?.any {
it.packageName == context.packageName
} ?: false
}
fun performSelection(items: List<EntryInfo>,
actionPopulateKeyboard: (entryInfo: EntryInfo) -> Unit,
actionEntrySelection: (autoSearch: Boolean) -> Unit) {
if (items.size == 1) {
val itemFound = items[0]
if (entryUUID != itemFound.id) {
actionPopulateKeyboard.invoke(itemFound)
} else {
// Force selection if magikeyboard already populated
actionEntrySelection.invoke(false)
}
} else if (items.size > 1) {
// Select the one we want in the selection
actionEntrySelection.invoke(true)
} else {
// Select an arbitrary one
actionEntrySelection.invoke(false)
}
}
fun populateKeyboardAndMoveAppToBackground(activity: Activity,
entry: EntryInfo,
toast: Boolean = true) {
// Populate Magikeyboard with entry
addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
// Consume the selection mode
EntrySelectionHelper.removeModesFromIntent(activity.intent)
activity.moveTaskToBack(true)
}
}
}

View File

@@ -225,6 +225,14 @@ class EntryInfo : NodeInfo {
}
}
fun getVisualTitle(): String {
return title.ifEmpty {
url.ifEmpty {
username.ifEmpty { id.toString() }
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is EntryInfo) return false

View File

@@ -178,6 +178,14 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
}
}
/**
* Token with space each 3 digits
*/
val tokenString: String
get() {
return token.replace("...".toRegex(), "$0 ")
}
val secondsRemaining: Int
get() = otpModel.period - (System.currentTimeMillis() / 1000 % otpModel.period).toInt()

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2019 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.password
import me.gosimple.nbvcxz.resources.Generator
class PassphraseGenerator {
@Throws(IllegalArgumentException::class)
fun generatePassphrase(wordCount: Int,
wordSeparator: String,
wordCase: WordCase): String {
// From eff_large dictionary
return when (wordCase) {
WordCase.LOWER_CASE -> {
Generator.generatePassphrase(wordSeparator, wordCount)
}
WordCase.UPPER_CASE -> {
applyWordCase(wordCount, wordSeparator) { word ->
word.uppercase()
}
}
WordCase.TITLE_CASE -> {
applyWordCase(wordCount, wordSeparator) { word ->
word.replaceFirstChar { char -> char.uppercaseChar() }
}
}
}
}
private fun applyWordCase(wordCount: Int,
wordSeparator: String,
wordAction: (word: String) -> String): String {
val splitWords = Generator.generatePassphrase(TEMP_SPLIT, wordCount).split(TEMP_SPLIT)
val stringBuilder = StringBuilder()
splitWords.forEach {
stringBuilder
.append(wordAction(it))
.append(wordSeparator)
}
return stringBuilder.toString().removeSuffix(wordSeparator)
}
enum class WordCase {
LOWER_CASE,
UPPER_CASE,
TITLE_CASE;
companion object {
fun getByOrdinal(position: Int): WordCase {
return when (position) {
0 -> LOWER_CASE
1 -> UPPER_CASE
else -> TITLE_CASE
}
}
}
}
companion object {
private const val TEMP_SPLIT = "-"
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2022 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.password
import android.content.res.Resources
import android.graphics.Color
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.IOActionTask
import kotlinx.coroutines.*
import me.gosimple.nbvcxz.Nbvcxz
import me.gosimple.nbvcxz.resources.Configuration
import me.gosimple.nbvcxz.resources.ConfigurationBuilder
import java.util.*
import kotlin.math.min
class PasswordEntropy(actionOnInitFinished: (() -> Unit)? = null) {
private var mPasswordEntropyCalculator: Nbvcxz? = null
private var entropyJob: Job? = null
init {
IOActionTask({
// Create the password generator object
val configuration: Configuration = ConfigurationBuilder()
.setLocale(Locale.getDefault())
.setMinimumEntropy(80.0)
.createConfiguration()
mPasswordEntropyCalculator = Nbvcxz(configuration)
}, {
actionOnInitFinished?.invoke()
}).execute()
}
enum class Strength(val color: Int) {
RISKY(Color.rgb(224, 56, 56)),
VERY_GUESSABLE(Color.rgb(196, 63, 49)),
SOMEWHAT_GUESSABLE(Color.rgb(219, 152, 55)),
SAFELY_UNGUESSABLE(Color.rgb(118, 168, 24)),
VERY_UNGUESSABLE(Color.rgb(37, 152, 41))
}
data class EntropyStrength(val strength: Strength,
val entropy: Double,
val estimationPercent: Int) {
override fun toString(): String {
return "EntropyStrength(strength=$strength, entropy=$entropy, estimationPercent=$estimationPercent)"
}
}
fun getEntropyStrength(passwordString: String,
entropyStrengthResult: (EntropyStrength) -> Unit) {
entropyStrengthResult.invoke(EntropyStrength(Strength.RISKY, CALCULATE_ENTROPY, 0))
entropyJob?.cancel()
entropyJob = CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.IO) {
val asyncResult: Deferred<EntropyStrength?> = async {
try {
if (passwordString.length <= MAX_PASSWORD_LENGTH) {
val estimate = mPasswordEntropyCalculator?.estimate(passwordString)
val basicScore = estimate?.basicScore ?: 0
val entropy = estimate?.entropy ?: 0.0
val percentScore = min(entropy * 100 / 200, 100.0).toInt()
val strength =
if (basicScore == 0 || percentScore < 10) {
Strength.RISKY
} else if (basicScore == 1 || percentScore < 20) {
Strength.VERY_GUESSABLE
} else if (basicScore == 2 || percentScore < 33) {
Strength.SOMEWHAT_GUESSABLE
} else if (basicScore == 3 || percentScore < 50) {
Strength.SAFELY_UNGUESSABLE
} else if (basicScore == 4) {
Strength.VERY_UNGUESSABLE
} else {
Strength.RISKY
}
EntropyStrength(strength, entropy, percentScore)
} else {
EntropyStrength(Strength.VERY_UNGUESSABLE, HIGH_ENTROPY, 100)
}
} catch (e: Exception) {
e.printStackTrace()
null
}
}
withContext(Dispatchers.Main) {
asyncResult.await()?.let { entropyStrength ->
entropyStrengthResult.invoke(entropyStrength)
}
}
}
}
}
companion object {
private const val MAX_PASSWORD_LENGTH = 128
private const val CALCULATE_ENTROPY = -1.0
private const val HIGH_ENTROPY = 1000.0
fun getStringEntropy(resources: Resources, entropy: Double): String {
return when (entropy) {
CALCULATE_ENTROPY -> {
resources.getString(R.string.entropy_calculate)
}
HIGH_ENTROPY -> {
resources.getString(R.string.entropy_high)
}
else -> {
resources.getString(
R.string.entropy,
"%.${2}f".format(entropy)
)
}
}
}
}
}

View File

@@ -20,33 +20,17 @@
package com.kunzisoft.keepass.password
import android.content.res.Resources
import android.graphics.Color
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import com.kunzisoft.keepass.R
import java.security.SecureRandom
import java.util.*
class PasswordGenerator(private val resources: Resources) {
// From KeePassXC code https://github.com/keepassxreboot/keepassxc/pull/538
private fun extendedChars(): String {
val charSet = StringBuilder()
// [U+0080, U+009F] are C1 control characters,
// U+00A0 is non-breaking space
run {
var ch = '\u00A1'
while (ch <= '\u00AC') {
charSet.append(ch)
++ch
}
}
// U+00AD is soft hyphen (format character)
var ch = '\u00AE'
while (ch < '\u00FF') {
charSet.append(ch)
++ch
}
charSet.append('\u00FF')
return charSet.toString()
}
@Throws(IllegalArgumentException::class)
fun generatePassword(length: Int,
upperCase: Boolean,
@@ -57,7 +41,11 @@ class PasswordGenerator(private val resources: Resources) {
space: Boolean,
specials: Boolean,
brackets: Boolean,
extended: Boolean): String {
extended: Boolean,
considerChars: String,
ignoreChars: String,
atLeastOneFromEach: Boolean,
excludeAmbiguousChar: Boolean): String {
// Desired password length is 0 or less
if (length <= 0) {
throw IllegalArgumentException(resources.getString(R.string.error_wrong_length))
@@ -72,74 +60,164 @@ class PasswordGenerator(private val resources: Resources) {
&& !space
&& !specials
&& !brackets
&& !extended) {
&& !extended
&& considerChars.isEmpty()) {
throw IllegalArgumentException(resources.getString(R.string.error_pass_gen_type))
}
val characterSet = getCharacterSet(
upperCase,
lowerCase,
digits,
minus,
underline,
space,
specials,
brackets,
extended)
val size = characterSet.length
val buffer = StringBuilder()
val random = SecureRandom() // use more secure variant of Random!
if (size > 0) {
for (i in 0 until length) {
buffer.append(characterSet[random.nextInt(size)])
// Filter builder
val passwordFilters = PasswordFilters().apply {
this.length = length
this.ignoreChars = ignoreChars
if (excludeAmbiguousChar)
this.ignoreChars += AMBIGUOUS_CHARS
if (upperCase) {
addFilter(
UPPERCASE_CHARS,
if (atLeastOneFromEach) 1 else 0
)
}
if (lowerCase) {
addFilter(
LOWERCASE_CHARS,
if (atLeastOneFromEach) 1 else 0
)
}
if (digits) {
addFilter(
DIGIT_CHARS,
if (atLeastOneFromEach) 1 else 0
)
}
if (minus) {
addFilter(
MINUS_CHAR,
if (atLeastOneFromEach) 1 else 0
)
}
if (underline) {
addFilter(
UNDERLINE_CHAR,
if (atLeastOneFromEach) 1 else 0
)
}
if (space) {
addFilter(
SPACE_CHAR,
if (atLeastOneFromEach) 1 else 0
)
}
if (specials) {
addFilter(
SPECIAL_CHARS,
if (atLeastOneFromEach) 1 else 0
)
}
if (brackets) {
addFilter(
BRACKET_CHARS,
if (atLeastOneFromEach) 1 else 0
)
}
if (extended) {
addFilter(
extendedChars(),
if (atLeastOneFromEach) 1 else 0
)
}
if (considerChars.isNotEmpty()) {
addFilter(
considerChars,
if (atLeastOneFromEach) 1 else 0
)
}
}
return buffer.toString()
return generateRandomString(SecureRandom(), passwordFilters)
}
private fun getCharacterSet(upperCase: Boolean,
lowerCase: Boolean,
digits: Boolean,
minus: Boolean,
underline: Boolean,
space: Boolean,
specials: Boolean,
brackets: Boolean,
extended: Boolean): String {
val charSet = StringBuilder()
private fun generateRandomString(random: Random, passwordFilters: PasswordFilters): String {
val randomString = StringBuilder()
if (upperCase) {
charSet.append(UPPERCASE_CHARS)
// Allocate appropriate memory for the password.
var requiredCharactersLeft = passwordFilters.getRequiredCharactersLeft()
// Build the password.
for (i in 0 until passwordFilters.length) {
var selectableChars: String = if (requiredCharactersLeft < passwordFilters.length - i) {
// choose from any group at random
passwordFilters.getSelectableChars()
} else {
// choose only from a group that we need to satisfy a minimum for.
passwordFilters.getSelectableCharsForNeed()
}
passwordFilters.ignoreChars.forEach {
selectableChars = selectableChars.replace(it.toString(), "")
}
// Now that the string is built, get the next random character.
val selectableCharsMaxIndex = selectableChars.length
val randomSelectableCharsIndex = if (selectableCharsMaxIndex > 0) random.nextInt(selectableCharsMaxIndex) else 0
val nextChar = selectableChars[randomSelectableCharsIndex]
// Put at random position
val randomStringMaxIndex = randomString.length
val randomStringIndex = if (randomStringMaxIndex > 0) random.nextInt(randomStringMaxIndex) else 0
randomString.insert(randomStringIndex, nextChar)
// Now figure out where it came from, and decrement the appropriate minimum value
passwordFilters.getFilterThatContainsChar(nextChar)?.let {
if (it.minCharsNeeded > 0) {
it.minCharsNeeded--
requiredCharactersLeft--
}
}
}
if (lowerCase) {
charSet.append(LOWERCASE_CHARS)
}
if (digits) {
charSet.append(DIGIT_CHARS)
}
if (minus) {
charSet.append(MINUS_CHAR)
}
if (underline) {
charSet.append(UNDERLINE_CHAR)
}
if (space) {
charSet.append(SPACE_CHAR)
}
if (specials) {
charSet.append(SPECIAL_CHARS)
}
if (brackets) {
charSet.append(BRACKET_CHARS)
}
if (extended) {
charSet.append(extendedChars())
return randomString.toString()
}
private data class Filter(var chars: String,
var minCharsNeeded: Int)
private class PasswordFilters {
var length: Int = 0
var ignoreChars = ""
val filters = mutableListOf<Filter>()
fun addFilter(chars: String, minCharsNeeded: Int) {
filters.add(Filter(chars, minCharsNeeded))
}
return charSet.toString()
fun getRequiredCharactersLeft(): Int {
var charsRequired = 0
filters.forEach {
charsRequired += it.minCharsNeeded
}
return charsRequired
}
fun getSelectableChars(): String {
val stringBuilder = StringBuilder()
filters.forEach {
stringBuilder.append(it.chars)
}
return stringBuilder.toString()
}
fun getFilterThatContainsChar(char: Char): Filter? {
return filters.find { it.chars.contains(char) }
}
fun getSelectableCharsForNeed(): String {
val selectableChars = StringBuilder()
// choose only from a group that we need to satisfy a minimum for.
filters.forEach {
if (it.minCharsNeeded > 0) {
selectableChars.append(it.chars)
}
}
return selectableChars.toString()
}
}
companion object {
@@ -151,5 +229,75 @@ class PasswordGenerator(private val resources: Resources) {
private const val SPACE_CHAR = " "
private const val SPECIAL_CHARS = "!\"#$%&'*+,./:;=?@\\^`"
private const val BRACKET_CHARS = "[]{}()<>"
private const val AMBIGUOUS_CHARS = "iI|lLoO01"
// From KeePassXC code https://github.com/keepassxreboot/keepassxc/pull/538
private fun extendedChars(): String {
val charSet = StringBuilder()
// [U+0080, U+009F] are C1 control characters,
// U+00A0 is non-breaking space
run {
var ch = '\u00A1'
while (ch <= '\u00AC') {
charSet.append(ch)
++ch
}
}
// U+00AD is soft hyphen (format character)
var ch = '\u00AE'
while (ch < '\u00FF') {
charSet.append(ch)
++ch
}
charSet.append('\u00FF')
return charSet.toString()
}
fun getColorizedPassword(password: String): Spannable {
val spannableString = SpannableStringBuilder()
if (password.isNotEmpty()) {
password.forEach {
when {
UPPERCASE_CHARS.contains(it)||
LOWERCASE_CHARS.contains(it) -> {
spannableString.append(it)
}
DIGIT_CHARS.contains(it) -> {
// RED
spannableString.append(colorizeChar(it, Color.rgb(246, 79, 62)))
}
SPECIAL_CHARS.contains(it) -> {
// Blue
spannableString.append(colorizeChar(it, Color.rgb(39, 166, 228)))
}
MINUS_CHAR.contains(it)||
UNDERLINE_CHAR.contains(it)||
BRACKET_CHARS.contains(it) -> {
// Purple
spannableString.append(colorizeChar(it, Color.rgb(185, 38, 209)))
}
extendedChars().contains(it) -> {
// Green
spannableString.append(colorizeChar(it, Color.rgb(44, 181, 50)))
}
else -> {
spannableString.append(it)
}
}
}
}
return spannableString
}
private fun colorizeChar(char: Char, color: Int): Spannable {
val spannableColorChar = SpannableString(char.toString())
spannableColorChar.setSpan(
ForegroundColorSpan(color),
0,
1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return spannableColorChar
}
}
}

View File

@@ -27,6 +27,7 @@ import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.ServiceCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Attachment
@@ -36,7 +37,10 @@ import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
@@ -275,7 +279,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
AttachmentState.COMPLETE,
AttachmentState.CANCELED,
AttachmentState.ERROR -> {
stopForeground(false)
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH)
notificationManager?.notify(attachmentNotification.notificationId, builder.build())
} else -> {
startForeground(attachmentNotification.notificationId, builder.build())

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.services
import android.content.Intent
import androidx.core.app.ServiceCompat
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.LockReceiver
import com.kunzisoft.keepass.utils.registerLockReceiver
@@ -33,6 +34,7 @@ abstract class LockNotificationService : NotificationService() {
protected open fun actionOnLock() {
// Stop the service in all cases
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
}

View File

@@ -23,7 +23,6 @@ import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat

View File

@@ -52,7 +52,7 @@ import com.kunzisoft.keepass.utils.UriUtil
class NestedAppSettingsFragment : NestedSettingsFragment() {
private var deleteKeysAlertDialog: AlertDialog? = null
private var warningAlertDialog: AlertDialog? = null
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
@@ -262,7 +262,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
val deviceCredentialChecked = deviceCredentialUnlockEnablePreference?.isChecked ?: false
if (!biometricChecked) {
biometricUnlockEnablePreference.isChecked = true
deleteKeysMessage(activity) {
warningMessage(activity, keystoreWarning = false, deleteKeys = true) {
biometricUnlockEnablePreference.isChecked = false
autoOpenPromptPreference?.isEnabled = deviceCredentialChecked
tempAdvancedUnlockPreference?.isEnabled = deviceCredentialChecked
@@ -270,13 +270,17 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} else {
if (deviceCredentialChecked) {
biometricUnlockEnablePreference.isChecked = false
deleteKeysMessage(activity) {
warningMessage(activity, keystoreWarning = true, deleteKeys = true) {
biometricUnlockEnablePreference.isChecked = true
deviceCredentialUnlockEnablePreference?.isChecked = false
}
} else {
autoOpenPromptPreference?.isEnabled = true
tempAdvancedUnlockPreference?.isEnabled = true
biometricUnlockEnablePreference.isChecked = false
warningMessage(activity, keystoreWarning = true, deleteKeys = false) {
biometricUnlockEnablePreference.isChecked = true
autoOpenPromptPreference?.isEnabled = true
tempAdvancedUnlockPreference?.isEnabled = true
}
}
}
true
@@ -305,7 +309,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
val biometricChecked = biometricUnlockEnablePreference?.isChecked ?: false
if (!deviceCredentialChecked) {
deviceCredentialUnlockEnablePreference.isChecked = true
deleteKeysMessage(activity) {
warningMessage(activity, keystoreWarning = false, deleteKeys = true) {
deviceCredentialUnlockEnablePreference.isChecked = false
autoOpenPromptPreference?.isEnabled = biometricChecked
tempAdvancedUnlockPreference?.isEnabled = biometricChecked
@@ -313,13 +317,17 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} else {
if (biometricChecked) {
deviceCredentialUnlockEnablePreference.isChecked = false
deleteKeysMessage(activity) {
warningMessage(activity, keystoreWarning = true, deleteKeys = true) {
deviceCredentialUnlockEnablePreference.isChecked = true
biometricUnlockEnablePreference?.isChecked = false
}
} else {
autoOpenPromptPreference?.isEnabled = true
tempAdvancedUnlockPreference?.isEnabled = true
deviceCredentialUnlockEnablePreference.isChecked = false
warningMessage(activity, keystoreWarning = true, deleteKeys = false) {
deviceCredentialUnlockEnablePreference.isChecked = true
autoOpenPromptPreference?.isEnabled = true
tempAdvancedUnlockPreference?.isEnabled = true
}
}
}
true
@@ -334,7 +342,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
tempAdvancedUnlockPreference?.setOnPreferenceClickListener {
tempAdvancedUnlockPreference.isChecked = !tempAdvancedUnlockPreference.isChecked
deleteKeysMessage(activity) {
warningMessage(activity, keystoreWarning = false, deleteKeys = true) {
tempAdvancedUnlockPreference.isChecked = !tempAdvancedUnlockPreference.isChecked
}
true
@@ -343,7 +351,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key))
if (biometricUnlockSupported || deviceCredentialUnlockSupported) {
deleteKeysFingerprints?.setOnPreferenceClickListener {
deleteKeysMessage(activity)
warningMessage(activity, keystoreWarning = false, deleteKeys = true)
false
}
} else {
@@ -357,22 +365,36 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
}
private fun deleteKeysMessage(activity: FragmentActivity, validate: (()->Unit)? = null) {
deleteKeysAlertDialog = AlertDialog.Builder(activity)
.setMessage(resources.getString(R.string.advanced_unlock_delete_all_key_warning))
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(resources.getString(android.R.string.ok)
) { _, _ ->
validate?.invoke()
deleteKeysAlertDialog?.setOnDismissListener(null)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AdvancedUnlockManager.deleteAllEntryKeysInKeystoreForBiometric(activity)
}
private fun warningMessage(activity: FragmentActivity,
keystoreWarning: Boolean,
deleteKeys: Boolean,
validate: (()->Unit)? = null) {
var message = ""
if (keystoreWarning) {
message += resources.getString(R.string.advanced_unlock_prompt_store_credential_message)
message += "\n\n" + resources.getString(R.string.advanced_unlock_keystore_warning)
}
if (keystoreWarning && deleteKeys) {
message += "\n\n"
}
if (deleteKeys) {
message += resources.getString(R.string.advanced_unlock_delete_all_key_warning)
}
warningAlertDialog = AlertDialog.Builder(activity)
.setMessage(message)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(resources.getString(android.R.string.ok)
) { _, _ ->
validate?.invoke()
warningAlertDialog?.setOnDismissListener(null)
if (deleteKeys && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AdvancedUnlockManager.deleteAllEntryKeysInKeystoreForBiometric(activity)
}
.setNegativeButton(resources.getString(android.R.string.cancel)
) { _, _ ->}
.create()
deleteKeysAlertDialog?.show()
}
.setNegativeButton(resources.getString(android.R.string.cancel)
) { _, _ ->}
.create()
warningAlertDialog?.show()
}
private fun onCreateAppearancePreferences(rootKey: String?) {
@@ -509,7 +531,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
override fun onPause() {
deleteKeysAlertDialog?.dismiss()
warningAlertDialog?.dismiss()
super.onPause()
}

View File

@@ -31,7 +31,10 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.password.PassphraseGenerator
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil
import java.util.*
@@ -111,6 +114,18 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.show_entry_colors_default))
}
fun hideProtectedValue(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.hide_password_key),
context.resources.getBoolean(R.bool.hide_password_default))
}
fun colorizePassword(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.colorize_password_key),
context.resources.getBoolean(R.bool.colorize_password_default))
}
fun showUsernamesListEntries(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.list_entries_show_username_key),
@@ -192,15 +207,194 @@ object PreferencesUtil {
fun getDefaultPasswordLength(context: Context): Int {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getInt(context.getString(R.string.password_length_key),
Integer.parseInt(context.getString(R.string.default_password_length)))
return prefs.getInt(context.getString(R.string.password_generator_length_key),
context.resources.getInteger(R.integer.password_generator_length_default))
}
fun getDefaultPasswordCharacters(context: Context): Set<String>? {
fun setDefaultPasswordLength(context: Context, passwordLength: Int) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putInt(
context.getString(R.string.password_generator_length_key),
passwordLength
)
apply()
}
}
fun getDefaultPasswordOptions(context: Context): Set<String> {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getStringSet(context.getString(R.string.list_password_generator_options_key),
HashSet(listOf(*context.resources
.getStringArray(R.array.list_password_generator_options_default_values))))
return prefs.getStringSet(context.getString(R.string.password_generator_options_key),
HashSet(listOf(*context.resources
.getStringArray(R.array.list_password_generator_options_default_values)))) ?: setOf()
}
fun setDefaultPasswordOptions(context: Context, passwordOptionsSet: Set<String>) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putStringSet(
context.getString(R.string.password_generator_options_key),
passwordOptionsSet
)
apply()
}
}
fun getDefaultPasswordConsiderChars(context: Context): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getString(context.getString(R.string.password_generator_consider_chars_key),
context.getString(R.string.password_generator_consider_chars_default)) ?: ""
}
fun setDefaultPasswordConsiderChars(context: Context, considerChars: String) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putString(
context.getString(R.string.password_generator_consider_chars_key),
considerChars
)
apply()
}
}
fun getDefaultPasswordIgnoreChars(context: Context): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getString(context.getString(R.string.password_generator_ignore_chars_key),
context.getString(R.string.password_generator_ignore_chars_default)) ?: ""
}
fun setDefaultPasswordIgnoreChars(context: Context, ignoreChars: String) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putString(
context.getString(R.string.password_generator_ignore_chars_key),
ignoreChars
)
apply()
}
}
fun getDefaultPassphraseWordCount(context: Context): Int {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getInt(context.getString(R.string.passphrase_generator_word_count_key),
context.resources.getInteger(R.integer.passphrase_generator_word_count_default))
}
fun setDefaultPassphraseWordCount(context: Context, passphraseWordCount: Int) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putInt(
context.getString(R.string.passphrase_generator_word_count_key),
passphraseWordCount
)
apply()
}
}
fun getDefaultPassphraseWordCase(context: Context): PassphraseGenerator.WordCase {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return PassphraseGenerator.WordCase
.getByOrdinal(prefs.getInt(context
.getString(R.string.passphrase_generator_word_case_key),
0)
)
}
fun setDefaultPassphraseWordCase(context: Context, wordCase: PassphraseGenerator.WordCase) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putInt(
context.getString(R.string.passphrase_generator_word_case_key),
wordCase.ordinal
)
apply()
}
}
fun getDefaultPassphraseSeparator(context: Context): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getString(context.getString(R.string.passphrase_generator_separator_key),
context.getString(R.string.passphrase_generator_separator_default)) ?: ""
}
fun setDefaultPassphraseSeparator(context: Context, separator: String) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putString(
context.getString(R.string.passphrase_generator_separator_key),
separator
)
apply()
}
}
fun getDefaultSearchParameters(context: Context): SearchParameters {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return SearchParameters().apply {
caseSensitive = prefs.getBoolean(context.getString(R.string.search_option_case_sensitive_key),
context.resources.getBoolean(R.bool.search_option_case_sensitive_default))
isRegex = prefs.getBoolean(context.getString(R.string.search_option_regex_key),
context.resources.getBoolean(R.bool.search_option_regex_default))
searchInTitles = prefs.getBoolean(context.getString(R.string.search_option_title_key),
context.resources.getBoolean(R.bool.search_option_title_default))
searchInUsernames = prefs.getBoolean(context.getString(R.string.search_option_username_key),
context.resources.getBoolean(R.bool.search_option_username_default))
searchInPasswords = prefs.getBoolean(context.getString(R.string.search_option_password_key),
context.resources.getBoolean(R.bool.search_option_password_default))
searchInUrls = prefs.getBoolean(context.getString(R.string.search_option_url_key),
context.resources.getBoolean(R.bool.search_option_url_default))
searchInExpired = prefs.getBoolean(context.getString(R.string.search_option_expired_key),
context.resources.getBoolean(R.bool.search_option_expired_default))
searchInNotes = prefs.getBoolean(context.getString(R.string.search_option_note_key),
context.resources.getBoolean(R.bool.search_option_note_default))
searchInOTP = prefs.getBoolean(context.getString(R.string.search_option_otp_key),
context.resources.getBoolean(R.bool.search_option_otp_default))
searchInOther = prefs.getBoolean(context.getString(R.string.search_option_other_key),
context.resources.getBoolean(R.bool.search_option_other_default))
searchInUUIDs = prefs.getBoolean(context.getString(R.string.search_option_uuid_key),
context.resources.getBoolean(R.bool.search_option_uuid_default))
searchInTags = prefs.getBoolean(context.getString(R.string.search_option_tag_key),
context.resources.getBoolean(R.bool.search_option_tag_default))
searchInCurrentGroup = prefs.getBoolean(context.getString(R.string.search_option_current_group_key),
context.resources.getBoolean(R.bool.search_option_current_group_default))
searchInSearchableGroup = prefs.getBoolean(context.getString(R.string.search_option_searchable_group_key),
context.resources.getBoolean(R.bool.search_option_searchable_group_default))
searchInRecycleBin = prefs.getBoolean(context.getString(R.string.search_option_recycle_bin_key),
context.resources.getBoolean(R.bool.search_option_recycle_bin_default))
searchInTemplates = prefs.getBoolean(context.getString(R.string.search_option_templates_key),
context.resources.getBoolean(R.bool.search_option_templates_default))
}
}
fun setDefaultSearchParameters(context: Context, searchParameters: SearchParameters) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putBoolean(context.getString(R.string.search_option_case_sensitive_key),
searchParameters.caseSensitive)
putBoolean(context.getString(R.string.search_option_regex_key),
searchParameters.isRegex)
putBoolean(context.getString(R.string.search_option_title_key),
searchParameters.searchInTitles)
putBoolean(context.getString(R.string.search_option_username_key),
searchParameters.searchInUsernames)
putBoolean(context.getString(R.string.search_option_password_key),
searchParameters.searchInPasswords)
putBoolean(context.getString(R.string.search_option_url_key),
searchParameters.searchInUrls)
putBoolean(context.getString(R.string.search_option_expired_key),
searchParameters.searchInExpired)
putBoolean(context.getString(R.string.search_option_note_key),
searchParameters.searchInNotes)
putBoolean(context.getString(R.string.search_option_otp_key),
searchParameters.searchInOTP)
putBoolean(context.getString(R.string.search_option_other_key),
searchParameters.searchInOther)
putBoolean(context.getString(R.string.search_option_uuid_key),
searchParameters.searchInUUIDs)
putBoolean(context.getString(R.string.search_option_tag_key),
searchParameters.searchInTags)
putBoolean(context.getString(R.string.search_option_current_group_key),
searchParameters.searchInCurrentGroup)
putBoolean(context.getString(R.string.search_option_searchable_group_key),
searchParameters.searchInSearchableGroup)
putBoolean(context.getString(R.string.search_option_recycle_bin_key),
searchParameters.searchInRecycleBin)
putBoolean(context.getString(R.string.search_option_templates_key),
searchParameters.searchInTemplates)
apply()
}
}
fun isClipboardNotificationsEnable(context: Context): Boolean {
@@ -351,12 +545,6 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.sort_recycle_bin_bottom_default))
}
fun hideProtectedValue(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.hide_password_key),
context.resources.getBoolean(R.bool.hide_password_default))
}
fun fieldFontIsInVisibility(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.monospace_font_fields_enable_key),
@@ -436,6 +624,7 @@ object PreferencesUtil {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.keyboard_search_share_key),
context.resources.getBoolean(R.bool.keyboard_search_share_default))
&& MagikeyboardService.activatedInSettings(context)
}
fun isKeyboardSaveSearchInfoEnable(context: Context): Boolean {
@@ -470,6 +659,12 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.keyboard_previous_database_credentials_default))
}
fun isKeyboardPreviousSearchEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.keyboard_previous_search_key),
context.resources.getBoolean(R.bool.keyboard_previous_search_default))
}
fun isKeyboardPreviousFillInEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.keyboard_previous_fill_in_key),
@@ -488,12 +683,6 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.autofill_close_database_default))
}
fun isAutofillAutoSearchEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key),
context.resources.getBoolean(R.bool.autofill_auto_search_default))
}
fun isAutofillInlineSuggestionsEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.autofill_inline_suggestions_key),
@@ -609,9 +798,6 @@ object PreferencesUtil {
context.getString(R.string.lock_database_screen_off_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.lock_database_back_root_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.lock_database_show_button_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.password_length_key) -> editor.putInt(name, value.toInt())
context.getString(R.string.list_password_generator_options_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
context.getString(R.string.hide_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.allow_copy_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.remember_database_locations_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.show_recent_files_key) -> editor.putBoolean(name, value.toBoolean())
@@ -638,10 +824,10 @@ object PreferencesUtil {
context.getString(R.string.keyboard_key_vibrate_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_key_sound_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_database_credentials_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_fill_in_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_lock_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_close_database_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_auto_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_inline_suggestions_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_manual_selection_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_save_search_info_key) -> editor.putBoolean(name, value.toBoolean())
@@ -653,6 +839,8 @@ object PreferencesUtil {
context.getString(R.string.setting_style_brightness_key) -> editor.putString(name, value)
context.getString(R.string.setting_icon_pack_choose_key) -> editor.putString(name, value)
context.getString(R.string.show_entry_colors_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.hide_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.colorize_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.list_entries_show_username_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.list_groups_show_number_entries_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.show_otp_token_key) -> editor.putBoolean(name, value.toBoolean())
@@ -662,6 +850,14 @@ object PreferencesUtil {
context.getString(R.string.hide_expired_entries_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_education_screens_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.password_generator_length_key) -> editor.putInt(name, value.toInt())
context.getString(R.string.password_generator_options_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
context.getString(R.string.password_generator_consider_chars_key) -> editor.putString(name, value)
context.getString(R.string.password_generator_ignore_chars_key) -> editor.putString(name, value)
context.getString(R.string.passphrase_generator_word_count_key) -> editor.putInt(name, value.toInt())
context.getString(R.string.passphrase_generator_word_case_key) -> editor.putInt(name, value.toInt())
context.getString(R.string.passphrase_generator_separator_key) -> editor.putString(name, value)
context.getString(R.string.sort_node_key) -> editor.putString(name, value)
context.getString(R.string.sort_group_before_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.sort_ascending_key) -> editor.putBoolean(name, value.toBoolean())

View File

@@ -225,10 +225,14 @@ open class SettingsActivity
commit()
}
toolbar?.title = NestedSettingsFragment.retrieveTitle(resources, key)
setTitle(key)
hideOrShowLockButton(key)
}
protected fun setTitle(key: NestedSettingsFragment.Screen) {
toolbar?.title = NestedSettingsFragment.retrieveTitle(resources, key)
}
/**
* To keep the current screen when activity is reloaded
*/

View File

@@ -28,6 +28,7 @@ class SettingsAdvancedUnlockActivity : SettingsActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mTimeoutEnable = false
setTitle(NestedSettingsFragment.Screen.ADVANCED_UNLOCK)
}
override fun retrieveMainFragment(): Fragment {

View File

@@ -69,7 +69,18 @@ object UriUtil {
return null
return when {
isFileScheme(fileUri) -> fileUri.path?.let { FileOutputStream(it) }
isContentScheme(fileUri) -> contentResolver.openOutputStream(fileUri, "rwt")
isContentScheme(fileUri) -> {
try {
contentResolver.openOutputStream(fileUri, "wt")
} catch (e: FileNotFoundException) {
Log.e(TAG, "Unable to open stream in `wt` mode, retry in `rwt` mode.", e)
// https://issuetracker.google.com/issues/180526528
// Try with rwt to fix content provider issue
val outStream = contentResolver.openOutputStream(fileUri, "rwt")
Log.w(TAG, "`rwt` mode used.")
outStream
}
}
else -> null
}
}

View File

@@ -42,7 +42,7 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
private var unlockAnimatedVector: FingerPrintAnimatedVector? = null
private var unlockTitleTextView: TextView? = null
private var unlockMessageTextView: TextView? = null
var unlockIconImageView: ImageView? = null
private var unlockIconImageView: ImageView? = null
init {

View File

@@ -46,7 +46,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) {
private var passwordView: EditText
private var passwordTextView: EditText
private var keyFileSelectionView: KeyFileSelectionView
private var checkboxPasswordView: CompoundButton
private var checkboxKeyFileView: CompoundButton
@@ -60,7 +60,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.view_main_credentials, this)
passwordView = findViewById(R.id.password)
passwordTextView = findViewById(R.id.password_text_view)
keyFileSelectionView = findViewById(R.id.keyfile_selection)
checkboxPasswordView = findViewById(R.id.password_checkbox)
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
@@ -75,8 +75,8 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
}
}
passwordView.setOnEditorActionListener(onEditorActionListener)
passwordView.addTextChangedListener(object : TextWatcher {
passwordTextView.setOnEditorActionListener(onEditorActionListener)
passwordTextView.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
@@ -86,7 +86,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
checkboxPasswordView.isChecked = true
}
})
passwordView.setOnKeyListener { _, _, keyEvent ->
passwordTextView.setOnKeyListener { _, _, keyEvent ->
var handled = false
if (keyEvent.action == KeyEvent.ACTION_DOWN
&& keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
@@ -108,11 +108,11 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
fun populatePasswordTextView(text: String?) {
if (text == null || text.isEmpty()) {
passwordView.setText("")
passwordTextView.setText("")
if (checkboxPasswordView.isChecked)
checkboxPasswordView.isChecked = false
} else {
passwordView.setText(text)
passwordTextView.setText(text)
if (checkboxPasswordView.isChecked)
checkboxPasswordView.isChecked = true
}
@@ -137,7 +137,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
fun getMainCredential(): MainCredential {
return MainCredential().apply {
this.masterPassword = if (checkboxPasswordView.isChecked)
passwordView.text?.toString() else null
passwordTextView.text?.toString() else null
this.keyFileUri = if (checkboxKeyFileView.isChecked)
keyFileSelectionView.uri else null
}
@@ -163,7 +163,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
*/
fun retrieveCredentialForStorage(listener: CredentialStorageListener): ByteArray? {
return when (mCredentialStorage) {
CredentialStorage.PASSWORD -> listener.passwordToStore(passwordView.text?.toString())
CredentialStorage.PASSWORD -> listener.passwordToStore(passwordTextView.text?.toString())
CredentialStorage.KEY_FILE -> listener.keyfileToStore(keyFileSelectionView.uri)
CredentialStorage.HARDWARE_KEY -> listener.hardwareKeyToStore()
}
@@ -176,15 +176,15 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
}
fun requestPasswordFocus() {
passwordView.requestFocusFromTouch()
passwordTextView.requestFocusFromTouch()
}
// Auto select the password field and open keyboard
fun focusPasswordFieldAndOpenKeyboard() {
passwordView.postDelayed({
passwordView.requestFocusFromTouch()
passwordTextView.postDelayed({
passwordTextView.requestFocusFromTouch()
val inputMethodManager = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as? InputMethodManager?
inputMethodManager?.showSoftInput(passwordView, InputMethodManager.SHOW_IMPLICIT)
inputMethodManager?.showSoftInput(passwordTextView, InputMethodManager.SHOW_IMPLICIT)
}, 100)
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2022 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.view
import android.content.Context
import android.text.Editable
import android.text.InputType
import android.text.SpannableString
import android.text.TextWatcher
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import android.widget.TextView
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.password.PasswordEntropy
import com.kunzisoft.keepass.settings.PreferencesUtil
class PassKeyView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) {
private var mPasswordEntropyCalculator: PasswordEntropy? = null
private val passwordInputLayout: TextInputLayout
private val passwordText: TextView
private val passwordStrengthProgress: LinearProgressIndicator
private val passwordEntropy: TextView
private var mViewHint: String = ""
private var mMaxLines: Int = 3
private var mShowPassword: Boolean = false
private var mPasswordTextWatcher: MutableList<TextWatcher> = mutableListOf()
private val passwordTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
mPasswordTextWatcher.forEach {
it.beforeTextChanged(charSequence, i, i1, i2)
}
}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
mPasswordTextWatcher.forEach {
it.onTextChanged(charSequence, i, i1, i2)
}
}
override fun afterTextChanged(editable: Editable) {
mPasswordTextWatcher.forEach {
it.afterTextChanged(editable)
}
getEntropyStrength(editable.toString())
}
}
init {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.PassKeyView,
0, 0).apply {
try {
mViewHint = getString(R.styleable.PassKeyView_passKeyHint)
?: context.getString(R.string.password)
mMaxLines = getInteger(R.styleable.PassKeyView_passKeyMaxLines, mMaxLines)
mShowPassword = getBoolean(R.styleable.PassKeyView_passKeyVisible,
!PreferencesUtil.hideProtectedValue(context))
} finally {
recycle()
}
}
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.view_passkey, this)
passwordInputLayout = findViewById(R.id.password_input_layout)
passwordInputLayout?.hint = mViewHint
passwordText = findViewById(R.id.password_text)
if (mShowPassword) {
passwordText?.inputType = passwordText.inputType or
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
}
passwordText?.maxLines = mMaxLines
passwordText?.applyFontVisibility()
passwordText.addTextChangedListener(passwordTextWatcher)
passwordStrengthProgress = findViewById(R.id.password_strength_progress)
passwordStrengthProgress?.apply {
setIndicatorColor(PasswordEntropy.Strength.RISKY.color)
progress = 0
max = 100
}
passwordEntropy = findViewById(R.id.password_entropy)
mPasswordEntropyCalculator = PasswordEntropy {
passwordText?.text?.toString()?.let { firstPassword ->
getEntropyStrength(firstPassword)
}
}
}
private fun getEntropyStrength(passwordText: String) {
mPasswordEntropyCalculator?.getEntropyStrength(passwordText) { entropyStrength ->
passwordStrengthProgress.apply {
post {
setIndicatorColor(entropyStrength.strength.color)
setProgressCompat(entropyStrength.estimationPercent, true)
}
}
passwordEntropy.apply {
post {
text = PasswordEntropy.getStringEntropy(resources, entropyStrength.entropy)
}
}
}
}
fun addTextChangedListener(textWatcher: TextWatcher) {
mPasswordTextWatcher.add(textWatcher)
}
fun removeTextChangedListener(textWatcher: TextWatcher) {
mPasswordTextWatcher.remove(textWatcher)
}
var passwordString: String
get() {
return passwordText.text.toString()
}
set(value) {
val spannableString =
if (PreferencesUtil.colorizePassword(context))
PasswordGenerator.getColorizedPassword(value)
else
SpannableString(value)
passwordText.text = spannableString
}
}

View File

@@ -13,6 +13,7 @@ import androidx.core.view.isVisible
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.settings.PreferencesUtil
class SearchFiltersView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
@@ -30,7 +31,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
private var searchUsername: CompoundButton
private var searchPassword: CompoundButton
private var searchURL: CompoundButton
private var searchExpires: CompoundButton
private var searchExpired: CompoundButton
private var searchNotes: CompoundButton
private var searchOther: CompoundButton
private var searchUUID: CompoundButton
@@ -49,7 +50,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
this.searchInUsernames = searchUsername.isChecked
this.searchInPasswords = searchPassword.isChecked
this.searchInUrls = searchURL.isChecked
this.excludeExpired = !(searchExpires.isChecked)
this.searchInExpired = searchExpired.isChecked
this.searchInNotes = searchNotes.isChecked
this.searchInOther = searchOther.isChecked
this.searchInUUIDs = searchUUID.isChecked
@@ -69,12 +70,12 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchUsername.isChecked = value.searchInUsernames
searchPassword.isChecked = value.searchInPasswords
searchURL.isChecked = value.searchInUrls
searchExpires.isChecked = !value.excludeExpired
searchExpired.isChecked = value.searchInExpired
searchNotes.isChecked = value.searchInNotes
searchOther.isChecked = value.searchInOther
searchUUID.isChecked = value.searchInUUIDs
searchTag.isChecked = value.searchInTags
searchGroupSearchable.isChecked = value.searchInRecycleBin
searchGroupSearchable.isChecked = value.searchInSearchableGroup
searchRecycleBin.isChecked = value.searchInRecycleBin
searchTemplate.isChecked = value.searchInTemplates
mOnParametersChangeListener = tempListener
@@ -107,7 +108,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchUsername = findViewById(R.id.search_chip_username)
searchPassword = findViewById(R.id.search_chip_password)
searchURL = findViewById(R.id.search_chip_url)
searchExpires = findViewById(R.id.search_chip_expires)
searchExpired = findViewById(R.id.search_chip_expires)
searchNotes = findViewById(R.id.search_chip_note)
searchUUID = findViewById(R.id.search_chip_uuid)
searchOther = findViewById(R.id.search_chip_other)
@@ -116,6 +117,9 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchRecycleBin = findViewById(R.id.search_chip_recycle_bin)
searchTemplate = findViewById(R.id.search_chip_template)
// Set search
searchParameters = PreferencesUtil.getDefaultSearchParameters(context)
// Expand menu with button
searchExpandButton.setOnClickListener {
val isVisible = searchAdvanceFiltersContainer?.visibility == View.VISIBLE
@@ -153,8 +157,8 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchParameters.searchInUrls = isChecked
mOnParametersChangeListener?.invoke(searchParameters)
}
searchExpires.setOnCheckedChangeListener { _, isChecked ->
searchParameters.excludeExpired = !isChecked
searchExpired.setOnCheckedChangeListener { _, isChecked ->
searchParameters.searchInExpired = isChecked
mOnParametersChangeListener?.invoke(searchParameters)
}
searchNotes.setOnCheckedChangeListener { _, isChecked ->
@@ -249,4 +253,8 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
}
}
}
fun saveSearchParameters() {
PreferencesUtil.setDefaultSearchParameters(context, searchParameters)
}
}

View File

@@ -162,7 +162,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
if (templateAttribute.options.isAssociatedWithPasswordGenerator()) {
setOnActionClickListener({
mOnPasswordGenerationActionClickListener?.invoke(field)
}, R.drawable.ic_generate_password_white_24dp)
}, R.drawable.ic_random_white_24dp)
}
}
}

View File

@@ -165,7 +165,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
setCopyButtonState(TextFieldView.ButtonState.GONE)
} else {
label = otpElement.type.name
value = otpElement.token
value = otpElement.tokenString
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { _, _ ->
mOnCopyActionClickListener?.invoke(Field(
@@ -175,7 +175,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
mLastOtpTokenView = this
mOtpRunnable = Runnable {
if (otpElement.shouldRefreshToken()) {
value = otpElement.token
value = otpElement.tokenString
}
if (mLastOtpTokenView == null) {
mOnOtpElementUpdated?.invoke(null)

View File

@@ -1,9 +1,13 @@
package com.kunzisoft.keepass.view
import android.content.Context
import android.graphics.Typeface
import android.os.Build
import android.text.InputFilter
import android.text.InputType
import android.text.Spannable
import android.text.SpannableString
import android.text.style.StyleSpan
import android.util.AttributeSet
import android.util.TypedValue
import android.view.ContextThemeWrapper
@@ -19,6 +23,9 @@ import androidx.core.view.isVisible
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
class TextEditFieldView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
@@ -123,7 +130,13 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
return valueView.text?.toString() ?: ""
}
set(value) {
valueView.setText(value)
val spannableString =
if (PreferencesUtil.colorizePassword(context)
&& TemplateField.isStandardPasswordName(context, label))
PasswordGenerator.getColorizedPassword(value)
else
SpannableString(value)
valueView.setText(spannableString)
}
override var default: String = ""
@@ -164,7 +177,11 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
fun setProtection(protection: Boolean) {
if (protection) {
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
val visibilityTag = if (PreferencesUtil.hideProtectedValue(context))
InputType.TYPE_TEXT_VARIATION_PASSWORD
else
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
valueView.inputType = valueView.inputType or visibilityTag
}
}

View File

@@ -20,13 +20,18 @@
package com.kunzisoft.keepass.view
import android.content.Context
import android.graphics.Typeface
import android.os.Build
import android.text.InputFilter
import android.text.Spannable
import android.text.SpannableString
import android.text.style.StyleSpan
import android.text.util.Linkify
import android.util.AttributeSet
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.View
import android.view.View.OnClickListener
import android.widget.RelativeLayout
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatImageButton
@@ -36,7 +41,10 @@ import androidx.core.text.util.LinkifyCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.model.EntryInfo.Companion.APPLICATION_ID_FIELD_NAME
import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.UriUtil
@@ -191,7 +199,13 @@ class TextFieldView @JvmOverloads constructor(context: Context,
return valueView.text.toString()
}
set(value) {
valueView.text = value
val spannableString =
if (PreferencesUtil.colorizePassword(context)
&& TemplateField.isStandardPasswordName(context, label))
PasswordGenerator.getColorizedPassword(value)
else
SpannableString(value)
valueView.text = spannableString
changeProtectedValueParameters()
}

View File

@@ -53,6 +53,9 @@ class EntryViewModel: ViewModel() {
val onAttachmentAction : LiveData<EntryAttachmentState?> get() = _onAttachmentAction
private val _onAttachmentAction = MutableLiveData<EntryAttachmentState?>()
val sectionSelected : LiveData<EntrySection> get() = _sectionSelected
private val _sectionSelected = MutableLiveData<EntrySection>()
val historySelected : LiveData<EntryHistory> get() = _historySelected
private val _historySelected = SingleLiveEvent<EntryHistory>()
@@ -124,6 +127,10 @@ class EntryViewModel: ViewModel() {
_historySelected.value = EntryHistory(NodeIdUUID(item.id), null, item, position)
}
fun selectSection(section: EntrySection) {
_sectionSelected.value = section
}
data class EntryInfoHistory(var mainEntryId: NodeId<UUID>,
var historyPosition: Int,
val template: Template,
@@ -135,6 +142,16 @@ class EntryViewModel: ViewModel() {
var entryInfo: EntryInfo,
var historyPosition: Int = -1)
enum class EntrySection(var position: Int) {
MAIN(0), ADVANCED(1);
companion object {
fun getEntrySectionByPosition(position: Int): EntrySection {
return if (position == 1) ADVANCED else MAIN
}
}
}
companion object {
private val TAG = EntryViewModel::class.java.name
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2022 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.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class KeyGeneratorViewModel: ViewModel() {
val keyGenerated : LiveData<String> get() = _keyGenerated
private val _keyGenerated = MutableLiveData<String>()
val keyGeneratedValidated : LiveData<Void?> get() = _keyGeneratedValidated
private val _keyGeneratedValidated = SingleLiveEvent<Void?>()
val requireKeyGeneration : LiveData<Void?> get() = _requireKeyGeneration
private val _requireKeyGeneration = SingleLiveEvent<Void?>()
val passwordGeneratedValidated : LiveData<Void?> get() = _passwordGeneratedValidated
private val _passwordGeneratedValidated = SingleLiveEvent<Void?>()
val requirePasswordGeneration : LiveData<Void?> get() = _requirePasswordGeneration
private val _requirePasswordGeneration = SingleLiveEvent<Void?>()
val passphraseGeneratedValidated : LiveData<Void?> get() = _passphraseGeneratedValidated
private val _passphraseGeneratedValidated = SingleLiveEvent<Void?>()
val requirePassphraseGeneration : LiveData<Void?> get() = _requirePassphraseGeneration
private val _requirePassphraseGeneration = SingleLiveEvent<Void?>()
fun setKeyGenerated(passKey: String) {
_keyGenerated.value = passKey
}
fun validateKeyGenerated() {
_keyGeneratedValidated.call()
}
fun validatePasswordGenerated() {
_passwordGeneratedValidated.call()
}
fun validatePassphraseGenerated() {
_passphraseGeneratedValidated.call()
}
fun requireKeyGeneration() {
_requireKeyGeneration.call()
}
fun requirePasswordGeneration() {
_requirePasswordGeneration.call()
}
fun requirePassphraseGeneration() {
_requirePassphraseGeneration.call()
}
}

View File

@@ -19,11 +19,11 @@ class SingleLiveEvent<T> : MutableLiveData<T>() {
}
// Observe the internal MutableLiveData
super.observe(owner, { t ->
super.observe(owner) { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
}
@MainThread

View File

@@ -0,0 +1,12 @@
<?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
android:shape="oval">
<solid android:color="?attr/colorPrimary"/>
</shape>
</item>
</ripple>

View File

@@ -1,12 +1,15 @@
<?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
android:shape="oval">
<solid android:color="?attr/colorAccent"/>
</shape>
</item>
</ripple>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape
android:shape="oval">
<solid android:color="@color/green_lighter"/>
</shape>
</item>
<item>
<shape
android:shape="oval">
<solid android:color="@color/green_light"/>
</shape>
</item>
</selector>

View File

@@ -1,5 +1,9 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
<path
android:fillColor="#FFFFFF"
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M3,14h4v-4H3V14zM3,19h4v-4H3V19zM3,9h4V5H3V9zM8,14h13v-4H8V14zM8,19h13v-4H8V19zM8,5v4h13V5H8z"/>
</vector>

View File

@@ -25,6 +25,9 @@
android:right="0dp"
android:top="12dp"
android:bottom="12dp"/>
<stroke android:width="1dp" android:color="@color/grey_blue_slight" />
<solid android:color="@color/grey_blue_slight"/>
<stroke
android:width="1dp"
android:color="@color/grey_blue_slight" />
<solid
android:color="@color/grey_blue_slight"/>
</shape>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2022 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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:radius="12dp" />
<padding
android:left="0dp"
android:right="0dp"
android:top="12dp"
android:bottom="12dp"/>
<stroke
android:width="1dp"
android:color="@color/grey_blue_slighter" />
</shape>

View File

@@ -136,6 +136,30 @@
</androidx.core.widget.NestedScrollView>
<com.google.android.material.tabs.TabLayout
android:id="@+id/entry_content_tab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="120dp"
android:layout_gravity="bottom|center_horizontal"
android:background="?attr/cardBackgroundTransparentColor"
app:tabIconTint="?android:attr/textColor"
app:tabMode="fixed">
<com.google.android.material.tabs.TabItem
android:id="@+id/entry_content_tab_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@drawable/ic_view_list_white_24dp" />
<com.google.android.material.tabs.TabItem
android:id="@+id/entry_content_tab_advanced"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@drawable/ic_time_white_24dp" />
</com.google.android.material.tabs.TabLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2022 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.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/key_generator_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/key_generator_coordinator"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/toolbar">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/key_generator_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.kunzisoft.keepass.view.ToolbarAction
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:theme="?attr/toolbarActionAppearance"
app:layout_constraintBottom_toBottomOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/key_generator_validation"
style="@style/KeepassDXStyle.Fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/validate"
android:src="@drawable/ic_check_white_24dp"
android:tint="?attr/colorOnAccentColor"
app:fabSize="mini"
app:layout_constraintTop_toTopOf="@+id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<include
layout="@layout/view_button_lock"
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

@@ -60,13 +60,35 @@
android:minHeight="144dp"
android:layout_marginTop="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<ImageView
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center"
android:padding="0dp"
android:contentDescription="@string/about"
android:src="@drawable/ic_launcher_foreground"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/activity_password_advanced_unlock_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginTop="12dp"
android:contentDescription="@string/about"
android:elevation="8dp"
android:src="@drawable/ic_app_white_24dp"
android:background="@drawable/background_image"
android:backgroundTint="@color/green_light"/>
<TextView
android:id="@+id/activity_password_advanced_unlock_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:orientation="vertical"
android:text="@string/education_unlock_title"
style="@style/KeepassDXStyle.TextAppearance.Secondary.TextOnPrimary"
android:gravity="center" />
</LinearLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_advanced_unlock_container_view"
android:layout_width="match_parent"

View File

@@ -17,153 +17,168 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="@dimen/card_view_margin_vertical"
android:paddingBottom="@dimen/card_view_margin_vertical">
android:paddingBottom="48dp">
<com.kunzisoft.keepass.view.TemplateView
android:id="@+id/entry_template"
<LinearLayout
android:id="@+id/entry_section_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/entry_attachments_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle">
<LinearLayout
android:layout_height="wrap_content"
<com.kunzisoft.keepass.view.TemplateView
android:id="@+id/entry_template"
android:layout_width="match_parent"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
android:layout_height="wrap_content" />
<!-- Binary files -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_attachments_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_attachments"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/entry_attachments_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle">
<LinearLayout
android:layout_height="wrap_content"
<androidx.cardview.widget.CardView
android:id="@+id/entry_attachments_container"
android:layout_width="match_parent"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<!-- Created -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_created_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_created"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_created"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- Modified -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_modified_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_modified"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_modified"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- Accessed -->
<androidx.appcompat.widget.AppCompatTextView
android:visibility="gone"
android:id="@+id/entry_accessed_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_accessed"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/entry_history"
android:name="com.kunzisoft.keepass.activities.fragments.EntryHistoryFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- TODO Custom data
<androidx.cardview.widget.CardView
android:id="@+id/entry_custom_data_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_custom_data_label"
android:layout_width="match_parent"
style="?attr/cardViewStyle">
<LinearLayout
android:layout_height="wrap_content"
android:text="@string/custom_data"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_custom_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
</LinearLayout>
</androidx.cardview.widget.CardView>
-->
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/entry_UUID_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<!-- UUID -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_UUID_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_UUID"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Binary files -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_UUID_reference"
android:layout_width="wrap_content"
android:id="@+id/entry_attachments_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
</HorizontalScrollView>
</LinearLayout>
</androidx.cardview.widget.CardView>
android:text="@string/entry_attachments"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/entry_attachments_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
<LinearLayout
android:id="@+id/entry_section_advanced"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/entry_UUID_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<!-- UUID -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_UUID_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_UUID"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_UUID_reference"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
</HorizontalScrollView>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- TODO Custom data
<androidx.cardview.widget.CardView
android:id="@+id/entry_custom_data_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_custom_data_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/custom_data"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_custom_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
</LinearLayout>
</androidx.cardview.widget.CardView>
-->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/cardViewStyle">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<!-- Created -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_created_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_created"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_created"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- Modified -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_modified_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_modified"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_modified"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextNode" />
<!-- Accessed -->
<androidx.appcompat.widget.AppCompatTextView
android:visibility="gone"
android:id="@+id/entry_accessed_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_accessed"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/entry_history"
android:name="com.kunzisoft.keepass.activities.fragments.EntryHistoryFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>

View File

@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 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/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
android:paddingBottom="@dimen/card_view_margin_vertical"
tools:targetApi="o">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="68dp"
android:background="?attr/colorPrimary" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/card_view_margin_horizontal"
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
android:layout_marginRight="@dimen/card_view_margin_horizontal"
android:layout_marginTop="@dimen/default_margin"
android:layout_marginBottom="@dimen/card_view_margin_vertical"
android:layout_gravity="bottom"
app:cardCornerRadius="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_view_padding">
<com.kunzisoft.keepass.view.PassKeyView
android:id="@+id/passphrase_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@+id/passphrase_copy_button"
android:layout_toLeftOf="@+id/passphrase_copy_button"
app:passKeyHint="@string/passphrase"
app:passKeyMaxLines="7"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/passphrase_copy_button"
style="@style/KeepassDXStyle.ImageButton.Simple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/passphrase_view"
android:layout_alignBottom="@+id/passphrase_view"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:contentDescription="@string/menu_copy"
android:src="@drawable/ic_content_copy_white_24dp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
<ScrollView
android:id="@+id/ScrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/card_view_margin_horizontal"
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
android:layout_marginRight="@dimen/card_view_margin_horizontal"
android:layout_marginTop="@dimen/card_view_margin_vertical"
android:layout_marginBottom="@dimen/card_view_margin_vertical"
app:cardCornerRadius="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.slider.Slider
android:id="@+id/slider_word_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/word_count"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_toStartOf="@+id/word_count"
android:layout_toLeftOf="@+id/word_count"
android:contentDescription="@string/content_description_password_length"
android:stepSize="1"
android:value="@integer/passphrase_generator_word_count_default"
android:valueFrom="@integer/passphrase_generator_word_count_min"
android:valueTo="@integer/passphrase_generator_word_count_max"
app:thumbColor="?attr/chipFilterBackgroundColor"
app:trackColorActive="?attr/chipFilterBackgroundColor"
app:trackColorInactive="?attr/chipFilterBackgroundColorDisabled" />
<EditText
android:id="@+id/word_count"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:inputType="number"
android:maxLength="2"
android:maxLines="1" />
</RelativeLayout>
<TextView
android:id="@+id/character_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Character count: 51" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Spinner
android:id="@+id/word_case"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginTop="12dp"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/word_separator_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/word_case"
android:layout_toRightOf="@+id/word_case">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/word_separator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/word_separator" />
</com.google.android.material.textfield.TextInputLayout>
</RelativeLayout>
</LinearLayout>
</FrameLayout>
</ScrollView>
</LinearLayout>

View File

@@ -23,319 +23,241 @@
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/default_margin"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
android:paddingBottom="@dimen/card_view_margin_vertical"
tools:targetApi="o">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_input_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/password_copy_button"
android:importantForAccessibility="no"
android:importantForAutofill="no"
app:endIconMode="password_toggle"
app:endIconTint="?attr/colorAccent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/hint_generated_password"
android:importantForAccessibility="no"
android:importantForAutofill="no"
android:inputType="textPassword|textMultiLine"
android:maxLines="3"
tools:ignore="TextFields" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/password_copy_button"
style="@style/KeepassDXStyle.ImageButton.Simple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:contentDescription="@string/menu_copy"
android:src="@drawable/ic_content_copy_white_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/generate_password_button"
android:layout_margin="@dimen/button_margin"
android:layout_height="68dp"
android:background="?attr/colorPrimary" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:drawableEnd="@drawable/ic_generate_password_white_24dp"
android:drawableRight="@drawable/ic_generate_password_white_24dp"
android:paddingLeft="24dp"
android:paddingStart="24dp"
android:paddingRight="24dp"
android:paddingEnd="24dp"
android:text="@string/generate_password" />
</LinearLayout>
android:layout_marginStart="@dimen/card_view_margin_horizontal"
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
android:layout_marginRight="@dimen/card_view_margin_horizontal"
android:layout_marginTop="@dimen/default_margin"
android:layout_marginBottom="@dimen/card_view_margin_vertical"
android:layout_gravity="bottom"
app:cardCornerRadius="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_view_padding">
<com.kunzisoft.keepass.view.PassKeyView
android:id="@+id/password_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@+id/password_copy_button"
android:layout_toLeftOf="@+id/password_copy_button" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/password_copy_button"
style="@style/KeepassDXStyle.ImageButton.Simple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/password_view"
android:layout_alignBottom="@+id/password_view"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:contentDescription="@string/menu_copy"
android:src="@drawable/ic_content_copy_white_24dp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
<ScrollView
android:id="@+id/ScrollView"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/length_label"
android:text="@string/length"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<EditText
android:id="@+id/length"
android:layout_width="50dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_height="wrap_content"
android:layout_below="@+id/length_label"
android:maxLines="1"
android:maxLength="3"
android:inputType="number"
android:text="@string/default_password_length"
android:hint="@string/hint_length"/>
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/seekbar_length"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/length"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/length"
android:layout_toEndOf="@+id/length"
android:layout_toRightOf="@+id/length"
android:contentDescription="@string/content_description_password_length"
app:min="@string/min_password_length"
android:progress="@string/default_password_length"
android:max="@string/max_password_length"/>
</RelativeLayout>
android:layout_marginStart="@dimen/card_view_margin_horizontal"
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
android:layout_marginRight="@dimen/card_view_margin_horizontal"
android:layout_marginTop="@dimen/card_view_margin_vertical"
android:layout_marginBottom="@dimen/card_view_margin_vertical"
app:cardCornerRadius="4dp">
<LinearLayout
android:id="@+id/RelativeLayout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp">
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_uppercase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/uppercase"
android:checked="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_uppercase"
android:layout_toRightOf="@+id/cb_uppercase"
android:text="@string/visual_uppercase" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_lowercase"
android:layout_width="wrap_content"
<com.google.android.material.slider.Slider
android:id="@+id/slider_length"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lowercase"
android:checked="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_lowercase"
android:layout_toRightOf="@+id/cb_lowercase"
android:text="@string/visual_lowercase" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_digits"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/digits"
android:checked="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_digits"
android:layout_toRightOf="@+id/cb_digits"
android:text="@string/visual_digits" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_minus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/minus" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_minus"
android:layout_toRightOf="@+id/cb_minus"
android:text="@string/visual_minus" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_underline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/underline" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_underline"
android:layout_toRightOf="@+id/cb_underline"
android:text="@string/visual_underline" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_space"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/space" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_space"
android:layout_toRightOf="@+id/cb_space"
android:text="@string/visual_space" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_specials"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/special" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_specials"
android:layout_toRightOf="@+id/cb_specials"
android:text="@string/visual_special" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_brackets"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/brackets"
android:layout_alignBottom="@+id/length"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:layout_width="wrap_content"
android:layout_toStartOf="@+id/length"
android:layout_toLeftOf="@+id/length"
android:contentDescription="@string/content_description_password_length"
android:stepSize="1"
android:value="@integer/password_generator_length_default"
android:valueFrom="@integer/password_generator_length_min"
android:valueTo="@integer/password_generator_length_max"
app:thumbColor="?attr/chipFilterBackgroundColor"
app:trackColorActive="?attr/chipFilterBackgroundColor"
app:trackColorInactive="?attr/chipFilterBackgroundColorDisabled" />
<EditText
android:id="@+id/length"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_brackets"
android:layout_toRightOf="@+id/cb_brackets"
android:text="@string/visual_brackets" />
android:inputType="number"
android:maxLength="3"
android:maxLines="1" />
</RelativeLayout>
<RelativeLayout
<com.google.android.material.chip.ChipGroup
android:id="@+id/password_filters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:chipSpacingVertical="16dp"
android:paddingTop="@dimen/default_margin"
android:paddingBottom="@dimen/default_margin">
<com.google.android.material.chip.Chip
android:id="@+id/upperCase_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/visual_uppercase"/>
<com.google.android.material.chip.Chip
android:id="@+id/lowerCase_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/visual_lowercase"/>
<com.google.android.material.chip.Chip
android:id="@+id/digits_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/visual_digits"/>
<com.google.android.material.chip.Chip
android:id="@+id/minus_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/visual_minus"/>
<com.google.android.material.chip.Chip
android:id="@+id/underline_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/visual_underline"/>
<com.google.android.material.chip.Chip
android:id="@+id/space_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/space"/>
<com.google.android.material.chip.Chip
android:id="@+id/special_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/visual_special"/>
<com.google.android.material.chip.Chip
android:id="@+id/brackets_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/visual_brackets"/>
<com.google.android.material.chip.Chip
android:id="@+id/extendedASCII_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/visual_extended"/>
</com.google.android.material.chip.ChipGroup>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_extended"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/consider_chars_filter_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/ignore_chars_filter_layout"
android:layout_width="0dp"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/consider_chars_filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/consider_chars_filter"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ignore_chars_filter_layout"
app:layout_constraintStart_toEndOf="@+id/consider_chars_filter_layout"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ignore_chars_filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/ignore_chars_filter"/>
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.chip.ChipGroup
android:id="@+id/password_advanced"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:chipSpacingVertical="16dp"
android:paddingTop="@dimen/default_margin"
android:paddingBottom="@dimen/default_margin">
<com.google.android.material.chip.Chip
android:id="@+id/atLeastOne_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/extended_ASCII"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:checked="true"
android:text="@string/at_least_one_char"/>
<com.google.android.material.chip.Chip
android:id="@+id/excludeAmbiguous_filter"
style="@style/KeepassDXStyle.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:layout_toEndOf="@+id/cb_extended"
android:layout_toRightOf="@+id/cb_extended"
android:text="@string/visual_extended" />
</RelativeLayout>
android:checked="true"
android:text="@string/exclude_ambiguous_chars"/>
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</ScrollView>
</LinearLayout>

View File

@@ -71,25 +71,11 @@
android:text="@string/password"/>
<!-- Password Input -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_input_layout"
<com.kunzisoft.keepass.view.PassKeyView
android:id="@+id/password_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:importantForAutofill="no"
app:endIconMode="password_toggle"
app:endIconTint="?attr/colorAccent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/pass_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:importantForAccessibility="no"
android:importantForAutofill="yes"
android:autofillHints="newPassword"
android:maxLines="1"
android:hint="@string/hint_pass"/>
</com.google.android.material.textfield.TextInputLayout>
app:passKeyVisible="false"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_repeat_input_layout"
android:layout_width="match_parent"
@@ -100,14 +86,14 @@
app:endIconMode="password_toggle"
app:endIconTint="?attr/colorAccent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/pass_conf_password"
android:id="@+id/password_confirmation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
android:importantForAccessibility="no"
android:importantForAutofill="yes"
android:autofillHints="newPassword"
android:maxLines="1"
android:importantForAutofill="no"
android:inputType="textPassword|textMultiLine"
android:maxLines="3"
android:hint="@string/hint_conf_pass"/>
</com.google.android.material.textfield.TextInputLayout>

View File

@@ -23,15 +23,15 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/icon_picker_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/icon_picker_pager"
android:id="@+id/tabs_view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -61,7 +61,9 @@
android:layout_toLeftOf="@+id/item_attachment_size_container"
android:layout_toEndOf="@+id/item_attachment_broken"
android:layout_toRightOf="@+id/item_attachment_broken"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="2"
tools:text="BinaryFile.attach" />
<LinearLayout

View File

@@ -144,7 +144,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="5136" />
tools:text="513 651" />
<FrameLayout
android:layout_width="wrap_content"

View File

@@ -20,18 +20,79 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_alignParentBottom="true"
android:paddingTop="4dp"
android:paddingBottom="8dp"
android:background="@color/grey_blue_deep"
android:orientation="vertical">
<LinearLayout
android:id="@+id/magikeyboard_entry_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginTop="4dp"
android:background="@drawable/key_border"
android:orientation="vertical">
<TextView
android:id="@+id/magikeyboard_entry_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textSize="16sp"
tools:text="Git account signup"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/white"/>
<LinearLayout
android:id="@+id/magikeyboard_database_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/magikeyboard_database_color"
android:layout_width="6dp"
android:layout_height="6dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_gravity="center"
android:contentDescription="@string/content_description_database_color"
android:src="@drawable/background_icon" />
<TextView
android:id="@+id/magikeyboard_database_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:textSize="11sp"
tools:text="Database A"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/grey_blue_slighter"/>
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/magikeyboard_entry_text"
android:id="@+id/magikeyboard_package_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/grey_blue_deep"
android:gravity="center"
android:textColor="@color/white"/>
android:textSize="12sp"
tools:text="com.kunzisoft.keepass"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/grey_blue_slighter"/>
<com.kunzisoft.keepass.magikeyboard.KeyboardView
android:id="@+id/magikeyboard_view"
style="@style/KeepassDXStyle.Keyboard"

View File

@@ -34,12 +34,12 @@
android:id="@+id/keyboard_popup_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:contentDescription="@string/content_description_keyboard_close_fields"
android:layout_centerVertical="true"
android:layout_margin="4dp"
android:padding="8dp"
android:background="@drawable/key_background"
android:contentDescription="@string/content_description_keyboard_close_fields"
android:src="@drawable/ic_close_white_24dp" />
</RelativeLayout>

View File

@@ -17,19 +17,20 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/keyboard_popup_field_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/keyboard_popup_field_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/key_background"
android:layout_margin="5dp"
android:paddingStart="5dp"
android:paddingEnd="5dp"
tools:text="Sample"
android:textColor="@color/white"/>
</RelativeLayout>
android:layout_height="wrap_content"
android:background="@drawable/key_background"
android:layout_gravity="center"
android:layout_margin="4dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
tools:text="Sample"
android:textColor="@color/white"/>

View File

@@ -10,7 +10,7 @@
android:layout_height="wrap_content"
app:fabSize="mini"
android:layout_gravity="center"
android:layout_margin="8dp"
android:layout_margin="9dp"
android:contentDescription="@string/lock"
android:visibility="gone"
android:src="@drawable/ic_lock_white_padding_24dp"

View File

@@ -36,19 +36,16 @@
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/password_checkbox"
android:layout_toEndOf="@+id/password_checkbox"
android:importantForAccessibility="no"
android:importantForAutofill="no"
app:endIconMode="password_toggle"
app:endIconTint="?attr/colorAccent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:id="@+id/password_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:hint="@string/password"
android:inputType="textPassword"
android:importantForAccessibility="no"
android:importantForAutofill="yes"
android:focusable="true"
android:focusableInTouchMode="true"

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:importantForAccessibility="no"
android:importantForAutofill="no"
app:endIconMode="password_toggle"
app:endIconTint="?attr/colorAccent"
tools:ignore="UnusedAttribute">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/hint_pass"
android:importantForAccessibility="no"
android:importantForAutofill="no"
android:inputType="textPassword|textMultiLine"
android:maxLines="3"
tools:ignore="TextFields" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/password_strength_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:trackCornerRadius="8dp"
app:layout_constraintTop_toBottomOf="@+id/password_input_layout"/>
<TextView
android:id="@+id/password_entropy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Entropy: 72.50 bit"
android:textSize="11sp"
android:layout_margin="4dp"
android:textColor="?attr/colorAccent"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/password_input_layout"
app:layout_constraintEnd_toEndOf="@+id/password_input_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Jeremy Jamet / Kunzisoft.
Copyright 2021 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
@@ -17,7 +17,12 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<bool name="biometric_unlock_enable_default" translatable="true">true</bool>
<bool name="device_credential_unlock_enable_default" translatable="true">true</bool>
</resources>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_generate"
android:icon="@drawable/ic_random_white_24dp"
android:title="@string/generate_password"
android:orderInCategory="8"
app:iconTint="?attr/colorControlNormal"
app:showAsAction="ifRoom" />
</menu>

View File

@@ -20,12 +20,12 @@
<string name="homepage">الصفحة الرئيسة</string>
<string name="accept">قبول</string>
<string name="add_group">إضافة مجموعة</string>
<string name="encryption">التشفير</string>
<string name="encryption_algorithm">خوارزمية التشفير</string>
<string name="encryption">التعميّة</string>
<string name="encryption_algorithm">خوارزمية التعميّة</string>
<string name="application">التطبيق</string>
<string name="brackets">الأقواس</string>
<string name="extended_ASCII">تمديد ASCII</string>
<string name="allow">السماح</string>
<string name="allow">سماح</string>
<string name="clipboard_cleared">مُسِحت الحافظة</string>
<string name="clipboard_error_title">خطأ في الحافظة</string>
<string name="clipboard_error_clear">تعذَّر مسح الحافظة</string>
@@ -104,14 +104,14 @@
<string name="education_entry_new_field_title">إضافة حقول مخصصة</string>
<string name="education_field_copy_title">نسخ حقل</string>
<string name="education_lock_title">تأمين قاعدة البيانات</string>
<string name="feedback">ردود الفعل</string>
<string name="feedback">أرسل انطباعاتك</string>
<string name="about_description">التنفيذ لمُدير كلمات المرور «كي‌ باس» على نظام أندرويد</string>
<string name="add_entry">إضافة إدخال</string>
<string name="edit_entry">تحرير الإدخال</string>
<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_summary">مدة الخمول قبل قفل قاعدة البيانات</string>
<string name="file_manager_install_description">المحرر الذي يمتلك صلاحتي ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT ضروري لانشاء, وفتح وحفض قواعد البيانات.</string>
<string name="file_manager_install_description">مدير الملفات الذي يمكنه القيام بالإجراءين ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT ضروري لانشاء, وفتح وحفض قواعد البيانات.</string>
<string name="clipboard_error">بعض الأجهزة لا تسمح للتطبيقات باستعمال الحافظة.</string>
<string name="clipboard_timeout">مهلة الحافظة</string>
<string name="clipboard_timeout_summary">مدة التخزين في الحافظة(إذا كان جهازك يدعمها)</string>
@@ -141,7 +141,7 @@
<string name="invalid_db_sig">تعذر تمييز نسق قاعدة البيانات.</string>
<string name="keyfile_is_empty">ملف المفتاح فارغ.</string>
<string name="list_entries_show_username_title">أظهر أسماء المستخدمين</string>
<string name="list_entries_show_username_summary">أظهر أسماء المستخدمين في قوائم المدخلات</string>
<string name="list_entries_show_username_summary">اعرض اسماء المستخدمين في قوائم المدخلات</string>
<string name="hint_generated_password">كلمة السر الموَلدة</string>
<string name="hint_keyfile">الملف المفتاحي</string>
<string name="hide_password_title">اخفاء كلمات السر</string>
@@ -215,7 +215,7 @@
<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_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>
@@ -236,7 +236,7 @@
<string name="reset_education_screens_title">إعادة تعيين الشاشات التعليمية</string>
<string name="education_search_title">البحث من خلال الإدخالات</string>
<string name="content_description_open_file">افتح الملف</string>
<string name="content_description_add_entry">إضافة إدخال</string>
<string name="content_description_add_entry">إضافة مدخلة</string>
<string name="content_description_add_group">إضافة مجموعة</string>
<string name="content_description_file_information">معلومات الملف</string>
<string name="entry_password_generator">مولد كلمة السر</string>
@@ -254,8 +254,8 @@
<string name="content_description_password_length">طول كلمة السر</string>
<string name="entry_add_field">أضف حقل</string>
<string name="content_description_remove_field">أزل حقل</string>
<string name="error_move_entry_here">لا يمكن نقل إدخال هنا.</string>
<string name="error_copy_entry_here">لا يمكن نسخ إدخال هنا.</string>
<string name="error_move_entry_here">يتعذر نقل مدخل إلى هنا.</string>
<string name="error_copy_entry_here">يتعذر نسخ مدخال إلى هنا.</string>
<string name="list_groups_show_number_entries_title">عرض عدد المدخلات</string>
<string name="list_groups_show_number_entries_summary">عرض عدد المدخلات في المجموعة</string>
<string name="content_description_update_from_list">تحديث</string>
@@ -302,7 +302,7 @@
<string name="menu_delete_entry_history">احذف السجل</string>
<string name="menu_restore_entry_history">استعادة السجل</string>
<string name="menu_empty_recycle_bin">أفرغ سلة المحذوفات</string>
<string name="menu_save_database">اِحفظ قاعدة البيانات</string>
<string name="menu_save_database">اِحفظ البيانات</string>
<string name="menu_master_key_settings">إعدادات المفتاح الرئيسي</string>
<string name="menu_security_settings">إعدادات الأمن</string>
<string name="creating_database">ينشئ قاعدة البيانات…</string>
@@ -357,8 +357,6 @@
<string name="autofill_web_domain_blocklist_title">قائمة النطاقات المحظورة</string>
<string name="autofill_application_id_blocklist_summary">منع الملء التلقائي للتطبيقات الموجودة في القائمة</string>
<string name="autofill_application_id_blocklist_title">قائمة التطبيقات المحظورة</string>
<string name="autofill_auto_search_summary">يقترح تلقائيا نتائج البحث في النطاقات ومعرفات التطبيقات</string>
<string name="autofill_auto_search_title">البحث التلقائي</string>
<string name="content_description_repeat_toggle_password_visibility">بدِّل ظهور كلمة السر</string>
<string name="hide_expired_entries_summary">لن تعرض المدخلات منتهية الصلاحية</string>
<string name="education_read_only_summary">غيِّر وضع الجلسة.
@@ -458,7 +456,7 @@
<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_reload_database">أعد تحميل البيانات</string>
<string name="menu_external_icon">أيقونة خارجية</string>
<string name="registration_mode">وضع التسجيل</string>
<string name="import_app_properties_title">استورد خصائص التطبيق</string>
@@ -468,7 +466,7 @@
<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="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>
@@ -486,4 +484,22 @@
<string name="autofill_save_search_info_title">احفظ معلومات البحث</string>
<string name="autofill_ask_to_save_data_title">اسأل لحفظ البيانات</string>
<string name="content_description_database_color">لون قاعدة البيانات</string>
<string name="menu_merge_from">ادمج من…</string>
<string name="show_uuid_summary">يعرض \"المعرف العام\" المرتبط بمُدخل او بمجموعة</string>
<string name="expired">انتهت المهلة</string>
<string name="tags">الوسوم</string>
<string name="menu_merge_database">ادمج البيانات</string>
<string name="current_group">المجموعة الحالية</string>
<string name="search_filters">مرشحات البحث</string>
<string name="warning_file_too_big">يفترض بقاعدة البيانات أن تحوي ملفات صغيرة الحجم ( كمفاتيح PGP).
\n
\nبرفع هذا الملف قد يزداد حجم قاعدة البيانات ويضعف أداءها.</string>
<string name="error_move_group_here">يتعذر نقل المجموعة إلى هنا.</string>
<string name="menu_save_copy_to">احفظ نسخة إلى…</string>
<string name="searchable">يمكن البحث عنه</string>
<string name="custom_data">بيانات مخصصة</string>
<string name="case_sensitive">حساسة لحالة الأحرف</string>
<string name="regex">تعابير نمطية</string>
<string name="enable_keep_screen_on_title">أبق الشاشة شغّالة</string>
<string name="enable_education_screens_summary">أبرز العناصر لتعلم طريقة عمل التطبيق</string>
</resources>

View File

@@ -29,17 +29,17 @@
<string name="application">Aplikace</string>
<string name="menu_app_settings">Nastavení aplikace</string>
<string name="brackets">Závorky</string>
<string name="file_manager_install_description">K založení, otevření a uložení databázových souborů je potřebný správce souborů, který akceptuje akci intentu ACTION_CREATE_DOCUMENT a ACTION_OPEN_DOCUMENT.</string>
<string name="file_manager_install_description">K založení, otevření a uložení databázových souborů je potřebný správce souborů, který přijímá akci záměru ACTION_CREATE_DOCUMENT a ACTION_OPEN_DOCUMENT.</string>
<string name="clipboard_cleared">Schránka vyčištěna</string>
<string name="clipboard_error_title">Chyba schránky</string>
<string name="clipboard_error">Některá zařízení nedovolují aplikacím používat schránku.</string>
<string name="clipboard_error_clear">Nelze vyprázdnit schránku</string>
<string name="clipboard_error_clear">Nedaří se vyprázdnit schránku</string>
<string name="clipboard_timeout">Časový limit schránky</string>
<string name="clipboard_timeout_summary">Doba uchování ve schránce (je-li podporována zařízením)</string>
<string name="clipboard_timeout_summary">Doba uchování ve schránce (pokud je zařízením podporována)</string>
<string name="select_to_copy">Vyberte zkopírovat %1$s do schránky</string>
<string name="retrieving_db_key">Načítám klíč databáze…</string>
<string name="retrieving_db_key">Načítá klíč databáze…</string>
<string name="database">Databáze</string>
<string name="decrypting_db">Dešifrování obsahu databáze…</string>
<string name="decrypting_db">Rozšifrování obsahu databáze…</string>
<string name="default_checkbox">Použít jako výchozí databázi</string>
<string name="digits">Číslice</string>
<string name="select_database_file">Otevřít existující databázi</string>
@@ -61,15 +61,15 @@
<string name="error_can_not_handle_uri">KeePassDX nemůže zpracovat toto URI.</string>
<string name="error_file_not_create">Soubor se nepodařilo vytvořit</string>
<string name="error_invalid_db">Databázi se nepodařilo načíst.</string>
<string name="error_invalid_path">Ujistěte se, že cesta je správná.</string>
<string name="error_no_name">Zadejte jméno.</string>
<string name="error_invalid_path">Ujistěte se, že je popis umístění správný.</string>
<string name="error_no_name">Zadejte název.</string>
<string name="error_nokeyfile">Vyberte soubor s klíčem.</string>
<string name="error_out_of_memory">Nedostatek paměti k načtení celé databáze.</string>
<string name="error_out_of_memory">Nedostatek paměti pro načtení celé databáze.</string>
<string name="error_pass_gen_type">Je třeba zvolit alespoň jeden způsob vytváření hesla.</string>
<string name="error_pass_match">Hesla se neshodují.</string>
<string name="error_rounds_too_large">Příliš vysoký „Počet průchodů“. Nastavuji na 2147483648.</string>
<string name="error_pass_match">Zadání hesla se neshodují.</string>
<string name="error_rounds_too_large">Příliš vysoký „Počet průchodů“. Nastavuje se na 2147483648.</string>
<string name="error_string_key">Je třeba, aby každý řetězec měl název kolonky.</string>
<string name="error_wrong_length">Do pole „Délka“ zadejte celé kladné číslo.</string>
<string name="error_wrong_length">Do kolonky „Délka“ zadejte celé kladné číslo.</string>
<string name="field_name">Název kolonky</string>
<string name="field_value">Hodnota kolonky</string>
<string name="file_browser">Správce souborů</string>
@@ -144,7 +144,7 @@
<string name="error_autofill_enable_service">Službu automatického vyplňování se nepodařilo zapnout.</string>
<string name="file_not_found_content">Soubor nenalezen. Zkuste jej otevřít ze správce souborů.</string>
<string name="list_entries_show_username_title">Zobrazit uživatelská jména</string>
<string name="list_entries_show_username_summary">V seznamech záznamů zobrazit uživatelská jména</string>
<string name="list_entries_show_username_summary">Zobrazí uživatelská jména v seznamech záznamů</string>
<string name="copy_field">Kopie %1$s</string>
<string name="menu_form_filling_settings">Vyplňování formulářů</string>
<string name="menu_copy">Zkopírovat</string>
@@ -223,14 +223,14 @@
<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>
<string name="allow_no_password_summary">Povolit klepnutí na Otevřít, i když není vybráno žádné heslo</string>
<string name="enable_read_only_title">Chráněno před zápisem</string>
<string name="enable_read_only_summary">Ve výchozím stavu otevřít databázi pouze pro čtení</string>
<string name="enable_education_screens_title">Vzdělávací nápovědy</string>
<string name="enable_education_screens_summary">Zvýraznit prvky k pochopení práce s aplikací</string>
<string name="reset_education_screens_title">Nastavit vzdělávací nápovědy do výchozího stavu</string>
<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="reset_education_screens_text">Znovu zobrazit výukové nápovědy</string>
<string name="education_create_database_title">Vytvořit databázový soubor</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>
@@ -244,15 +244,15 @@
<string name="education_entry_edit_title">Upravit záznam</string>
<string name="education_entry_edit_summary">Přidejte ke svému záznamu vlastní kolonky. Společná data mohou být sdílena mezi různými kolonkami záznamu odkazem.</string>
<string name="education_generate_password_title">Vytvořit silné heslo</string>
<string name="education_generate_password_summary">Generujte silné heslo pro svůj záznam, definujte je podle kritérií formuláře, a nezapomeňte na bezpečné heslo.</string>
<string name="education_generate_password_summary">Nechte si vytvořit odolné heslo pro svůj záznam, definujte je podle kritérií formuláře, a nezapomeňte na bezpečné heslo.</string>
<string name="education_entry_new_field_title">Přidat vlastní kolonky</string>
<string name="education_entry_new_field_summary">Registrovat další kolonku, zadat hodnotu a volitelně ji ochránit.</string>
<string name="education_unlock_title">Odemknout databázi</string>
<string name="education_read_only_title">Ochraňte svou databázi před zápisem</string>
<string name="education_read_only_summary">Změnit režim otevírání pro dané sezení.
\n
\nV režimu \"pouze pro čtení\" zabráníte nechtěným změnám v databázi.
\nV režimu \"zápisu\" je možné přidávat, mazat nebo měnit všechny prvky podle libosti.</string>
\nV režimu pouze pro čtení zabráníte nechtěným změnám v databázi.
\nV režimu „umožněné změny“ je možné přidávat, mazat nebo měnit všechny prvky podle libosti.</string>
<string name="education_field_copy_title">Zkopírovat kolonku</string>
<string name="education_field_copy_summary">Zkopírované kolonky lze vkládat podle libosti.
\n
@@ -262,20 +262,20 @@
<string name="education_sort_title">Řazení položek</string>
<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="education_donation_summary">Zapojte se a pomozte zvýšit stabilitu, zabezpečení 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 <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_buy_pro">Zakoupením varianty pro získáte přístup k tomuto <strong>vizuálnímu stylu</strong> a hlavně pomůžete <strong>uskutečnění komunitních projektů.</strong></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>
<string name="html_text_dev_feature">Tato funkce je &lt;strong&gt;ve vývoji&lt;/strong&gt; a potřebuje Váš &lt;strong&gt;příspěvek&lt;/strong&gt;, aby byla brzy k dispozici.</string>
<string name="html_text_dev_feature_buy_pro">Zakoupením &lt;strong&gt;pro&lt;/strong&gt; varianty,</string>
<string name="html_text_dev_feature_contibute">&lt;strong&gt;Zapojením se&lt;/strong&gt;,</string>
<string name="html_text_dev_feature_contibute"><strong>Podpořením vývoje</strong>,</string>
<string name="html_text_dev_feature_encourage">povzbudíte vývojáře k doplnění &lt;strong&gt;nových funkcí&lt;/strong&gt; a &lt;strong&gt;opravám chyb&lt;/strong&gt; dle vašich připomínek.</string>
<string name="html_text_dev_feature_thanks">Mockrát děkujeme za Váš příspěvek.</string>
<string name="html_text_dev_feature_thanks">Mnohokrát děkujeme za Váš příspěvek.</string>
<string name="html_text_dev_feature_work_hard">Tvrdě pracujeme na brzkém vydání této funkce.</string>
<string name="html_text_dev_feature_upgrade">Pamatujte na aktualizaci aplikace instalováním nových verzí.</string>
<string name="download">Stáhnout</string>
<string name="contribute">Přispět</string>
<string name="contribute">Podpořit vývoj</string>
<string name="style_choose_title">Vzhled aplikace</string>
<string name="style_choose_summary">Motiv vzhledu aplikace</string>
<string name="icon_pack_choose_title">Sada ikon</string>
@@ -316,14 +316,14 @@
<string name="content_description_add_entry">Přidat záznam</string>
<string name="content_description_add_group">Přidat skupinu</string>
<string name="content_description_file_information">Informace o souboru</string>
<string name="content_description_password_checkbox">Checkbox hesla</string>
<string name="content_description_keyfile_checkbox">Checkbox souboru s klíčem</string>
<string name="content_description_password_checkbox">Zaškrtávací kolonka hesla</string>
<string name="content_description_keyfile_checkbox">Zaškrtávací kolonka souboru s klíčem</string>
<string name="content_description_repeat_toggle_password_visibility">Opakovat přepnutí viditelnosti hesla</string>
<string name="content_description_entry_icon">Ikona záznamu</string>
<string name="entry_password_generator">Generátor hesel</string>
<string name="content_description_password_length">Délka hesla</string>
<string name="entry_add_field">Přidat pole</string>
<string name="content_description_remove_field">Odebrat pole</string>
<string name="entry_add_field">Přidat kolonku</string>
<string name="content_description_remove_field">Odebrat kolonku</string>
<string name="entry_UUID">UUID</string>
<string name="error_move_entry_here">Sem záznam přesunout nelze.</string>
<string name="error_copy_entry_here">Sem záznam zkopírovat nelze.</string>
@@ -331,8 +331,8 @@
<string name="list_groups_show_number_entries_summary">Zobrazit počet záznamů ve skupině</string>
<string name="content_description_background">Pozadí</string>
<string name="content_description_update_from_list">Aktualizovat</string>
<string name="content_description_keyboard_close_fields">Zavřít pole</string>
<string name="error_create_database_file">Nelze vytvořit databázi s tímto heslem a souborem klíče.</string>
<string name="content_description_keyboard_close_fields">Zavřít kolonky</string>
<string name="error_create_database_file">Nepodařilo se vytvořit databázi s tímto heslem a souborem klíče.</string>
<string name="menu_advanced_unlock_settings">Rozšířené odemknutí</string>
<string name="biometric">Biometrika</string>
<string name="biometric_auto_open_prompt_title">Automaticky otevřít pobídku</string>
@@ -345,15 +345,15 @@
<string name="entry_setup_otp">Nastavit heslo na jedno použití (OTP)</string>
<string name="otp_type">Typ OTP</string>
<string name="otp_secret">Tajnost</string>
<string name="otp_period">Interval (vteřiny)</string>
<string name="otp_period">Interval (sekundy)</string>
<string name="otp_counter">Čítač</string>
<string name="otp_digits">Číslice</string>
<string name="otp_algorithm">Algoritmus</string>
<string name="entry_otp">OTP</string>
<string name="error_invalid_OTP">Neplatná OTP tajnost.</string>
<string name="error_disallow_no_credentials">Nejméně jeden přihlašovací údaj musí být zadán.</string>
<string name="error_disallow_no_credentials">Je třeba aby byl zadán alespoň jeden přihlašovací údaj.</string>
<string name="error_copy_group_here">Sem skupinu kopírovat nelze.</string>
<string name="error_otp_secret_key">Tajný klíč musí mít formát Base32.</string>
<string name="error_otp_secret_key">Je třeba, aby tajný klíč byl ve formátu Base32.</string>
<string name="error_otp_counter">Čítač musít být mezi %1$d a %2$d.</string>
<string name="error_otp_period">Interval musít být mezi %1$d a %2$d vteřinami.</string>
<string name="error_otp_digits">Token musí obsahovat mezi %1$d a %2$d číslicemi.</string>
@@ -384,7 +384,7 @@
<string name="compression_none">Žádná</string>
<string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Nastavení klávesnice zařízení</string>
<string name="error_save_database">Nebylo možno uložit databázi.</string>
<string name="error_save_database">Databázi nebylo možné uložit.</string>
<string name="menu_save_database">Uložit databázi</string>
<string name="menu_empty_recycle_bin">Vysypat koš</string>
<string name="command_execution">Provádění příkazu…</string>
@@ -399,14 +399,14 @@
<string name="keyboard_auto_go_action_title">Akce auto-klávesy</string>
<string name="keyboard_auto_go_action_summary">Akce klávesy \"Jít\" po stisknutí klávesy \"Kolonka\"</string>
<string name="download_attachment">Stáhnout %1$s</string>
<string name="download_initialization">Zahájení…</string>
<string name="download_initialization">Zahajování…</string>
<string name="download_progression">Probíhá: %1$d%%</string>
<string name="download_finalization">Dokončování…</string>
<string name="download_complete">Kompletní!</string>
<string name="hide_expired_entries_title">Skrýt propadlé záznamy</string>
<string name="hide_expired_entries_summary">Propadlé záznamy nebudou ukázány</string>
<string name="download_complete">Dokončeno!</string>
<string name="hide_expired_entries_title">Skrýt záznamy kterým skončila platnost</string>
<string name="hide_expired_entries_summary">Nejsou zobrazovány záznamy kterým skončila platnost</string>
<string name="contact">Kontakt</string>
<string name="contribution">Příspěvky</string>
<string name="contribution">Podpoření vývoje</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>
@@ -424,18 +424,16 @@
<string name="html_about_contribution">Abychom si <strong>udrželi svoji svobodu</strong>, <strong>mohli opravovat chyby</strong>, <strong>přidávat nové funkce</strong> a <strong>byli pořád aktivní</strong>, počítáme s Vaším <strong>přispěním</strong>.</string>
<string name="error_create_database">Nepodařilo se vytvořit soubor databáze.</string>
<string name="entry_add_attachment">Přidat přílohu</string>
<string name="discard">Zavrhnout</string>
<string name="discard_changes">Zavrhnout změny\?</string>
<string name="discard">Zahodit</string>
<string name="discard_changes">Zahodit změny\?</string>
<string name="validate">Zkontrolovat</string>
<string name="education_setup_OTP_summary">Nastavit správu One-Time hesla (HOTP / TOTP) pro založení tokenu požadovaného pro dvoufázové ověření (2FA).</string>
<string name="education_setup_OTP_title">Nastavit OTP</string>
<string name="autofill_auto_search_summary">Automaticky navrhnout výsledky hledání z webové domény nebo ID aplikace</string>
<string name="autofill_auto_search_title">Samočinné hledání</string>
<string name="lock_database_show_button_summary">Zobrazí tlačítko zámku v uživatelském rozhraní</string>
<string name="lock_database_show_button_title">Zobrazit tlačítko zámku</string>
<string name="autofill_preference_title">Nastavení samovyplnění</string>
<string name="warning_database_link_revoked">Přístup k souboru zrušenému správcem souborů</string>
<string name="error_label_exists">Tento štítek již existuje.</string>
<string name="error_label_exists">Tento štítek už existuje.</string>
<string name="keyboard_search_share_summary">Při sdílení URL s KeePassDX filtrovat záznamy podle URL domény</string>
<string name="keyboard_search_share_title">Prohledat sdílené info</string>
<string name="autofill_block_restart">Aktivovat zamezení restartováním aplikace obsahující formulář.</string>
@@ -473,7 +471,7 @@
<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_summary">Po dokončení vyplňování formuláře se zeptat na uložení dat</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 výsledky vyhledávání</string>
@@ -505,7 +503,7 @@
<string name="advanced_unlock_prompt_not_initialized">Nelze inicializovat pobídku pro rozšířené odemknutí.</string>
<string name="advanced_unlock_scanning_error">Chyba při rozšířeném odemknutí: %1$s</string>
<string name="advanced_unlock_not_recognized">Otisk pro rozšířené odemknutí nebyl rozpoznán</string>
<string name="advanced_unlock_invalid_key">Nelze načíst klíč rozšířeného odemknutí. Prosím, smažte jej a opakujte proces rozpoznání odemknutí.</string>
<string name="advanced_unlock_invalid_key">Nedaří se načíst klíč rozšířeného odemknutí. Prosím, smažte ho a opakujte proces rozpoznání odemknutí.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Načíst důvěrný údaj pomocí dat rozšířeného odemknutí</string>
<string name="advanced_unlock_prompt_extract_credential_title">Otevřít pomocí rozšířeného odemykání</string>
<string name="advanced_unlock_prompt_store_credential_message">Varování: Pokud použijete rozpoznání rozšířeného odemknutí, musíte si i nadále pamatovat hlavní heslo.</string>
@@ -528,8 +526,8 @@
<string name="autofill_inline_suggestions_title">Návrhy inline</string>
<string name="autofill_inline_suggestions_summary">Pokusí ze zobrazit návrhy samovyplnění přímo z kompatibilní klávesnice</string>
<string name="warning_database_revoked">Přístup k souboru odebrán správcem souborů, uzavřete databázi a nově ji otevřete z jejího adresáře.</string>
<string name="menu_reload_database">Databázi nově načíst</string>
<string name="warning_database_info_changed_options">Přepsat externí změny uložením databáze nebo databázi včetně posledních změn nově načíst.</string>
<string name="menu_reload_database">Znovu načíst data</string>
<string name="warning_database_info_changed_options">Sloučit data, přepsat externí změny uložením databáze nebo databázi znovu načíst včetně nejnovějších změn.</string>
<string name="warning_database_info_changed">Informace obsažená ve Vašem databázovém souboru by změněna mimo aplikaci.</string>
<string name="unit_gibibyte">GiB</string>
<string name="unit_mebibyte">MiB</string>
@@ -543,7 +541,7 @@
<string name="style_brightness_title">Jas motivu</string>
<string name="error_upload_file">Během nahrávání souboru došlo k chybě.</string>
<string name="error_file_to_big">Soubor, který se pokoušíte nahrát, je příliš velký.</string>
<string name="content_description_otp_information">Info o jednorázovém hesle</string>
<string name="content_description_otp_information">Info o jednorázovém heslu</string>
<string name="properties">Vlastnosti</string>
<string name="error_export_app_properties">Během exportu vlastností aplikace došlo k chybě</string>
<string name="success_export_app_properties">Vlastnosti aplikace byly exportovány</string>
@@ -557,20 +555,20 @@
<string name="error_start_database_action">Během akce v databázi došlo k chybě.</string>
<string name="error_remove_file">Při odstraňování dat soboru došlo k chybě.</string>
<string name="error_duplicate_file">Datový soubor již existuje.</string>
<string name="error_move_group_here">Sem skupinu přesunout nemůžete.</string>
<string name="error_move_group_here">Sem skupinu nelze přesunout.</string>
<string name="error_word_reserved">Toto slovo je rezervováno a nelze je použít.</string>
<string name="templates">Předlohy</string>
<string name="templates_group_uuid_title">Skupina předloh</string>
<string name="templates_group_enable_summary">K vyplnění polí záznamu použijte dynamické předlohy</string>
<string name="templates_group_enable_title">Využití předloh</string>
<string name="version">Verze</string>
<string name="template">Předloha</string>
<string name="template">Šablona</string>
<string name="standard">Standard</string>
<string name="membership">Členství</string>
<string name="secure_note">Bezpečná poznámka</string>
<string name="international_bank_account_number">IBAN</string>
<string name="bank_identifier_code">SWIFT / BIC</string>
<string name="bank_name">Jméno banky</string>
<string name="bank_name">Název banky</string>
<string name="bank">Banka</string>
<string name="account">Účet</string>
<string name="seed">Seed</string>
@@ -589,9 +587,9 @@
<string name="personal_identification_number">PIN</string>
<string name="card_verification_value">CVV</string>
<string name="number">Číslo</string>
<string name="holder">Majitel</string>
<string name="debit_credit_card">Debitní / Kreditní karta</string>
<string name="template_group_name">Předlohy</string>
<string name="holder">Držitel</string>
<string name="debit_credit_card">Debetní / kreditní karta</string>
<string name="template_group_name">Šablony</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>
@@ -601,7 +599,7 @@
<string name="hint_icon_name">Jméno symbolu</string>
<string name="warning_exact_alarm">Nepovolili jste aplikaci použít přesný alarm. Výsledkem je, že funkce požadující časovač nebudou provedeny v přesný okamžik.</string>
<string name="permission">Povolení</string>
<string name="tags">Tagy</string>
<string name="tags">Štítky</string>
<string name="menu_merge_database">Sloučit databázi</string>
<string name="warning_database_info_reloaded">Opětovné načtení databáze smaže všechny lokálně změněné data.</string>
<string name="warning_keyfile_integrity">Hash souboru není garantován protože Android umožňuje změnu souboru v běhu. Změňte koncovku souboru na .bin pro opravu integrity.</string>
@@ -612,7 +610,18 @@
<string name="case_sensitive">Rozlišování velkých a malých písmen</string>
<string name="current_group">Tato skupina</string>
<string name="menu_merge_from">Sloučit z …</string>
<string name="custom_data">Vlastní data</string>
<string name="custom_data">Uživatelsky určené údaje</string>
<string name="search_filters">Vyhledat filtry</string>
<string name="menu_save_copy_to">Uložit kopii do …</string>
<string name="content_description_entry_background_color">Zadejte barvu pozadí</string>
<string name="content_description_entry_foreground_color">Zadejte barvu popředí</string>
<string name="expired">Platnost skončila</string>
<string name="show_entry_colors_summary">Zobrazí barvy popředí a pozadí v záznamu</string>
<string name="show_entry_colors_title">Barvy záznamu</string>
<string name="regex">Běžný výraz</string>
<string name="enable_keep_screen_on_summary">Při zobrazování položky zabránit zhasnutí obrazovky</string>
<string name="navigation_drawer_open">Otevření navigačního panelu</string>
<string name="navigation_drawer_close">Zavření navigačního panelu</string>
<string name="inherited">Převzít</string>
<string name="auto_type_sequence">Posloupnost automatického vyplňování</string>
</resources>

View File

@@ -429,8 +429,6 @@
<string name="discard">Kassér</string>
<string name="discard_changes">Kasser ændringer\?</string>
<string name="validate">Valider</string>
<string name="autofill_auto_search_summary">Foreslår automatisk søgeresultater fra webdomænet eller applikations-id</string>
<string name="autofill_auto_search_title">Automatisk søgning</string>
<string name="lock_database_show_button_summary">Viser låseknappen i brugergrænsefladen</string>
<string name="lock_database_show_button_title">Vis låseknap</string>
<string name="autofill_preference_title">Indstillinger for automatisk udfyldning</string>

View File

@@ -199,7 +199,7 @@
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string>
<string name="set_autofill_service_title">Standarddienst für automatisches Ausfüllen festlegen</string>
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um Formulare schnell in anderen Apps auszufüllen</string>
<string name="autofill_select_entry">Eintrag auswählen…</string>
<string name="autofill_select_entry">Eintrag auswählen </string>
<string name="clipboard">Zwischenablage</string>
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
<string name="biometric_delete_all_key_summary">Alle Verschlüsselungsschlüssel löschen, die mit der modernen Entsperrerkennung zusammenhängen</string>
@@ -252,7 +252,7 @@
<string name="education_donation_summary">Mithelfen, um Stabilität und Sicherheit zu verbessern sowie weitere Funktionen zu ermöglichen.</string>
<string name="html_text_ad_free">Anders als viele andere Passwortmanager ist dieser &lt;strong&gt;werbefrei&lt;/strong&gt;, &lt;strong&gt;quelloffen&lt;/strong&gt; und unter einer &lt;strong&gt;Copyleft-Lizenz&lt;/strong&gt;. Es werden keine persönlichen Daten gesammelt, in welcher Form auch immer, unabhängig von der verwendeten Version (kostenlos oder Pro).</string>
<string name="html_text_buy_pro">Mit dem Kauf der Pro-Version erhalten Sie Zugriff auf diesen &lt;strong&gt;visuellen Stil&lt;/strong&gt; und unterstützen insbesondere &lt;strong&gt;die Umsetzung gemeinschaftlicher Projekte.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Dieser &lt;strong&gt;visuelle Stil&lt;/strong&gt; wurde wegen Ihrer Großzügigkeit freigeschaltet.</string>
<string name="html_text_feature_generosity">Dieser <strong>visuelle Stil</strong> ist dank Ihrer Großzügigkeit verfügbar.</string>
<string name="html_text_donation">Um unsere Freiheit zu bewahren und immer aktiv zu bleiben, zählen wir auf Ihren &lt;strong&gt;Beitrag.&lt;/strong&gt;</string>
<string name="html_text_dev_feature">Diese Funktion ist &lt;strong&gt;in Entwicklung&lt;/strong&gt; und erfordert &lt;strong&gt;Ihren Beitrag&lt;/strong&gt;, um bald verfügbar zu sein.</string>
<string name="html_text_dev_feature_buy_pro">Durch den Kauf der &lt;strong&gt;Pro-Version&lt;/strong&gt;,</string>
@@ -317,7 +317,7 @@
<string name="show_recent_files_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</string>
<string name="hide_broken_locations_title">Defekte Datenbankverknüpfungen ausblenden</string>
<string name="hide_broken_locations_summary">Nicht funktionierende Verknüpfungen in der Liste der zuletzt verwendeten Datenbanken ausblenden</string>
<string name="do_not_kill_app">App nicht beenden </string>
<string name="do_not_kill_app">App nicht beenden </string>
<string name="lock_database_back_root_summary">Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird</string>
<string name="clear_clipboard_notification_title">Beim Schließen löschen</string>
<string name="recycle_bin">Papierkorb</string>
@@ -445,8 +445,6 @@
<string name="discard">Verwerfen</string>
<string name="discard_changes">Änderungen verwerfen\?</string>
<string name="validate">Validieren</string>
<string name="autofill_auto_search_summary">Suchergebnisse automatisch nach Web-Domain oder Anwendungs-ID vorschlagen</string>
<string name="autofill_auto_search_title">Automatische Suche</string>
<string name="autofill_manual_selection_title">Manuelle Auswahl</string>
<string name="autofill_manual_selection_summary">Manuelle Auswahl des Datenbankeintrags ermöglichen</string>
<string name="lock_database_show_button_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string>
@@ -455,7 +453,7 @@
<string name="warning_database_link_revoked">Zugriff auf die Datei durch den Dateimanager widerrufen</string>
<string name="error_label_exists">Diese Bezeichnung existiert bereits.</string>
<string name="keyboard_search_share_summary">Beim Teilen einer URL mit KeePassDX die Einträge nach dieser URL filtern</string>
<string name="keyboard_search_share_title">Gemeinsame Infos durchsuchen</string>
<string name="keyboard_search_share_title">Gemeinsame Infos auswählen</string>
<string name="autofill_block_restart">Starten Sie die Anwendung, die das Formular enthält, neu, um die Sperrung zu aktivieren.</string>
<string name="autofill_block">Automatisches Ausfüllen sperren</string>
<string name="autofill_web_domain_blocklist_summary">Liste der Domains, auf denen ein automatisches Ausfüllen verhindert wird</string>
@@ -470,8 +468,8 @@
<string name="keyboard_change">Tastatur wechseln</string>
<string name="keyboard_previous_fill_in_title">Auto-Key-Aktion</string>
<string name="keyboard_previous_database_credentials_title">Datenbank-Anmeldebildschirm</string>
<string name="keyboard_previous_fill_in_summary">Nach Ausführung der automatischen Tastenaktion automatisch zur vorherigen Tastatur wechseln</string>
<string name="keyboard_previous_database_credentials_summary">Automatisches Zurückschalten zur vorherigen Tastatur auf dem Datenbank-Anmeldebildschirm</string>
<string name="keyboard_previous_fill_in_summary">Nach dem Ausführen der automatischen Tastenaktion automatisch zur vorherigen Tastatur wechseln</string>
<string name="keyboard_previous_database_credentials_summary">Auf dem Datenbank-Anmeldebildschirm automatisch zur vorherigen Tastatur wechseln</string>
<string name="education_add_attachment_summary">Laden Sie einen Anhang für Ihren Eintrag hoch, um wichtige externe Daten zu speichern.</string>
<string name="content_description_credentials_information">Anmeldeinformationen</string>
<string name="data">Daten</string>
@@ -492,7 +490,7 @@
<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>
<string name="keyboard_previous_lock_summary">Nach dem Sperren der Datenbank automatisch zur vorherigen Tastatur zurückschalten</string>
<string name="keyboard_previous_lock_summary">Nach dem Sperren der Datenbank automatisch zur vorherigen Tastatur wechseln</string>
<string name="keyboard_previous_lock_title">Datenbank sperren</string>
<string name="notification">Benachrichtigung</string>
<string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string>
@@ -507,10 +505,10 @@
<string name="autofill_save_search_info_title">Suchinformationen speichern</string>
<string name="autofill_close_database_summary">Datenbank nach Auswahl eines Eintrags zum automatischen Ausfüllen schließen</string>
<string name="keyboard_save_search_info_summary">Nachdem eine URL mit KeePassDX geteilt und ein Eintrag gewählt wurde, wird versucht, sich diesen Eintrag für die weitere Nutzung zu merken</string>
<string name="keyboard_save_search_info_title">Gemeinsam genutzte Informationen speichern</string>
<string name="keyboard_save_search_info_title">Gemeinsame Informationen speichern</string>
<string name="warning_empty_recycle_bin">Sollen alle ausgewählten Knoten wirklich aus dem Papierkorb gelöscht werden\?</string>
<string name="error_field_name_already_exists">Der Feldname existiert bereits.</string>
<string name="advanced_unlock_prompt_store_credential_message">Warnung: Sie müssen sich immer noch an Ihr Masterpasswort erinnern, wenn Sie die moderne Entsperrerkennung verwenden.</string>
<string name="advanced_unlock_prompt_store_credential_message">Sie müssen sich immer noch an Ihre Anmeldedaten erinnern, wenn Sie die moderne Entsperrerkennung verwenden.</string>
<string name="open_advanced_unlock_prompt_store_credential">Zum Speichern der Anmeldeinformationen Dialog zum modernen Entsperren öffnen</string>
<string name="open_advanced_unlock_prompt_unlock_database">Dialog zum modernen Entsperren der Datenbank öffnen</string>
<string name="menu_keystore_remove_key">Schlüssel für modernes Entsperren löschen</string>
@@ -633,9 +631,33 @@
<string name="search_filters">Suchfilter</string>
<string name="current_group">Aktuelle Gruppe</string>
<string name="case_sensitive">Groß-/Kleinschreibung beachten</string>
<string name="menu_merge_from">Zusammenführen von </string>
<string name="menu_save_copy_to">Kopie speichern unter </string>
<string name="menu_merge_from">Zusammenführen von </string>
<string name="menu_save_copy_to">Kopie speichern unter </string>
<string name="content_description_nav_header">Navigationskopfzeile</string>
<string name="navigation_drawer_close">Navigationsleiste schließen</string>
<string name="navigation_drawer_open">Navigationsleiste öffnen</string>
<string name="expired">Abgelaufen</string>
<string name="warning_database_already_opened">Eine Datenbank ist bereits geöffnet, schließen Sie diese, um die neue zu öffnen</string>
<string name="advanced_unlock_keystore_warning">Diese Funktion speichert verschlüsselte Anmeldedaten im sicheren Schlüsselspeicher Ihres Geräts.
\n
\nAbhängig von der nativen API-Implementierung des Betriebssystems ist sie möglicherweise nicht voll funktionsfähig.
\nÜberprüfen Sie die Kompatibilität und Sicherheit des Schlüsselspeichers mit dem Hersteller Ihres Geräts und dem Ersteller des von Ihnen verwendeten ROMs.</string>
<string name="content_description_passphrase_word_count">Passphrase-Wortanzahl</string>
<string name="passphrase">Passphrase</string>
<string name="colorize_password_title">Passwörter einfärben</string>
<string name="colorize_password_summary">Passwortzeichen nach Typ einfärben</string>
<string name="keyboard_previous_search_title">Suchbildschirm</string>
<string name="keyboard_previous_search_summary">Auf dem Suchbildschirm automatisch zur vorherigen Tastatur wechseln</string>
<string name="entropy">Entropie: %1$s Bit</string>
<string name="entropy_high">Entropie: Hoch</string>
<string name="entropy_calculate">Entropie: Berechnen </string>
<string name="at_least_one_char">Mindestens ein Zeichen von jedem</string>
<string name="consider_chars_filter">Zeichen berücksichtigen</string>
<string name="word_separator">Trennzeichen</string>
<string name="ignore_chars_filter">Zeichen ignorieren</string>
<string name="lower_case">Kleinbuchstaben</string>
<string name="upper_case">GROẞBUCHSTABEN</string>
<string name="character_count">Anzahl der Zeichen: %1$d</string>
<string name="exclude_ambiguous_chars">Mehrdeutige Zeichen ausschließen</string>
<string name="title_case">Groß-/Kleinschreibung des Titels</string>
</resources>

View File

@@ -413,8 +413,6 @@
<string name="contribution">Συνεισφορά</string>
<string name="education_setup_OTP_summary">Ρύθμιση διαχείρισης μίας-χρήσης κωδικού πρόσβασης (HOTP / TOTP) για δημιουργία token που ζητήθηκε για έλεγχο ταυτότητας δύο παραγόντων (2FA).</string>
<string name="education_setup_OTP_title">Ρύθμιση OTP</string>
<string name="autofill_auto_search_summary">Προτείνετε αυτόματα αποτελέσματα αναζήτησης από τον τομέα ιστού ή το αναγνωριστικό εφαρμογής</string>
<string name="autofill_auto_search_title">Αυτόματη αναζήτηση</string>
<string name="lock_database_show_button_summary">Εμφανίζει το κουμπί κλειδώματος στη διεπαφή χρήστη</string>
<string name="lock_database_show_button_title">Εμφάνιση κουμπιού κλειδώματος</string>
<string name="autofill_preference_title">Ρυθμίσεις Αυτόματης Συμπλήρωσης</string>
@@ -492,7 +490,7 @@
<string name="error_registration_read_only">Η αποθήκευση ενός νέου αντικειμένου δεν επιτρέπεται σε μια βάση δεδομένων μόνο για ανάγνωση</string>
<string name="error_field_name_already_exists">Το όνομα πεδίου υπάρχει ήδη.</string>
<string name="advanced_unlock_prompt_store_credential_title">Προηγμένο ξεκλείδωμα αναγνώρισης</string>
<string name="advanced_unlock_prompt_store_credential_message">Προειδοποίηση: Θα πρέπει να θυμάστε τον κύριο κωδικό πρόσβασης εάν χρησιμοποιείτε προηγμένο ξεκλείδωμα αναγνώρισης.</string>
<string name="advanced_unlock_prompt_store_credential_message">Πρέπει ακόμα να θυμάστε τα κύρια διαπιστευτήριά σας εάν χρησιμοποιείτε σύνθετη αναγνώριση ξεκλειδώματος.</string>
<string name="advanced_unlock_prompt_extract_credential_title">Ανοίξτε τη βάση δεδομένων με προηγμένο ξεκλείδωμα αναγνώρισης</string>
<string name="open_advanced_unlock_prompt_store_credential">Ανοίξτε τη προηγμένη προτροπή ξεκλειδώματος για αποθήκευση διαπιστευτηρίων</string>
<string name="open_advanced_unlock_prompt_unlock_database">Ανοίξτε τη προηγμένη προτροπή ξεκλειδώματος για να ξεκλειδώσετε τη βάση δεδομένων</string>
@@ -625,4 +623,28 @@
<string name="current_group">Τρέχουσα ομάδα</string>
<string name="regex">Κοινή έκφραση</string>
<string name="menu_save_copy_to">Αποθήκευση αντιγράφου στο …</string>
<string name="expired">Ληγμένο</string>
<string name="warning_database_already_opened">Μια βάση δεδομένων είναι ήδη ανοιχτή, κλείστε την πρώτα για να ανοίξετε τη νέα</string>
<string name="advanced_unlock_keystore_warning">Αυτή η δυνατότητα θα αποθηκεύσει κρυπτογραφημένα δεδομένα διαπιστευτηρίων στο ασφαλές KeyStore της συσκευής σας.
\n
\nΑνάλογα με την εγγενή υλοποίηση API του λειτουργικού συστήματος, ενδέχεται να μην είναι πλήρως λειτουργικό.
\nΕλέγξτε τη συμβατότητα και την ασφάλεια του KeyStore με τον κατασκευαστή της συσκευής σας και τον δημιουργό της ROM που χρησιμοποιείτε.</string>
<string name="passphrase">Συνθηματική φράση</string>
<string name="colorize_password_summary">Χρωματίστε τους χαρακτήρες του κωδικού πρόσβασης ανά τύπο</string>
<string name="keyboard_previous_search_title">Οθόνη αναζήτησης</string>
<string name="keyboard_previous_search_summary">Αυτόματη εναλλαγή στο προηγούμενο πληκτρολόγιο στην οθόνη αναζήτησης</string>
<string name="entropy">Εντροπία: %1$s bit</string>
<string name="entropy_calculate">Εντροπία: Υπολογισμός…</string>
<string name="title_case">Γράμματα Τίτλου</string>
<string name="colorize_password_title">Χρωματίστε τους κωδικούς πρόσβασης</string>
<string name="entropy_high">Εντροπία: Υψηλή</string>
<string name="consider_chars_filter">Σκεφτείτε χαρακτήρες</string>
<string name="exclude_ambiguous_chars">Εξαιρέστε διφορούμενους χαρακτήρες</string>
<string name="content_description_passphrase_word_count">Πλήθος λέξεων συνθηματικής φράσης</string>
<string name="at_least_one_char">Τουλάχιστον ένας χαρακτήρας από το καθένα</string>
<string name="ignore_chars_filter">Αγνοήστε χαρακτήρες</string>
<string name="lower_case">πεζά γράμματα</string>
<string name="upper_case">ΚΕΦΑΛΑΙΑ ΓΡΑΜΜΑΤΑ</string>
<string name="word_separator">Διαχωριστής</string>
<string name="character_count">Αριθμός χαρακτήρων: %1$d</string>
</resources>

View File

@@ -502,8 +502,6 @@
<string name="autofill_ask_to_save_data_title">Pedir que se guarden los datos</string>
<string name="autofill_save_search_info_summary">Intente guardar la información de la búsqueda cuando haga una selección de entrada manual</string>
<string name="autofill_save_search_info_title">Guardar la información de la búsqueda</string>
<string name="autofill_auto_search_summary">Sugerir automáticamente los resultados de la búsqueda desde el dominio web o el ID de la aplicación</string>
<string name="autofill_auto_search_title">Búsqueda automática</string>
<string name="autofill_close_database_summary">Cerrar la base de datos después de una selección de autocompletado</string>
<string name="autofill_close_database_title">Cerrar la base de datos</string>
<string name="enter">Entrar</string>
@@ -627,4 +625,5 @@
<string name="menu_merge_from">Fusionar desde …</string>
<string name="menu_save_copy_to">Guardar una copia en …</string>
<string name="inherited">Heredar</string>
<string name="expired">Caducada</string>
</resources>

View File

@@ -449,8 +449,6 @@
<string name="discard">Abandonner</string>
<string name="discard_changes">Abandonner les modifications \?</string>
<string name="validate">Valider</string>
<string name="autofill_auto_search_summary">Suggérer automatiquement des résultats de recherche à partir du domaine Web ou de lidentifiant de lapplication</string>
<string name="autofill_auto_search_title">Recherche automatique</string>
<string name="lock_database_show_button_summary">Affiche le bouton de verrouillage dans linterface utilisateur</string>
<string name="lock_database_show_button_title">Afficher le bouton de verrouillage</string>
<string name="autofill_preference_title">Paramètres de remplissage automatique</string>
@@ -462,8 +460,8 @@
<string name="autofill_web_domain_blocklist_title">Liste de blocage de domaine Web</string>
<string name="autofill_application_id_blocklist_summary">Liste de blocage qui empêche le remplissage automatique des applications</string>
<string name="autofill_application_id_blocklist_title">Liste de blocage dapplication</string>
<string name="keyboard_search_share_summary">Lorsqu\'une adresse web est partagée avec KeePassDX, filtrer automatiquement les entrées contenant le domaine</string>
<string name="keyboard_search_share_title">Rechercher les informations partagées</string>
<string name="keyboard_search_share_summary">Lorsque des infos sont partagées avec KeePassDX, filtre les entrées en utilisant ces infos pour alimenter le Magiclavier</string>
<string name="keyboard_search_share_title">Sélectionner les infos partagées</string>
<string name="filter">Filtre</string>
<string name="subdomain_search_summary">Recherche des domaines Web avec des contraintes de sous-domaines</string>
<string name="subdomain_search_title">Recherche de sous-domaine</string>
@@ -490,13 +488,13 @@
<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 de sauvegarde des données à la fin du remplissage d\'un formulaire</string>
<string name="autofill_ask_to_save_data_title">Demander à enregistrer des données</string>
<string name="autofill_save_search_info_summary">Essayer denregistrer les informations de recherche lors de la sélection manuelle dune entrée</string>
<string name="autofill_save_search_info_summary">Essaye denregistrer les informations de recherche lors de la sélection manuelle dune entrée</string>
<string name="autofill_save_search_info_title">Enregistrer les infos de recherche</string>
<string name="autofill_close_database_summary">Ferme la base de données après une sélection de remplissage automatique</string>
<string name="autofill_close_database_title">Fermer la base de données</string>
<string name="keyboard_previous_lock_summary">Revient automatiquement au clavier précédent après le verrouillage de la base de données</string>
<string name="keyboard_previous_lock_title">Verrouiller la base de données</string>
<string name="keyboard_save_search_info_summary">Essayer denregistrer l\'association entre une adresse web partagée à KeePassDX et l\'entrée sélectionnée manuellement</string>
<string name="keyboard_save_search_info_summary">Après avoir partagé des infos avec KeePassDX, lorsqu\'une entrée est sélectionnée, essaye d\'enregistrer les infos dans l\'entrée pour faciliter les utilisations futures</string>
<string name="keyboard_save_search_info_title">Enregistrer les infos partagées</string>
<string name="notification">Notification</string>
<string name="biometric_security_update_required">Mise à jour de sécurité biométrique requise.</string>
@@ -517,7 +515,7 @@
<string name="advanced_unlock_invalid_key">Impossible de lire la clé de déverrouillage avancé. Veuillez la supprimer et répéter la procédure de reconnaissance de déverrouillage.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Extraire les identifiants de la base de données avec des données de déverrouillage avancées</string>
<string name="advanced_unlock_prompt_extract_credential_title">Ouvrir la base de données avec la reconnaissance de déverrouillage avancée</string>
<string name="advanced_unlock_prompt_store_credential_message">Attention : vous devez toujours vous souvenir de votre mot de passe principal si vous utilisez le déverrouillage avancé.</string>
<string name="advanced_unlock_prompt_store_credential_message">Vous devez toujours vous souvenir de votre identifiant principal si vous utilisez le déverrouillage avancé.</string>
<string name="advanced_unlock_prompt_store_credential_title">Reconnaissance de déverrouillage avancée</string>
<string name="open_advanced_unlock_prompt_store_credential">Ouvrez l\'invite de déverrouillage avancé pour stocker les informations d\'identification</string>
<string name="open_advanced_unlock_prompt_unlock_database">Ouvrez l\'invite de déverrouillage avancé pour déverrouiller la base de données</string>
@@ -636,4 +634,27 @@
<string name="custom_data">Données personnalisées</string>
<string name="case_sensitive">Sensible à la casse</string>
<string name="expired">Expiré</string>
<string name="warning_database_already_opened">Une base de données est déjà ouverte, fermez-la d\'abord pour ouvrir la nouvelle base</string>
<string name="advanced_unlock_keystore_warning">Cette fonction permet de stocker des données d\'identification chiffrées dans le KeyStore sécurisé de votre appareil.
\n
\nSelon l\'implémentation de l\'API native du système d\'exploitation, il se peut qu\'elle ne soit pas entièrement fonctionnelle.
\nVérifiez la compatibilité et la sécurité du KeyStore auprès du fabricant de votre appareil et du créateur de la ROM que vous utilisez.</string>
<string name="content_description_passphrase_word_count">Nombre de mots</string>
<string name="keyboard_previous_search_title">Écran de recherche</string>
<string name="entropy">Entropie : %1$s bit</string>
<string name="entropy_high">Entropie : Haute</string>
<string name="at_least_one_char">Au moins un caractère de chaque</string>
<string name="exclude_ambiguous_chars">Exclure les caractères ambigus</string>
<string name="ignore_chars_filter">Ignorer les caractères</string>
<string name="lower_case">minuscule</string>
<string name="upper_case">MAJUSCULE</string>
<string name="title_case">1ère Lettre Majuscule</string>
<string name="keyboard_previous_search_summary">Retour automatique au clavier précédent sur l\'écran de recherche</string>
<string name="colorize_password_title">Coloriser les mots de passe</string>
<string name="entropy_calculate">Entropie : Calcule…</string>
<string name="passphrase">Phrase de passe</string>
<string name="colorize_password_summary">Coloriser les caractères du mot de passe par type</string>
<string name="consider_chars_filter">Considérer les caractères</string>
<string name="word_separator">Séparateur</string>
<string name="character_count">Nombre de caractères : %1$d</string>
</resources>

View File

@@ -16,37 +16,37 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="feedback">प्प्रतिपुष्टिा</string>
--><resources>
<string name="feedback">प्रतिक्रिया</string>
<string name="homepage">होमपेज</string>
<string name="about_description">एंड्रॉयड पर आधारित KeePass पासवर्ड मैनेजर</string>
<string name="accept">स्वीकार</string>
<string name="accept">स्वीकार करें</string>
<string name="add_entry">रिकॉर्ड जोड़ें</string>
<string name="edit_entry">रिकॉर्ड संपादित करें</string>
<string name="add_group">समूह जोड़ें</string>
<string name="encryption">एन्क्रिप्शन</string>
<string name="encryption_algorithm">एन्क्रिप्शन एल्गोरिथम</string>
<string name="key_derivation_function">कुंजी उत्पत्ति कृत्य</string>
<string name="app_timeout">ऐप का समय समाप्त</string>
<string name="app_timeout_summary">ऐप लॉक होने से पहले निष्क्रियता</string>
<string name="app_timeout">समय समाप्त</string>
<string name="app_timeout_summary">डाटाबेस के बन्द होने से पहले तक का समय</string>
<string name="application">ऐप</string>
<string name="brackets">कोष्ठक</string>
<string name="extended_ASCII">विस्तारित ASCII</string>
<string name="allow">अनुमति दें</string>
<string name="clipboard_cleared">क्लिपबोर्ड साफ कर दिया</string>
<string name="clipboard_cleared">क्लिपबोर्ड साफ किया गया</string>
<string name="clipboard_error_title">क्लिपबोर्ड एरर</string>
<string name="clipboard_error">सैमसंग के कुछ एंड्रॉइड फोन क्लिपबोर्ड का पयोग नहीं करने देंगे।</string>
<string name="clipboard_error">कुछ डिवाइस ऐप्स को क्लिपबोर्ड का प्रयोग करने की अनुमति नहीं देंगे।</string>
<string name="clipboard_error_clear">क्लिपबोर्ड को साफ़ नहीं किया जा सका</string>
<string name="clipboard_timeout">क्लिपबोर्ड टाइमआउट</string>
<string name="clipboard_timeout_summary">क्लिपबोर्ड में भंडारण की अवधि</string>
<string name="clipboard_timeout_summary">क्लिपबोर्ड में भंडारण की अवधि (यदि आपके डिवाइस द्वारा समर्थित है)</string>
<string name="select_to_copy">क्लिपबोर्ड पर %1$s को कॉपी करने के लिए चयन करें</string>
<string name="retrieving_db_key">डेटाबेस कुंजी पुनर्प्राप्त क रह है…</string>
<string name="retrieving_db_key">डेटाबेस कुंजी पुनर्प्राप्त की जा रह है…</string>
<string name="database">डेटाबेस</string>
<string name="decrypting_db">डेटाबेस सामग्री डिक्रिप्टिंग</string>
<string name="decrypting_db">डेटाबेस सामग्री डिक्रिप्ट कर रहे है</string>
<string name="default_checkbox">डिफ़ॉल्ट डेटाबेस के रूप में उपयोग करें</string>
<string name="digits">अंक</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft बिल्कुल कोई वारंटी के साथ आता है । यह लाइब्रे सॉफ्टवेयर है, और जीपीएल संस्करण 3 या बाद की शर्तों के तहत इसे पुनर्वितरित करने के लिए आपका स्वागत है।</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft <strong>ओपन सोर्स</strong> है और <strong>बिना किसी विज्ञापन</strong> के उपलब्ध है।
\nइसे <strong>GPLv3</strong> लाइसेंस के अन्तर्गत बिना किसी वारन्टि के प्रदान किया गया है।</string>
<string name="entry_accessed">एक्सेस किया गया</string>
<string name="entry_cancel">रद्द करें</string>
<string name="entry_notes">टिप्पणियाँ</string>
@@ -63,13 +63,13 @@
<string name="entry_user_name">उपयोगकर्ता का नाम</string>
<string name="error_can_not_handle_uri">KeePassDX में इस URI को संभाल नहीं सका।</string>
<string name="error_file_not_create">फाइल नहीं बना सका</string>
<string name="error_invalid_db">टाबेस नहीं पढ़ सका।</string>
<string name="error_invalid_db">टाबेस को पढ़ा नहीं जा सका।</string>
<string name="error_invalid_path">सुनिश्चित करें कि रास्ता सही है।</string>
<string name="error_no_name">एक नाम दर्ज करें।</string>
<string name="error_nokeyfile">कीफाइल का चयन करें।</string>
<string name="error_out_of_memory">आपके पूरे डेटाबेस को लोड करने के लिए कोई मेमोरी नहीं।</string>
<string name="error_nokeyfile">कीफाइल का चयन करें।</string>
<string name="error_out_of_memory">आपके पूरे डेटाबेस को लोड करने के लिए मेमोरी नहीं है</string>
<string name="error_load_database">आपके डेटाबेस को लोड नहीं किया जा सका।</string>
<string name="error_load_database_KDF_memory">कुंजी लोड नहीं कर सककेडीएफ \"मेमोरी उपयोग\" को कम करने का प्रयास करें।</string>
<string name="error_load_database_KDF_memory">कुंजी लोड नहीं कर सकKDF \"मेमोरी उपयोग\" को कम करने का प्रयास करें।</string>
<string name="error_pass_gen_type">कम से कम एक पासवर्ड जनरेशन प्रकार का चयन करना होगा।</string>
<string name="error_pass_match">पासवर्ड मेल नहीं खाते हैं।</string>
<string name="error_rounds_too_large">\"परिवर्तन राउंड\" बहुत अधिक है। 2147483648 पर सेट हो रहा है।</string>
@@ -85,15 +85,15 @@
<string name="content_description_repeat_toggle_password_visibility">पासवर्ड दृश्यता टॉगल दोहराएं</string>
<string name="content_description_entry_icon">प्रवेश आइकन</string>
<string name="entry_password_generator">पासवर्ड जनरेटर</string>
<string name="content_description_password_length">पासवर्ड की लबाई</string>
<string name="content_description_password_length">पासवर्ड की लम्बाई</string>
<string name="entry_add_field">फ़ील्ड जोड़ें</string>
<string name="content_description_remove_field">फ़ील्ड निकालें</string>
<string name="content_description_remove_field">फ़ील्ड हटाये</string>
<string name="entry_UUID">UUID</string>
<string name="file_manager_install_description">"डेटाबेस फाइल को बनाने खोलने और सेव करने के लिए आपके फोन में फाइल मैनेजर ऐप होना चाहिए जोकि दिए गए एक्शन को सपोर्ट कर सके ACTION_CREATE_DOCUMENT और ACTION_OPEN_DOCUMENT"</string>
<string name="file_manager_install_description">डेटाबेस फाइल को बनाने, खोलने और सेव करने के लिए आपके फोन में फाइल मैनेजर ऐप होना चाहिए जो ACTION_CREATE_DOCUMENT और ACTION_OPEN_DOCUMENT कार्यों को करने मे सक्षम हो।</string>
<string name="content_description_background">पृष्ठभूमि</string>
<string name="content_description_update_from_list">अपडेट करें</string>
<string name="content_description_remove_from_list">निकालें</string>
<string name="content_description_keyboard_close_fields">बंद क्षेत्र</string>
<string name="content_description_remove_from_list">हटाएँ</string>
<string name="content_description_keyboard_close_fields">फ़ील्ड बंद करें</string>
<string name="master_key">मास्टर कुंजी</string>
<string name="security">सुरक्षा</string>
<string name="entry_history">इतिहास</string>
@@ -105,6 +105,65 @@
<string name="otp_digits">अंक</string>
<string name="otp_algorithm">एल्गोरिथ्म</string>
<string name="entry_otp">ओटीप</string>
<string name="error_invalid_OTP">अमान्य ओटीपी गुप्त</string>
<string name="error_invalid_OTP">अमान्य ओटीपी रहस्य</string>
<string name="error_disallow_no_credentials">कम से कम एक क्रेडेंशियल सेट किया जाना चाहिए।</string>
</resources>
<string name="email">ईमेल</string>
<string name="email_address">ईमेल पता</string>
<string name="ssid">SSID</string>
<string name="error_otp_digits">टोकन में %1$d से %2$d अंक होने चाहिए।</string>
<string name="style_choose_summary">ऐप में इस्तेमाल की गई थीम</string>
<string name="style_brightness_summary">हल्की या गहरी थीम चुनें</string>
<string name="error_move_group_here">आप यहा किसी समूह को स्थानांतरित नहीं कर सकते है।</string>
<string name="content_description_database_color">डाटाबेस का रंग</string>
<string name="error_otp_secret_key">गुप्त कुंजी Base32 स्वरूप में होनी आवश्यक है।</string>
<string name="wireless">वाई-फाई</string>
<string name="hide_expired_entries_title">समाप्त हो चुकी प्रविष्टियो को छुपाएं</string>
<string name="icon_pack_choose_title">आइकन पैक</string>
<string name="contribution">योगदान</string>
<string name="error_copy_entry_here">आप यहां प्रविष्टि की प्रतिलिपि नहीं बना सकते।</string>
<string name="error_copy_group_here">आप यहाँ समूह की प्रतिलिपि नहीं कर सकते हैं।</string>
<string name="error_create_database_file">इस पासवर्ड और कीफ़ाइल के साथ डेटाबेस बनाने में असमर्थ।</string>
<string name="template_group_name">प्रारूप</string>
<string name="discard_changes">परिवर्तनों को निरस्त करें\?</string>
<string name="content_description_otp_information">वन-टाइम पासवर्ड की जानकारी</string>
<string name="personal_identification_number">PIN</string>
<string name="name">नाम</string>
<string name="error_word_reserved">यह शब्द आरक्षित है और इसका उपयोग नहीं किया जा सकता है।</string>
<string name="allow_copy_password_summary">प्रविष्टि पासवर्ड और संरक्षित फ़ील्ड को क्लिपबोर्ड पर कॉपी करने की अनुमति दें</string>
<string name="card_verification_value">CVV</string>
<string name="error_create_database">डेटाबेस फ़ाइल बनाने में असमर्थ।</string>
<string name="error_otp_period">अवधि %1$d और %2$d सेकैंड के बीच होनी चाहिए।</string>
<string name="public_key">सार्वजनिक कुंजी</string>
<string name="current_group">वर्तमान समूह</string>
<string name="error_move_entry_here">आप यहा किसी प्रविष्टि को स्थानांतरित नहीं कर सकते है।</string>
<string name="error_save_database">डेटाबेस को सेव नहीं किया जा सका।</string>
<string name="error_otp_counter">काउंटर %1$d और %2$d के बीच में होना चाहिए।</string>
<string name="unit_gibibyte">GiB</string>
<string name="hide_expired_entries_summary">समाप्त हो चुकी प्रविष्टियों को नहीं दिखाया गया है</string>
<string name="unit_kibibyte">KiB</string>
<string name="unit_mebibyte">MiB</string>
<string name="style_choose_title">ऐप थीम</string>
<string name="icon_pack_choose_summary">ऐप में इस्तेमाल किया गया आइकन पैक</string>
<string name="content_description_credentials_information">क्रेडेंशियल्स कि जानकारी</string>
<string name="contact">संपर्क करें</string>
<string name="validate">सत्यापित करें</string>
<string name="discard">निरस्त करें</string>
<string name="entry_add_attachment">अटेचमेंट लगाये</string>
<string name="debit_credit_card">डेबिट / क्रेडिट कार्ड</string>
<string name="holder">धारक</string>
<string name="bank">बैंक</string>
<string name="bank_name">बैंक का नाम</string>
<string name="bank_identifier_code">SWIFT / BIC</string>
<string name="international_bank_account_number">IBAN</string>
<string name="about">के बारे मे</string>
<string name="download_canceled">रद्द!</string>
<string name="unit_byte">B</string>
<string name="show_entry_colors_title">प्रविष्टि के रंग</string>
<string name="error_rebuild_list">सूची का ठीक से पुनर्निर्माण करने में असमर्थ।</string>
<string name="error_file_to_big">आप जिस फ़ाइल को अपलोड करने का प्रयास कर रहे हैं वह बहुत बड़ी है।</string>
<string name="error_remove_file">फ़ाइल डेटा को हटाते समय त्रुटि हुई।</string>
<string name="error_field_name_already_exists">फ़ील्ड का नाम पहले से मौजूद है।</string>
<string name="error_database_uri_null">डेटाबेस URI को पुनःप्राप्त नहीं किया जा सका।</string>
<string name="error_upload_file">फ़ाइल डेटा को अपलोड करते समय एक त्रुटि हुई।</string>
<string name="error_duplicate_file">फ़ाइल डेटा पहले से उपलब्ध है।</string>
</resources>

View File

@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--><resources>
<string name="about_description">Android implementacija KeePass upravitelja lozinki</string>
<string name="about_description">Android implementacija KeePass upravlja lozinki</string>
<string name="accept">Prihvati</string>
<string name="add_entry">Dodaj unos</string>
<string name="edit_entry">Uredi unos</string>
@@ -31,13 +31,13 @@
<string name="application">Aplikacija</string>
<string name="brackets">Zagrade</string>
<string name="extended_ASCII">Prošireni ASCII</string>
<string name="file_manager_install_description">Za stvaranje, otvaranje i spremanje datoteka baze podataka potreban je upravitelj datoteka koji prihvaća zahtjeve ACTION_CREATE_DOCUMENT i ACTION_OPEN_DOCUMENT.</string>
<string name="file_manager_install_description">Za stvaranje, otvaranje i spremanje datoteka baze podataka potreban je upravlj datoteka koji prihvaća zahtjeve ACTION_CREATE_DOCUMENT i ACTION_OPEN_DOCUMENT.</string>
<string name="allow">Dozvoli</string>
<string name="clipboard_cleared">Međuspremnik ispražnjen</string>
<string name="clipboard_error_title">Greška međuspremnika</string>
<string name="clipboard_error">Neki uređaji neće dopustiti aplikacijama korištenje međuspremnika.</string>
<string name="clipboard_error_clear">Nije moguće isprazniti međuspremnik</string>
<string name="clipboard_timeout_summary">Trajanje pohrane u međuspremniku (ako uređaj to podržava)</string>
<string name="clipboard_timeout_summary">Trajanje spremišta u međuspremniku (ako uređaj to podržava)</string>
<string name="content_description_background">Pozadina</string>
<string name="content_description_open_file">Otvori datoteku</string>
<string name="content_description_add_node">Dodaj čvor</string>
@@ -85,7 +85,7 @@
<string name="entry_url">URL</string>
<string name="entry_user_name">Korisničko ime</string>
<string name="error_nokeyfile">Odaberi datoteku ključa.</string>
<string name="error_pass_gen_type">Barem jedan tip generiranja lozinke mora biti odabran.</string>
<string name="error_pass_gen_type">Barem jedna vrsta generiranja lozinke mora biti odabrana.</string>
<string name="error_pass_match">Lozinke se ne poklapaju.</string>
<string name="error_wrong_length">Upiši pozitivan cijeli broj u polje „Duljina”.</string>
<string name="error_otp_secret_key">Tajni ključ mora biti u Base32 formatu.</string>
@@ -93,7 +93,7 @@
<string name="error_otp_digits">Token mora sadržavati %1$d do %2$d znamenki.</string>
<string name="field_name">Ime polja</string>
<string name="field_value">Vrijednost polja</string>
<string name="file_browser">Upravitelj datoteka</string>
<string name="file_browser">Upravlj datoteka</string>
<string name="generate_password">Generiraj lozinku</string>
<string name="hint_conf_pass">Potvrdi lozinku</string>
<string name="hint_generated_password">Generirana lozinka</string>
@@ -258,7 +258,7 @@
<string name="menu_file_selection_read_only">Zaštićeno od pisanja</string>
<string name="progress_title">Rad u tijeku …</string>
<string name="read_only">Zaštićeno od pisanja</string>
<string name="read_only_warning">Ovisno o upravitelju datoteka, KeePassDX možda neće moći pisati u tvoje spremište.</string>
<string name="read_only_warning">Ovisno o upravlju datoteka, KeePassDX možda neće moći pisati u tvoje spremište.</string>
<string name="contains_duplicate_uuid_procedure">Riješiti problem generiranjem novih UUID-ova za duplikate\?</string>
<string name="root">Korijen</string>
<string name="memory_usage_explanation">Količina memorije koju će koristiti funkcija za generiranje ključeva.</string>
@@ -336,7 +336,6 @@
<string name="homepage">Početna stranica</string>
<string name="remember_keyfile_locations_title">Zapamti mjesto datoteke ključa</string>
<string name="unavailable_feature_version">Uređaj koristi Android verziju %1$s, ali potrebna je verzija %2$s ili novija.</string>
<string name="autofill_auto_search_summary">Automatski predloži rezultate pretrage od web domene ili ID-a aplikacije</string>
<string name="hide_broken_locations_summary">Sakrij pokvarene poveznice u popisu nedavnih baza podataka</string>
<string name="html_text_dev_feature">Ova se funkcija nalazi &lt;strong&gt;u razvoju&lt;/strong&gt; i treba tvoj &lt;strong&gt;doprinos&lt;/strong&gt; kako bi uskoro bila dostupna.</string>
<string name="education_generate_password_title">Stvori sigurnu lozinku</string>
@@ -346,7 +345,6 @@
<string name="autofill_application_id_blocklist_title">Popis blokiranja aplikacija</string>
<string name="validate">Provjeri valjanost</string>
<string name="education_entry_edit_title">Uredi unos</string>
<string name="autofill_auto_search_title">Automatska pretraga</string>
<string name="html_text_dev_feature_work_hard">Trudimo se brzo izdati ovu funkciju.</string>
<string name="html_text_dev_feature_encourage">potičeš programere da stvore &lt;strong&gt;nove funkcije&lt;/strong&gt; i &lt;strong&gt;isprave greške&lt;/strong&gt; na osnovi tvojih napomena.</string>
<string name="keyboard_auto_go_action_summary">Radnja tipke „Idi” nakon pritiskanja tipke „Polje”</string>
@@ -372,7 +370,7 @@
<string name="html_text_dev_feature_upgrade">Redovito aktualiziraj aplikaciju instaliranjem najnovijih verzija.</string>
<string name="autofill_block_restart">Za aktiviranje blokiranja, ponovo pokreni aplikaciju koja sadrži obrazac.</string>
<string name="education_sort_summary">Odaberi način razvrstavanja unosa i grupa.</string>
<string name="warning_database_link_revoked">Pristup datoteci koju je opozvao upravitelj datoteka</string>
<string name="warning_database_link_revoked">Pristup datoteci opozvan od upravljača datoteka</string>
<string name="download_attachment">Preuzmi %1$s</string>
<string name="keyboard_entry_timeout_title">Istek vremena</string>
<string name="auto_focus_search_summary">Pokreni pretragu prilikom otvaranja baze podataka</string>
@@ -387,7 +385,7 @@
<string name="education_entry_new_field_title">Dodaj prilagođena polja</string>
<string name="education_lock_summary">Zaključaj bazu podataka brzo, aplikaciju možeš postaviti tako da bazu nakon nekog vremena zaključa i kad se ekran isključi.</string>
<string name="show_recent_files_summary">Prikaži mjesto nedavnih baza podataka</string>
<string name="education_advanced_unlock_summary">Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom.</string>
<string name="education_advanced_unlock_summary">Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom ili podacima za prijavu uređaja.</string>
<string name="html_text_donation">Kako bismo zadržali našu slobodu i uvijek bili aktivni, računamo na tvoj&lt;strong&gt;doprinos.&lt;/strong&gt;</string>
<string name="kdf_explanation">Za stvaranje ključa za algoritam šifriranja, glavni ključ se transformira pomoću funkcije za generiranje ključeva koja sadrži slučajnu komponentu.</string>
<string name="lock_database_back_root_summary">Zaključaj bazu podataka kad korisnik pritisne gumb za natrag na glavnom ekranu</string>
@@ -471,7 +469,7 @@
<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 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_summary">Zatraži spremanje podataka kad se obrazac ispuni</string>
<string name="autofill_ask_to_save_data_title">Zatraži spremanje podataka</string>
<string name="autofill_save_search_info_title">Spremi podatke pretrage</string>
<string name="autofill_close_database_summary">Zatvori bazu podataka nakon odabira automatskog ispunjavanja</string>
@@ -488,9 +486,9 @@
<string name="search_mode">Modus pretrage</string>
<string name="error_field_name_already_exists">Ime polja već postoji.</string>
<string name="advanced_unlock_delete_all_key_warning">Izbrisati sve ključeve šifriranja povezane s naprednim prepoznavanjem otključavanja\?</string>
<string name="credential_before_click_advanced_unlock_button">Upiši lozinku i zatim pritisni ovaj gumb.</string>
<string name="credential_before_click_advanced_unlock_button">Upiši lozinku, zatim pritisni ovaj gumb.</string>
<string name="advanced_unlock_prompt_extract_credential_title">Otvori bazu podataka pomoću naprednog prepoznavanja otključavanja</string>
<string name="advanced_unlock_prompt_store_credential_message">Upozorenje: Ako koristiš prepoznavanje naprednog otključavanja morat ćeš i dalje znati glavnu lozinku.</string>
<string name="advanced_unlock_prompt_store_credential_message">Ako koristiš prepoznavanje naprednog otključavanja morat ćeš i dalje znati glavnu lozinku.</string>
<string name="menu_keystore_remove_key">Izbriši ključ naprednog otključavanja</string>
<string name="advanced_unlock_prompt_store_credential_title">Napredno prepoznavanje otključavanja</string>
<string name="advanced_unlock_prompt_not_initialized">Nije moguće pokrenuti prozor naprednog otključavanja.</string>
@@ -521,9 +519,9 @@
<string name="autofill_inline_suggestions_title">Umetnuti prijedlozi</string>
<string name="autofill_inline_suggestions_keyboard">Prijedlozi za automatsko popunjavanje su dodani.</string>
<string name="autofill_inline_suggestions_summary">Pokušaj prikazivanja prijedloga za automatsko popunjavanje izravno s kompatibilne tipkovnice</string>
<string name="warning_database_revoked">Pristup datoteci koju je opozvao upravljač datoteka. Zatvori bazu podataka i ponovo je otvori s njezinog mjesta.</string>
<string name="warning_database_revoked">Pristup datoteci opozvan od upravljača datoteka. Zatvori bazu podataka i ponovo je otvori s njezinog mjesta.</string>
<string name="warning_database_info_changed_options">Sjedini podatke, prepiši vanjske promjene spremanjem baze podataka ili je ponovo učitaj s najnovijim promjenama.</string>
<string name="warning_database_info_changed">Podaci u datoteci tvoje baze podataka izmijenjeni su izvan programa.</string>
<string name="warning_database_info_changed">Podaci u datoteci tvoje baze podataka izmijenjeni su izvan aplikacije.</string>
<string name="menu_reload_database">Ponovo učitaj podatke</string>
<string name="error_otp_type">Ovaj obrazac ne prepoznaje postojeću vrstu jednokratne lozinke. Provjera valjanosti možda više neće pravilno generirati token.</string>
<string name="unit_gibibyte">GiB</string>
@@ -594,7 +592,7 @@
<string name="menu_external_icon">Vanjska ikona</string>
<string name="seed">Tajna fraza</string>
<string name="hint_icon_name">Ime ikone</string>
<string name="warning_exact_alarm">Nisi dopustio/la programu da koristi točan alarm. Kao rezultat toga, značajke koje zahtijevaju tajmer neće biti obavljene s točnim vremenom.</string>
<string name="warning_exact_alarm">Nisi dopustio/la aplikaciji da koristi točan alarm. Zbog toga funkije koje zahtijevaju tajmer neće biti obavljene s točnim vremenom.</string>
<string name="permission">Dozvola</string>
<string name="tags">Oznake</string>
<string name="menu_merge_database">Sjedini podatke</string>
@@ -607,4 +605,40 @@
<string name="content_description_entry_foreground_color">Prednja boja unosa</string>
<string name="content_description_database_color">Boja baze podataka</string>
<string name="content_description_entry_background_color">Pozadinska boja unosa</string>
<string name="expired">Isteklo</string>
<string name="search_filters">Filtri pretrage</string>
<string name="current_group">Trenutačna grupa</string>
<string name="case_sensitive">Razlikovanje velikih/malih slova</string>
<string name="searchable">Pretraživo</string>
<string name="inherited">Naslijedi</string>
<string name="custom_data">Prilagođeni podaci</string>
<string name="auto_type_sequence">Slijed automatskog popunjavanja</string>
<string name="regex">Regularni izraz</string>
<string name="menu_merge_from">Sjedini iz …</string>
<string name="menu_save_copy_to">Spremi kopiju u …</string>
<string name="navigation_drawer_open">Ploča navigacije otvorena</string>
<string name="navigation_drawer_close">Ploča navigacije zatvorena</string>
<string name="advanced_unlock_keystore_warning">Ova će funckija spremiti šifrirane podatke za prijavu u sigurni KeyStore tvog uređaja.
\n
\nOvisno o izvornoj API implementaciji operacijskog sustava, funcionalsnost možda neće biti potpuna.
\nProvjeri kompatibilnost i sigurnost KeyStorea kod proizvođača tvog uređaja i kreatora ROM-a koji koristiš.</string>
<string name="warning_database_already_opened">Baza podataka je već otvorena. Za otvaranje nove, najprije je zatvori</string>
<string name="content_description_passphrase_word_count">Broj riječi dugačke lozinke</string>
<string name="colorize_password_title">Oboji lozinke</string>
<string name="colorize_password_summary">Oboje znakove lozinke prema vrsti</string>
<string name="passphrase">Dugačka lozinka</string>
<string name="entropy">Entropija: %1$s bit</string>
<string name="entropy_high">Entropija: visoka</string>
<string name="entropy_calculate">Entropija: Izračunaj …</string>
<string name="at_least_one_char">Barem jedan znak od svakog</string>
<string name="exclude_ambiguous_chars">Isključi višeznačne znakove</string>
<string name="consider_chars_filter">Uzmi u obzir znakove</string>
<string name="keyboard_previous_search_title">Ekran pretrage</string>
<string name="keyboard_previous_search_summary">Automatski se vrati na prethodnu tipkovnicu na ekranu pretrage</string>
<string name="word_separator">Znak razdvajanja</string>
<string name="ignore_chars_filter">Zanemari znakove</string>
<string name="lower_case">mala slova</string>
<string name="title_case">Velika Početna Slova</string>
<string name="character_count">Broj znakova: %1$d</string>
<string name="upper_case">VELIKA SLOVA</string>
</resources>

View File

@@ -314,8 +314,6 @@
<string name="error_create_database_file">Ezzel a jelszó és kulcsfájl kombinációval nem hozható létre adatbázis.</string>
<string name="education_setup_OTP_summary">Állítson be egyszer használatos jelszókezelést (HOTP/TOTP), hogy tokent állítson elő a kétfaktoros hitelesítéshez (2FA).</string>
<string name="education_setup_OTP_title">OTP beállítása</string>
<string name="autofill_auto_search_summary">Keresési találatok automatikus javaslata a webes domain vagy az alkalmazásazonosító alapján</string>
<string name="autofill_auto_search_title">Automatikus keresés</string>
<string name="clear_clipboard_notification_summary">Az adatbázis zárolása, ha a vágólap ideje lejár, vagy ha a használata után bezárja az értesítést</string>
<string name="clear_clipboard_notification_title">Zárolás bezáráskor</string>
<string name="lock_database_show_button_summary">Megjeleníti a zárolás gombot a felhasználói felületen</string>

View File

@@ -329,8 +329,6 @@
<string name="autofill_save_search_info_summary">Cobalah untuk menyimpan informasi pencarian saat membuat pilihan entri manual</string>
<string name="autofill_save_search_info_title">Simpan info pencarian</string>
<string name="autofill_inline_suggestions_title">Saran sebaris</string>
<string name="autofill_auto_search_summary">Secara otomatis menyarankan hasil pencarian dari domain web atau ID aplikasi</string>
<string name="autofill_auto_search_title">Pencarian otomatis</string>
<string name="autofill_close_database_summary">Menutup database setelah pilihan isi-auto</string>
<string name="autofill_close_database_title">Tutup database</string>
<string name="enter">Enter</string>

View File

@@ -417,8 +417,6 @@
<string name="education_setup_OTP_title">Imposta password usa e getta (OTP)</string>
<string name="enable_auto_save_database_summary">Salva il database dopo ogni azione importante (in modalità «Modificabile»)</string>
<string name="enable_auto_save_database_title">Salvataggio automatico del database</string>
<string name="autofill_auto_search_summary">Suggerisci automaticamente risultati dal dominio web o ID dell\'applicazione</string>
<string name="autofill_auto_search_title">Ricerca automatica</string>
<string name="keyboard_auto_go_action_summary">Azione del tasto \"Vai\" dopo aver premuto un tasto \"Campo\"</string>
<string name="keyboard_auto_go_action_title">Tasto di azione automatica</string>
<string name="device_keyboard_setting_title">Impostazioni della tastiera del dispositivo</string>

View File

@@ -388,8 +388,6 @@
<string name="keyboard_previous_lock_summary">データベースのロック後、切り替え前のキーボードへ自動的に戻します</string>
<string name="autofill_close_database_title">データベースを閉じる</string>
<string name="autofill_close_database_summary">自動入力の選択後、データベースを閉じます</string>
<string name="autofill_auto_search_title">自動検索</string>
<string name="autofill_auto_search_summary">ウェブドメインまたはアプリケーション ID から検索結果を自動的に提案します</string>
<string name="autofill_application_id_blocklist_title">アプリケーションのブロックリスト</string>
<string name="autofill_save_search_info_title">検索情報の保存</string>
<string name="autofill_save_search_info_summary">手動でエントリーを選択したとき、検索情報の保存を試みます</string>

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