Compare commits

...

130 Commits

Author SHA1 Message Date
J-Jamet
f45b3fc50a Merge branch 'release/2.9.18' 2021-04-20 20:11:05 +02:00
J-Jamet
01196be30d Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2021-04-20 11:57:48 +02:00
J-Jamet
0a2999bffb Change Pro description 2021-04-20 11:46:55 +02:00
J-Jamet
8f097096e7 Update CHANGELOG 2021-04-20 11:36:52 +02:00
J-Jamet
cd97fc046a Fix theme in Libre version 2021-04-20 11:35:14 +02:00
Hosted Weblate
eeb10f31a6 Merge branch 'origin/develop' into Weblate. 2021-04-20 11:34:10 +02:00
J-Jamet
9df5e116e8 Change to version 2.9.18 to deploy bugs fixes 2021-04-20 11:01:26 +02:00
J-Jamet
1228a03d39 searchInEntry as instance method 2021-04-19 18:34:44 +02:00
J-Jamet
a5e1b3096e Merge branch 'feature/Search_Fields' into develop #962 2021-04-19 18:28:17 +02:00
J-Jamet
b41ae67128 Update CHANGELOG 2021-04-19 18:28:07 +02:00
Sebastian
ddfbe20125 Translated using Weblate (Danish)
Currently translated at 98.1% (517 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2021-04-19 18:27:10 +02:00
J-Jamet
0bfe9291dd Move UUID util 2021-04-19 18:26:40 +02:00
J-Jamet
622b2e1edc Fix search in field reference 2021-04-19 18:24:30 +02:00
J-Jamet
4a40719534 Search refactoring 2021-04-19 17:54:16 +02:00
J-Jamet
384993d363 Remove diacritical marks in search string #945 2021-04-19 15:24:06 +02:00
J-Jamet
01b7d28154 Update CHANGELOG 2021-04-19 13:28:48 +02:00
J-Jamet
d7c4f5577f Merge branch 'feature/Move_Group' into develop #658 2021-04-19 13:27:02 +02:00
J-Jamet
a69d23ca64 Update CHANGELOG 2021-04-19 13:26:41 +02:00
J-Jamet
e2f8b7a6e3 Fix move group message 2021-04-19 13:16:30 +02:00
J-Jamet
171a0b012f Fix remove recycle bin 2021-04-19 12:59:02 +02:00
J-Jamet
5c04b15433 Check group name to prevent manual backup group creation 2021-04-19 11:45:53 +02:00
J-Jamet
6397feffff Better search backup group implementation 2021-04-19 11:11:07 +02:00
J-Jamet
e73b9b7f1c Move group and fix backup in KDB 2021-04-18 23:52:59 +02:00
WaldiS
0d82e40c67 Translated using Weblate (Polish)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-04-17 23:27:06 +02:00
Stephan Paternotte
b75d6d02fa Translated using Weblate (Dutch)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2021-04-17 23:27:06 +02:00
J. Lavoie
76d4542716 Translated using Weblate (French)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2021-04-17 23:27:05 +02:00
J. Lavoie
87955de849 Translated using Weblate (German)
Currently translated at 95.8% (505 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-04-17 23:27:04 +02:00
J-Jamet
6df60cf5da Upgrade biometric lib to 1.1.0 2021-04-17 12:19:14 +02:00
Oliver Cervera
3c23a314f0 Translated using Weblate (Italian)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-04-16 10:27:10 +02:00
Oğuz Ersen
8fda6b04a4 Translated using Weblate (Turkish)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-04-15 01:57:14 +02:00
Eric
9fa98e6b76 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-04-15 01:57:14 +02:00
Ihor Hordiichuk
deb685f39b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-04-15 01:57:13 +02:00
Kunzisoft
d7851d3a18 Translated using Weblate (French)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2021-04-15 01:57:13 +02:00
Retrial
44946fc54a Translated using Weblate (Greek)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2021-04-15 01:57:12 +02:00
Hosted Weblate
a033d10adc Merge branch 'origin/develop' into Weblate. 2021-04-14 10:49:40 +02:00
André Marcelo Alvarenga
5a3e599fe0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 81.2% (428 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2021-04-14 10:49:39 +02:00
Oliver Cervera
a9f645f389 Translated using Weblate (Italian)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-04-14 10:49:39 +02:00
J-Jamet
d662f0903a Workaround to autofill recognition #960 2021-04-13 12:48:33 +02:00
J-Jamet
beaa947eb7 Upgrade version 2021-04-13 11:41:03 +02:00
random r
48006b64d6 Translated using Weblate (Italian)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-04-13 11:34:57 +02:00
J-Jamet
8f195ba66f Merge tag '2.9.17' into develop
2.9.17
2021-04-13 09:11:12 +02:00
J-Jamet
123288e745 Merge branch 'release/2.9.17' 2021-04-13 09:11:05 +02:00
Milo Ivir
5866e95d49 Translated using Weblate (Croatian)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2021-04-10 22:27:10 +02:00
WaldiS
e79f395424 Translated using Weblate (Polish)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-04-10 22:27:09 +02:00
HARADA Hiroyuki
999ca87fec Translated using Weblate (Japanese)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-04-10 22:27:09 +02:00
Oliver Cervera
1217266d88 Translated using Weblate (Italian)
Currently translated at 98.1% (517 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-04-10 22:27:08 +02:00
Sebastian
bb262198be Translated using Weblate (Danish)
Currently translated at 93.1% (491 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2021-04-10 22:27:08 +02:00
J-Jamet
11aae77caf Update CHANGELOG 2021-04-10 20:56:25 +02:00
J-Jamet
8212cede6e Merge branch 'feature/Duration_Preference' into develop #579 2021-04-10 20:53:35 +02:00
J-Jamet
a3c51884f4 Fix timeout strings 2021-04-10 20:53:24 +02:00
J-Jamet
b8890aca7f Better notification timer implementation 2021-04-10 20:30:48 +02:00
J-Jamet
014b0cce14 Add duration preference with number picker 2021-04-10 19:29:19 +02:00
Oğuz Ersen
6d860c5cb7 Translated using Weblate (Turkish)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-04-08 20:55:17 +02:00
Eric
d8be832858 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-04-08 20:55:17 +02:00
Ihor Hordiichuk
afcb9fcf41 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-04-08 20:55:16 +02:00
solokot
3c7ae0aaf0 Translated using Weblate (Russian)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-04-08 20:55:15 +02:00
Retrial
6b7f93dbfe Translated using Weblate (Greek)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2021-04-08 20:55:15 +02:00
J-Jamet
c40b255022 Check properties 2021-04-08 16:40:01 +02:00
J-Jamet
1742d265f3 Better stylish implementation 2021-04-08 16:26:14 +02:00
Nikita Epifanov
3240e0bcae Translated using Weblate (Russian)
Currently translated at 100.0% (527 of 527 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-04-08 13:59:42 +02:00
J-Jamet
ff185f6505 Upgrade version and CHANGELOG 2021-04-08 12:58:39 +02:00
J-Jamet
346b517c9d Force twofish padding compatibility #955 2021-04-08 12:55:23 +02:00
Hosted Weblate
80f00aba0a Merge branch 'origin/develop' into Weblate. 2021-04-08 12:07:37 +02:00
J-Jamet
949905f6e2 Merge branch 'feature/Import_Export_App_Properties' into develop 2021-04-08 12:06:52 +02:00
J-Jamet
b9e26fecfd Export and import properties 2021-04-08 11:45:23 +02:00
solokot
232682f4a8 Translated using Weblate (Russian)
Currently translated at 100.0% (516 of 516 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-04-08 08:27:03 +02:00
C. Rüdinger
de3b690d60 Translated using Weblate (German)
Currently translated at 97.0% (501 of 516 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-04-08 08:27:03 +02:00
J-Jamet
de69a78a98 First commit to import and export app properties 2021-04-07 16:13:40 +02:00
J-Jamet
1c341c34a3 Merge tag '2.9.16' into develop
2.9.16
2021-04-07 09:53:13 +02:00
J-Jamet
33beb57e9d Merge branch 'release/2.9.16' 2021-04-07 09:53:07 +02:00
J-Jamet
66eeadca0b Upgrade version code 2021-04-06 17:39:27 +02:00
J-Jamet
a10d1c98a8 Fix KDB parcelable 2021-04-06 17:32:40 +02:00
J-Jamet
59ead4986f Move Parent Parcelable 2021-04-06 15:05:11 +02:00
J-Jamet
09f6c18189 Small changes 2021-04-06 15:02:24 +02:00
Timur Seber
a5cd6d5ac0 Translated using Weblate (Tatar)
Currently translated at 8.5% (44 of 516 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tt/
2021-04-06 06:26:46 +02:00
J-Jamet
0f3ad7c8b1 Fix select custom icon 2021-04-05 19:06:54 +02:00
J-Jamet
0487dea7fc Fix null cache directory 2021-04-05 11:32:07 +02:00
Timur Seber
a6803bf0e3 Added translation using Weblate (Tatar) 2021-04-05 05:18:42 +02:00
J-Jamet
8cac1ee284 Merge branch 'feature/ExternalFileHelper' into develop 2021-04-05 00:06:03 +02:00
J-Jamet
196620e1bd Remove unused methods 2021-04-05 00:04:25 +02:00
J-Jamet
43d6c76873 Refactor open document click listener 2021-04-04 23:44:25 +02:00
J-Jamet
b864c39a0d Fix add database workflow in some devices 2021-04-04 23:13:24 +02:00
J-Jamet
818b975111 Change default type verification to create document 2021-04-04 09:56:09 +02:00
J-Jamet
d5fbc8393f Change file creation methods 2021-04-03 19:40:55 +02:00
Oliver Cervera
df9a71a63d Translated using Weblate (Italian)
Currently translated at 100.0% (516 of 516 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-04-02 12:26:46 +02:00
J-Jamet
7b5e9d2344 Better parcelable entry CREATOR implementation #948 2021-04-02 09:37:18 +02:00
J-Jamet
7fc2d95886 Fix document file retrievment 2021-04-01 10:52:00 +02:00
J-Jamet
78d3b369bb Move Parcelable inheritance 2021-03-31 19:39:07 +02:00
J-Jamet
bb3620680b Upgrade version 2021-03-31 17:58:49 +02:00
J-Jamet
d4a45655ca Merge tag '2.9.15' into develop
2.9.15
2021-03-29 22:01:52 +02:00
J-Jamet
c9c739fd52 Merge branch 'release/2.9.15' 2021-03-29 22:01:44 +02:00
J-Jamet
2b359cc592 Remove unused code 2021-03-29 21:00:10 +02:00
J-Jamet
151b7a323d Upgrade version code 2021-03-29 13:58:30 +02:00
J-Jamet
1063dc2b63 Add TODO SparseArray 2021-03-29 13:57:30 +02:00
J-Jamet
f9f59a6eb1 Replace serializable UUID by Parcelable UUID 2021-03-29 13:49:49 +02:00
J-Jamet
73156cc337 Fix clipboard null exception 2021-03-29 13:09:22 +02:00
J-Jamet
7d53607f49 Capture exception when launching cipher action 2021-03-29 13:07:55 +02:00
J-Jamet
7539945465 Capture exception when error when launching database action 2021-03-29 13:01:31 +02:00
J-Jamet
51df8e7bb1 Try to fix rare bug 2021-03-29 12:52:12 +02:00
J-Jamet
17029ce67c Fix bad padding exception 2021-03-29 12:42:53 +02:00
J-Jamet
8cedc313cf Upgrade version code 2021-03-27 10:54:26 +01:00
J-Jamet
5afe3acac1 Remove unused string 2021-03-27 10:23:38 +01:00
J-Jamet
9887b58b71 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2021-03-27 10:18:55 +01:00
J-Jamet
ec8363ba6a Merge branch 'develop' into release/2.9.15 2021-03-27 10:13:53 +01:00
J-Jamet
fcfb71f13b Fix disable Memory Usage setting with AES #941 2021-03-27 10:12:29 +01:00
J-Jamet
3a12e431ff Update CHANGELOG 2021-03-27 05:49:29 +01:00
J-Jamet
bc4ed8e123 Update CHANGELOG 2021-03-27 05:44:09 +01:00
J-Jamet
445e9540a5 Merge branch 'feature/Dynamic_Memory_And_Encrypt_Module' into develop 2021-03-26 20:06:12 +01:00
Joan Jaume Oliver
bbc2a2a9dd Translated using Weblate (Spanish)
Currently translated at 98.6% (515 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-03-26 19:29:42 +01:00
Joan Jaume Oliver
5117bc78b6 Translated using Weblate (Catalan)
Currently translated at 48.2% (252 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2021-03-26 19:29:41 +01:00
Reza Almanda
26d8b2fa22 Translated using Weblate (Indonesian)
Currently translated at 74.3% (388 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2021-03-25 01:29:42 +01:00
random r
2b7fe35305 Translated using Weblate (Italian)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-03-23 21:30:04 +01:00
Vít Šindlář
d5819ea4d0 Translated using Weblate (Czech)
Currently translated at 99.6% (520 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2021-03-23 21:30:04 +01:00
Hosted Weblate
0cf136712a Merge branch 'origin/develop' into Weblate. 2021-03-20 14:07:16 +01:00
Milo Ivir
34a453873a Translated using Weblate (Croatian)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2021-03-20 14:07:16 +01:00
Kunzisoft
0acac3b096 Translated using Weblate (French)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2021-03-20 14:07:04 +01:00
Oymate
ede6070e43 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 4.2% (22 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn_BD/
2021-03-17 08:18:07 +01:00
jan madsen
a61b1d4337 Translated using Weblate (Danish)
Currently translated at 93.8% (490 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2021-03-17 08:18:06 +01:00
WaldiS
0f8c71a9df Translated using Weblate (Polish)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-03-14 13:02:53 +01:00
gnu-ewm
cb0b6e010d Translated using Weblate (Polish)
Currently translated at 99.8% (521 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-03-12 21:02:54 +01:00
Hisikawa Mizuki
23dc7be1ab Translated using Weblate (Japanese)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-03-12 21:02:54 +01:00
Oliver Cervera
4b14ad07d2 Translated using Weblate (Italian)
Currently translated at 99.8% (521 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-03-12 21:02:54 +01:00
Oğuz Ersen
8a4bf7896f Translated using Weblate (Turkish)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2021-03-11 17:14:10 +01:00
Allan Nordhøy
208ea29643 Translated using Weblate (Norwegian Bokmål)
Currently translated at 76.6% (400 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2021-03-11 17:14:09 +01:00
Eric
7c52ec731a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-03-11 17:14:09 +01:00
Ihor Hordiichuk
c6ee38e435 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2021-03-11 17:14:08 +01:00
solokot
65253cc5b9 Translated using Weblate (Russian)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2021-03-11 17:14:08 +01:00
Stephan Paternotte
d1a1a23cbc Translated using Weblate (Dutch)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2021-03-11 17:14:08 +01:00
HARADA Hiroyuki
e8bb3a5ba7 Translated using Weblate (Japanese)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-03-11 17:14:07 +01:00
Retrial
22b8f82770 Translated using Weblate (Greek)
Currently translated at 100.0% (522 of 522 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2021-03-11 17:14:07 +01:00
132 changed files with 2296 additions and 1653 deletions

View File

@@ -1,6 +1,23 @@
KeePassDX(2.9.18)
* Move groups #658
* Improve autofill recognition #960
* Remove diacritical marks in search string #945
* Fix search in references #962
* Fix themes in Libre version
KeePassDX(2.9.17)
* Import / Export app properties #839
* Force twofish padding compatibility #955
* Better timeout preference #579
KeePassDX(2.9.16)
* Fix small bugs #948
KeePassDX(2.9.15) KeePassDX(2.9.15)
* Fix themes #935 * Fix themes #935 #926
* Decrease default clipboard time #934 * Decrease default clipboard time #934
* Better opening performance #929 #933
* Fix memory usage setting #941
KeePassDX(2.9.14) KeePassDX(2.9.14)
* Add custom icons #96 * Add custom icons #96

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 30 targetSdkVersion 30
versionCode = 66 versionCode = 72
versionName = "2.9.15" versionName = "2.9.18"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"
@@ -109,7 +109,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01' implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.1.0-rc01' implementation 'androidx.biometric:biometric:1.1.0'
// Lifecycle - LiveData - ViewModel - Coroutines // Lifecycle - LiveData - ViewModel - Coroutines
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.3.2"
implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.fragment:fragment-ktx:1.2.5'

View File

@@ -39,6 +39,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
@@ -93,6 +94,8 @@ class EntryActivity : LockingActivity() {
private var clipboardHelper: ClipboardHelper? = null private var clipboardHelper: ClipboardHelper? = null
private var mFirstLaunchOfActivity: Boolean = false private var mFirstLaunchOfActivity: Boolean = false
private var mExternalFileHelper: ExternalFileHelper? = null
private var iconColor: Int = 0 private var iconColor: Int = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -140,6 +143,9 @@ class EntryActivity : LockingActivity() {
clipboardHelper = ClipboardHelper(this) clipboardHelper = ClipboardHelper(this)
mFirstLaunchOfActivity = savedInstanceState?.getBoolean(KEY_FIRST_LAUNCH_ACTIVITY) ?: true mFirstLaunchOfActivity = savedInstanceState?.getBoolean(KEY_FIRST_LAUNCH_ACTIVITY) ?: true
// Init SAF manager
mExternalFileHelper = ExternalFileHelper(this)
// Init attachment service binder manager // Init attachment service binder manager
mAttachmentFileBinderManager = AttachmentFileBinderManager(this) mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
@@ -344,7 +350,7 @@ class EntryActivity : LockingActivity() {
// Manage attachments // Manage attachments
entryContentsView?.assignAttachments(entryInfo.attachments.toSet(), StreamDirection.DOWNLOAD) { attachmentItem -> entryContentsView?.assignAttachments(entryInfo.attachments.toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
createDocument(this, attachmentItem.name)?.let { requestCode -> mExternalFileHelper?.createDocument(attachmentItem.name)?.let { requestCode ->
mAttachmentsToDownload[requestCode] = attachmentItem mAttachmentsToDownload[requestCode] = attachmentItem
} }
} }
@@ -380,7 +386,7 @@ class EntryActivity : LockingActivity() {
} }
} }
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri -> mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) { if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload -> mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager mAttachmentFileBinderManager

View File

@@ -45,7 +45,7 @@ import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
import com.kunzisoft.keepass.activities.fragments.EntryEditFragment import com.kunzisoft.keepass.activities.fragments.EntryEditFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.autofill.AutofillComponent import com.kunzisoft.keepass.autofill.AutofillComponent
@@ -103,7 +103,7 @@ class EntryEditActivity : LockingActivity(),
private var lockView: View? = null private var lockView: View? = null
// To manage attachments // To manage attachments
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAllowMultipleAttachments: Boolean = false private var mAllowMultipleAttachments: Boolean = false
private var mTempAttachments = ArrayList<EntryAttachmentState>() private var mTempAttachments = ArrayList<EntryAttachmentState>()
@@ -241,7 +241,7 @@ class EntryEditActivity : LockingActivity(),
} }
// To retrieve attachment // To retrieve attachment
mSelectFileHelper = SelectFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
mAttachmentFileBinderManager = AttachmentFileBinderManager(this) mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
// Save button // Save button
@@ -458,8 +458,8 @@ class EntryEditActivity : LockingActivity(),
/** /**
* Add a new attachment * Add a new attachment
*/ */
private fun addNewAttachment(item: MenuItem) { private fun addNewAttachment() {
mSelectFileHelper?.selectFileOnClickViewListener?.onMenuItemClick(item) mExternalFileHelper?.openDocument()
} }
override fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?) { override fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?) {
@@ -505,7 +505,7 @@ class EntryEditActivity : LockingActivity(),
entryEditFragment?.icon = icon entryEditFragment?.icon = icon
} }
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
uri?.let { attachmentToUploadUri -> uri?.let { attachmentToUploadUri ->
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile -> UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile ->
documentFile.name?.let { fileName -> documentFile.name?.let { fileName ->
@@ -655,7 +655,7 @@ class EntryEditActivity : LockingActivity(),
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation( && entryEditActivityEducation.checkAndPerformedAttachmentEducation(
attachmentView, attachmentView,
{ {
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(attachmentView) mExternalFileHelper?.openDocument()
}, },
{ {
performedNextEducation(entryEditActivityEducation) performedNextEducation(entryEditActivityEducation)
@@ -683,7 +683,7 @@ class EntryEditActivity : LockingActivity(),
return true return true
} }
R.id.menu_add_attachment -> { R.id.menu_add_attachment -> {
addNewAttachment(item) addNewAttachment()
return true return true
} }
R.id.menu_add_otp -> { R.id.menu_add_otp -> {

View File

@@ -42,8 +42,9 @@ import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
@@ -82,7 +83,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private var mDatabaseFileUri: Uri? = null private var mDatabaseFileUri: Uri? = null
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
@@ -103,14 +104,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
createDatabaseButtonView?.setOnClickListener { createNewFile() } createDatabaseButtonView?.setOnClickListener { createNewFile() }
// Open database button // Open database button
mSelectFileHelper = SelectFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
openDatabaseButtonView = findViewById(R.id.open_keyfile_button) openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView?.apply { openDatabaseButtonView?.setOpenDocumentClickListener(mExternalFileHelper)
mSelectFileHelper?.selectFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
// History list // History list
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list) val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
@@ -162,29 +158,31 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
// Observe list of databases // Observe list of databases
databaseFilesViewModel.databaseFilesLoaded.observe(this) { databaseFiles -> databaseFilesViewModel.databaseFilesLoaded.observe(this) { databaseFiles ->
when (databaseFiles.databaseFileAction) { try {
DatabaseFilesViewModel.DatabaseFileAction.NONE -> { when (databaseFiles.databaseFileAction) {
mAdapterDatabaseHistory?.replaceAllDatabaseFileHistoryList(databaseFiles.databaseFileList) DatabaseFilesViewModel.DatabaseFileAction.NONE -> {
} mAdapterDatabaseHistory?.replaceAllDatabaseFileHistoryList(databaseFiles.databaseFileList)
DatabaseFilesViewModel.DatabaseFileAction.ADD -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToAdd ->
mAdapterDatabaseHistory?.addDatabaseFileHistory(databaseFileToAdd)
} }
GroupActivity.launch(this@FileDatabaseSelectActivity, DatabaseFilesViewModel.DatabaseFileAction.ADD -> {
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity)) databaseFiles.databaseFileToActivate?.let { databaseFileToAdd ->
} mAdapterDatabaseHistory?.addDatabaseFileHistory(databaseFileToAdd)
DatabaseFilesViewModel.DatabaseFileAction.UPDATE -> { }
databaseFiles.databaseFileToActivate?.let { databaseFileToUpdate -> }
mAdapterDatabaseHistory?.updateDatabaseFileHistory(databaseFileToUpdate) DatabaseFilesViewModel.DatabaseFileAction.UPDATE -> {
} databaseFiles.databaseFileToActivate?.let { databaseFileToUpdate ->
} mAdapterDatabaseHistory?.updateDatabaseFileHistory(databaseFileToUpdate)
DatabaseFilesViewModel.DatabaseFileAction.DELETE -> { }
databaseFiles.databaseFileToActivate?.let { databaseFileToDelete -> }
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileToDelete) DatabaseFilesViewModel.DatabaseFileAction.DELETE -> {
databaseFiles.databaseFileToActivate?.let { databaseFileToDelete ->
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileToDelete)
}
} }
} }
databaseFilesViewModel.consumeAction()
} catch (e: Exception) {
Log.e(TAG, "Unable to observe database action", e)
} }
databaseFilesViewModel.consumeAction()
} }
// Observe default database // Observe default database
@@ -202,6 +200,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential() val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri) databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
} }
GroupActivity.launch(this@FileDatabaseSelectActivity,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity))
} }
ACTION_DATABASE_LOAD_TASK -> { ACTION_DATABASE_LOAD_TASK -> {
val database = Database.getInstance() val database = Database.getInstance()
@@ -230,7 +230,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
* Create a new file by calling the content provider * Create a new file by calling the content provider
*/ */
private fun createNewFile() { private fun createNewFile() {
createDocument(this, getString(R.string.database_file_name_default) + mExternalFileHelper?.createDocument( getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default), "application/x-keepass") getString(R.string.database_file_extension_default), "application/x-keepass")
} }
@@ -282,7 +282,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
// Show open and create button or special mode // Show open and create button or special mode
when (mSpecialMode) { when (mSpecialMode) {
SpecialMode.DEFAULT -> { SpecialMode.DEFAULT -> {
if (allowCreateDocumentByStorageAccessFramework(packageManager)) { if (ExternalFileHelper.allowCreateDocumentByStorageAccessFramework(packageManager)) {
// There is an activity which can handle this intent. // There is an activity which can handle this intent.
createDatabaseButtonView?.visibility = View.VISIBLE createDatabaseButtonView?.visibility = View.VISIBLE
} else{ } else{
@@ -355,14 +355,14 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
} }
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
if (uri != null) { if (uri != null) {
launchPasswordActivityWithPath(uri) launchPasswordActivityWithPath(uri)
} }
} }
// Retrieve the created URI from the file manager // Retrieve the created URI from the file manager
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri -> mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) { if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true) AssignMasterKeyDialogFragment.getInstance(true)
@@ -408,9 +408,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
openDatabaseButtonView != null openDatabaseButtonView != null
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation( && fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
openDatabaseButtonView!!, openDatabaseButtonView!!,
{tapTargetView -> { tapTargetView ->
tapTargetView?.let { tapTargetView?.let {
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it) mExternalFileHelper?.openDocument()
} }
}, },
{} {}

View File

@@ -812,74 +812,75 @@ class GroupActivity : LockingActivity(),
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?, override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
nodes: List<Node>): Boolean { nodes: List<Node>): Boolean {
// Move or copy only if allowed (in root if allowed) when (pasteMode) {
if (mCurrentGroup != mDatabase?.rootGroup ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
|| mDatabase?.rootCanContainsEntry() == true) { // Copy
mCurrentGroup?.let { newParent ->
when (pasteMode) { mProgressDatabaseTaskProvider?.startDatabaseCopyNodes(
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> { nodes,
// Copy newParent,
mCurrentGroup?.let { newParent -> !mReadOnly && mAutoSaveEnable
mProgressDatabaseTaskProvider?.startDatabaseCopyNodes( )
nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
}
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move
mCurrentGroup?.let { newParent ->
mProgressDatabaseTaskProvider?.startDatabaseMoveNodes(
nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
}
else -> {
} }
} }
} else { ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
coordinatorLayout?.let { coordinatorLayout -> // Move
Snackbar.make(coordinatorLayout, mCurrentGroup?.let { newParent ->
R.string.error_copy_entry_here, mProgressDatabaseTaskProvider?.startDatabaseMoveNodes(
Snackbar.LENGTH_LONG).asError().show() nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
} }
else -> {}
} }
finishNodeAction() finishNodeAction()
return true return true
} }
private fun eachNodeRecyclable(nodes: List<Node>): Boolean {
mDatabase?.let { database ->
return nodes.find { node ->
var cannotRecycle = true
if (node is Entry) {
cannotRecycle = !database.canRecycle(node)
} else if (node is Group) {
cannotRecycle = !database.canRecycle(node)
}
cannotRecycle
} == null
}
return false
}
private fun deleteNodes(nodes: List<Node>, recycleBin: Boolean = false): Boolean { private fun deleteNodes(nodes: List<Node>, recycleBin: Boolean = false): Boolean {
val database = mDatabase mDatabase?.let { database ->
// If recycle bin enabled, ensure it exists // If recycle bin enabled, ensure it exists
if (database != null && database.isRecycleBinEnabled) { if (database.isRecycleBinEnabled) {
database.ensureRecycleBinExists(resources) database.ensureRecycleBinExists(resources)
}
// If recycle bin enabled and not in recycle bin, move in recycle bin
if (database != null
&& database.isRecycleBinEnabled
&& database.recycleBin != mCurrentGroup) {
mProgressDatabaseTaskProvider?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
)
}
// else open the dialog to confirm deletion
else {
val deleteNodesDialogFragment: DeleteNodesDialogFragment =
if (recycleBin) {
EmptyRecycleBinDialogFragment.getInstance(nodes)
} else {
DeleteNodesDialogFragment.getInstance(nodes)
} }
deleteNodesDialogFragment.show(supportFragmentManager, "deleteNodesDialogFragment")
// If recycle bin enabled and not in recycle bin, move in recycle bin
if (eachNodeRecyclable(nodes)) {
mProgressDatabaseTaskProvider?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
)
}
// else open the dialog to confirm deletion
else {
val deleteNodesDialogFragment: DeleteNodesDialogFragment =
if (recycleBin) {
EmptyRecycleBinDialogFragment.getInstance(nodes)
} else {
DeleteNodesDialogFragment.getInstance(nodes)
}
deleteNodesDialogFragment.show(supportFragmentManager, "deleteNodesDialogFragment")
}
finishNodeAction()
} }
finishNodeAction()
return true return true
} }
@@ -1076,6 +1077,16 @@ class GroupActivity : LockingActivity(),
} }
} }
override fun isValidGroupName(name: String): GroupEditDialogFragment.Error {
if (name.isEmpty()) {
return GroupEditDialogFragment.Error(true, R.string.error_no_name)
}
if (mDatabase?.groupNamesNotAllowed?.find { it.equals(name, ignoreCase = true) } != null) {
return GroupEditDialogFragment.Error(true, R.string.error_word_reserved)
}
return GroupEditDialogFragment.Error(false, null)
}
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction, override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction,
groupInfo: GroupInfo) { groupInfo: GroupInfo) {

View File

@@ -34,7 +34,8 @@ import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.IconPickerFragment import com.kunzisoft.keepass.activities.fragments.IconPickerFragment
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
@@ -66,7 +67,7 @@ class IconPickerActivity : LockingActivity() {
private var mDatabase: Database? = null private var mDatabase: Database? = null
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -84,15 +85,11 @@ class IconPickerActivity : LockingActivity() {
coordinatorLayout = findViewById(R.id.icon_picker_coordinator) coordinatorLayout = findViewById(R.id.icon_picker_coordinator)
mExternalFileHelper = ExternalFileHelper(this)
uploadButton = findViewById(R.id.icon_picker_upload) uploadButton = findViewById(R.id.icon_picker_upload)
if (mDatabase?.allowCustomIcons == true) { if (mDatabase?.allowCustomIcons == true) {
uploadButton.setOnClickListener { uploadButton.setOpenDocumentClickListener(mExternalFileHelper)
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it)
}
uploadButton.setOnLongClickListener {
mSelectFileHelper?.selectFileOnClickViewListener?.onLongClick(it)
true
}
} else { } else {
uploadButton.visibility = View.GONE uploadButton.visibility = View.GONE
} }
@@ -124,8 +121,6 @@ class IconPickerActivity : LockingActivity() {
// Focus view to reinitialize timeout // Focus view to reinitialize timeout
findViewById<ViewGroup>(R.id.icon_picker_container)?.resetAppTimeoutWhenViewFocusedOrChanged(this) findViewById<ViewGroup>(R.id.icon_picker_container)?.resetAppTimeoutWhenViewFocusedOrChanged(this)
mSelectFileHelper = SelectFileHelper(this)
iconPickerViewModel.standardIconPicked.observe(this) { iconStandard -> iconPickerViewModel.standardIconPicked.observe(this) { iconStandard ->
mIconImage.standard = iconStandard mIconImage.standard = iconStandard
// Remove the custom icon if a standard one is selected // Remove the custom icon if a standard one is selected
@@ -281,7 +276,7 @@ class IconPickerActivity : LockingActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
addCustomIcon(uri) addCustomIcon(uri)
} }
} }

View File

@@ -42,10 +42,7 @@ import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.*
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
@@ -95,7 +92,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private var mDatabaseKeyFileUri: Uri? = null private var mDatabaseKeyFileUri: Uri? = null
private var mRememberKeyFile: Boolean = false private var mRememberKeyFile: Boolean = false
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mPermissionAsked = false private var mPermissionAsked = false
private var readOnly: Boolean = false private var readOnly: Boolean = false
@@ -138,13 +135,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState) readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this) mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mSelectFileHelper = SelectFileHelper(this@PasswordActivity) mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
keyFileSelectionView?.apply { keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
mSelectFileHelper?.selectFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
passwordView?.setOnEditorActionListener(onEditorActionListener) passwordView?.setOnEditorActionListener(onEditorActionListener)
passwordView?.addTextChangedListener(object : TextWatcher { passwordView?.addTextChangedListener(object : TextWatcher {
@@ -702,8 +694,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
} }
var keyFileResult = false var keyFileResult = false
mSelectFileHelper?.let { mExternalFileHelper?.let {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
mDatabaseKeyFileUri = uri mDatabaseKeyFileUri = uri

View File

@@ -30,13 +30,13 @@ import android.text.SpannableStringBuilder
import android.text.TextWatcher import android.text.TextWatcher
import android.view.View import android.view.View
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
@@ -60,7 +60,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var mListener: AssignPasswordDialogListener? = null private var mListener: AssignPasswordDialogListener? = null
private var mSelectFileHelper: SelectFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mEmptyPasswordConfirmationDialog: AlertDialog? = null private var mEmptyPasswordConfirmationDialog: AlertDialog? = null
private var mNoKeyConfirmationDialog: AlertDialog? = null private var mNoKeyConfirmationDialog: AlertDialog? = null
@@ -133,11 +133,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox) keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection) keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
mSelectFileHelper = SelectFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
keyFileSelectionView?.apply { keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
setOnClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
setOnLongClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
}
val dialog = builder.create() val dialog = builder.create()
@@ -289,7 +286,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
uri?.let { pathUri -> uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile -> UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
keyFileSelectionView?.error = null keyFileSelectionView?.error = null

View File

@@ -219,14 +219,19 @@ class GroupEditDialogFragment : DialogFragment() {
} }
private fun isValid(): Boolean { private fun isValid(): Boolean {
if (nameTextView.text.toString().isEmpty()) { val error = mEditGroupListener?.isValidGroupName(nameTextView.text.toString()) ?: Error(false, null)
nameTextLayoutView.error = getString(R.string.error_no_name) error.messageId?.let { messageId ->
return false nameTextLayoutView.error = getString(messageId)
} ?: kotlin.run {
nameTextLayoutView.error = null
} }
return true return !error.isError
} }
data class Error(val isError: Boolean, val messageId: Int?)
interface EditGroupListener { interface EditGroupListener {
fun isValidGroupName(name: String): Error
fun approveEditGroup(action: EditGroupDialogAction, fun approveEditGroup(action: EditGroupDialogAction,
groupInfo: GroupInfo) groupInfo: GroupInfo)
fun cancelEditGroup(action: EditGroupDialogAction, fun cancelEditGroup(action: EditGroupDialogAction,

View File

@@ -311,13 +311,17 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
menu?.removeItem(R.id.menu_edit) menu?.removeItem(R.id.menu_edit)
} }
// Copy and Move (not for groups) // Move
if (readOnly
|| isASearchResult) {
menu?.removeItem(R.id.menu_move)
}
// Copy (not allowed for group)
if (readOnly if (readOnly
|| isASearchResult || isASearchResult
|| nodes.any { it.type == Type.GROUP }) { || nodes.any { it.type == Type.GROUP }) {
// TODO Copy For Group
menu?.removeItem(R.id.menu_copy) menu?.removeItem(R.id.menu_copy)
menu?.removeItem(R.id.menu_move)
} }
// Deletion // Deletion

View File

@@ -0,0 +1,254 @@
/*
* 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.helpers
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.View
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil
class ExternalFileHelper {
private var activity: FragmentActivity? = null
private var fragment: Fragment? = null
constructor(context: FragmentActivity) {
this.activity = context
this.fragment = null
}
constructor(context: Fragment) {
this.activity = context.activity
this.fragment = context
}
fun openDocument(getContent: Boolean = false,
typeString: String = "*/*") {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
if (getContent) {
openActivityWithActionGetContent(typeString)
} else {
openActivityWithActionOpenDocument(typeString)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to open document", e)
showFileManagerDialogFragment()
}
} else {
showFileManagerDialogFragment()
}
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun openActivityWithActionOpenDocument(typeString: String) {
val intentOpenDocument = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
else
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun openActivityWithActionGetContent(typeString: String) {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
}
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
else
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param onFileSelected Callback retrieve from data
* @return true if requestCode was captured, false elsewhere
*/
fun onOpenDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
onFileSelected: ((uri: Uri?) -> Unit)?): Boolean {
when (requestCode) {
FILE_BROWSE -> {
if (resultCode == RESULT_OK) {
val filename = data?.dataString
var keyUri: Uri? = null
if (filename != null) {
keyUri = UriUtil.parse(filename)
}
onFileSelected?.invoke(keyUri)
}
return true
}
GET_CONTENT, OPEN_DOC -> {
if (resultCode == RESULT_OK) {
if (data != null) {
val uri = data.data
if (uri != null) {
try {
// try to persist read and write permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity?.contentResolver?.apply {
takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
}
} catch (e: Exception) {
// nop
}
onFileSelected?.invoke(uri)
}
}
}
return true
}
}
return false
}
/**
* Show Browser dialog to select file picker app
*/
private fun showFileManagerDialogFragment() {
try {
if (fragment != null) {
fragment?.parentFragmentManager
} else {
activity?.supportFragmentManager
}?.let { fragmentManager ->
FileManagerDialogFragment().show(fragmentManager, "browserDialog")
}
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
}
}
fun createDocument(titleString: String,
typeString: String = "application/octet-stream"): Int? {
val idCode = getUnusedCreateFileRequestCode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
putExtra(Intent.EXTRA_TITLE, titleString)
}
if (fragment != null)
fragment?.startActivityForResult(intent, idCode)
else
activity?.startActivityForResult(intent, idCode)
return idCode
} catch (e: Exception) {
Log.e(TAG, "Unable to create document", e)
showFileManagerDialogFragment()
}
} else {
showFileManagerDialogFragment()
}
return null
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param onFileCreated Callback retrieve from data
* @return true if requestCode was captured, false elsewhere
*/
fun onCreateDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
onFileCreated: (fileCreated: Uri?)->Unit) {
// Retrieve the created URI from the file manager
if (fileRequestCodes.contains(requestCode) && resultCode == RESULT_OK) {
onFileCreated.invoke(data?.data)
fileRequestCodes.remove(requestCode)
}
}
companion object {
private const val TAG = "OpenFileHelper"
private const val GET_CONTENT = 25745
private const val OPEN_DOC = 25845
private const val FILE_BROWSE = 25645
private var CREATE_FILE_REQUEST_CODE_DEFAULT = 3853
private var fileRequestCodes = ArrayList<Int>()
private fun getUnusedCreateFileRequestCode(): Int {
val newCreateFileRequestCode = CREATE_FILE_REQUEST_CODE_DEFAULT++
fileRequestCodes.add(newCreateFileRequestCode)
return newCreateFileRequestCode
}
@SuppressLint("InlinedApi")
fun allowCreateDocumentByStorageAccessFramework(packageManager: PackageManager,
typeString: String = "application/octet-stream"): Boolean {
return when {
// To check if a custom file manager can manage the ACTION_CREATE_DOCUMENT
Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT -> {
packageManager.queryIntentActivities(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
}, PackageManager.MATCH_DEFAULT_ONLY).isNotEmpty()
}
else -> true
}
}
}
}
fun View.setOpenDocumentClickListener(externalFileHelper: ExternalFileHelper?) {
externalFileHelper?.let { fileHelper ->
setOnClickListener {
fileHelper.openDocument()
}
setOnLongClickListener {
fileHelper.openDocument(true)
true
}
} ?: kotlin.run {
setOnClickListener(null)
setOnLongClickListener(null)
}
}

View File

@@ -1,244 +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.helpers
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import android.view.MenuItem
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil
class SelectFileHelper {
private var activity: Activity? = null
private var fragment: Fragment? = null
val selectFileOnClickViewListener: SelectFileOnClickViewListener
get() = SelectFileOnClickViewListener()
constructor(context: Activity) {
this.activity = context
this.fragment = null
}
constructor(context: Fragment) {
this.activity = context.activity
this.fragment = context
}
inner class SelectFileOnClickViewListener :
View.OnClickListener,
View.OnLongClickListener,
MenuItem.OnMenuItemClickListener {
private fun onAbstractClick(longClick: Boolean = false) {
try {
if (longClick) {
try {
openActivityWithActionGetContent()
} catch (e: Exception) {
openActivityWithActionOpenDocument()
}
} else {
try {
openActivityWithActionOpenDocument()
} catch (e: Exception) {
openActivityWithActionGetContent()
}
}
} catch (e: Exception) {
Log.e(TAG, "Enable to start the file picker activity", e)
// Open browser dialog
if (lookForOpenIntentsFilePicker())
showBrowserDialog()
}
}
override fun onClick(v: View) {
onAbstractClick()
}
override fun onLongClick(v: View?): Boolean {
onAbstractClick(true)
return true
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
onAbstractClick()
return true
}
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionOpenDocument() {
val intentOpenDocument = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
else
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionGetContent() {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
else
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
}
private fun lookForOpenIntentsFilePicker(): Boolean {
var showBrowser = false
try {
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
if (fragment != null)
fragment?.startActivityForResult(intent, FILE_BROWSE)
else
activity?.startActivityForResult(intent, FILE_BROWSE)
} else {
showBrowser = true
}
} catch (e: Exception) {
Log.w(TAG, "Enable to start OPEN_INTENTS_FILE_BROWSE", e)
showBrowser = true
}
return showBrowser
}
/**
* Indicates whether the specified action can be used as an intent. This
* method queries the package manager for installed packages that can
* respond to an intent with the specified action. If no suitable package is
* found, this method returns false.
*
* @param context The application's environment.
* @param action The Intent action to check for availability.
*
* @return True if an Intent with the specified action can be sent and
* responded to, false otherwise.
*/
private fun isIntentAvailable(context: Context, action: String): Boolean {
val packageManager = context.packageManager
val intent = Intent(action)
val list = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY)
return list.size > 0
}
/**
* Show Browser dialog to select file picker app
*/
private fun showBrowserDialog() {
try {
val fileManagerDialogFragment = FileManagerDialogFragment()
fragment?.let {
fileManagerDialogFragment.show(it.parentFragmentManager, "browserDialog")
} ?: fileManagerDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
} catch (e: Exception) {
Log.e(TAG, "Can't open BrowserDialog", e)
}
}
/**
* To use in onActivityResultCallback in Fragment or Activity
* @param keyFileCallback Callback retrieve from data
* @return true if requestCode was captured, false elsechere
*/
fun onActivityResultCallback(
requestCode: Int,
resultCode: Int,
data: Intent?,
keyFileCallback: ((uri: Uri?) -> Unit)?): Boolean {
when (requestCode) {
FILE_BROWSE -> {
if (resultCode == RESULT_OK) {
val filename = data?.dataString
var keyUri: Uri? = null
if (filename != null) {
keyUri = UriUtil.parse(filename)
}
keyFileCallback?.invoke(keyUri)
}
return true
}
GET_CONTENT, OPEN_DOC -> {
if (resultCode == RESULT_OK) {
if (data != null) {
val uri = data.data
if (uri != null) {
try {
// try to persist read and write permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
activity?.contentResolver?.apply {
takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
}
} catch (e: Exception) {
// nop
}
keyFileCallback?.invoke(uri)
}
}
}
return true
}
}
return false
}
companion object {
private const val TAG = "OpenFileHelper"
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
private const val GET_CONTENT = 25745
private const val OPEN_DOC = 25845
private const val FILE_BROWSE = 25645
}
}

View File

@@ -37,12 +37,12 @@ object Stylish {
* Initialize the class with a theme preference * Initialize the class with a theme preference
* @param context Context to retrieve the theme preference * @param context Context to retrieve the theme preference
*/ */
fun init(context: Context) { fun load(context: Context) {
Log.d(Stylish::class.java.name, "Attatching to " + context.packageName) Log.d(Stylish::class.java.name, "Attatching to " + context.packageName)
themeString = PreferencesUtil.getStyle(context) themeString = PreferencesUtil.getStyle(context)
} }
private fun retrieveEquivalentSystemStyle(context: Context, styleString: String): String { fun retrieveEquivalentSystemStyle(context: Context, styleString: String): String {
val systemNightMode = when (PreferencesUtil.getStyleBrightness(context)) { val systemNightMode = when (PreferencesUtil.getStyleBrightness(context)) {
context.getString(R.string.list_style_brightness_light) -> false context.getString(R.string.list_style_brightness_light) -> false
context.getString(R.string.list_style_brightness_night) -> true context.getString(R.string.list_style_brightness_night) -> true
@@ -84,12 +84,16 @@ object Stylish {
} }
} }
fun defaultStyle(context: Context): String {
return context.getString(R.string.list_style_name_light)
}
/** /**
* Assign the style to the class attribute * Assign the style to the class attribute
* @param styleString Style id String * @param styleString Style id String
*/ */
fun assignStyle(context: Context, styleString: String) { fun assignStyle(context: Context, styleString: String) {
themeString = retrieveEquivalentSystemStyle(context, styleString) PreferencesUtil.setStyle(context, styleString)
} }
/** /**

View File

@@ -29,7 +29,7 @@ class App : MultiDexApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Stylish.init(this) Stylish.load(this)
PRNGFixes.apply() PRNGFixes.apply()
} }

View File

@@ -25,6 +25,7 @@ import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.net.Uri import android.net.Uri
import android.os.IBinder import android.os.IBinder
import android.util.Log
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.SingletonHolderParameter import com.kunzisoft.keepass.utils.SingletonHolderParameter
@@ -76,7 +77,11 @@ class CipherDatabaseAction(context: Context) {
mServiceConnection!!, mServiceConnection!!,
Context.BIND_ABOVE_CLIENT) Context.BIND_ABOVE_CLIENT)
if (mBinder == null) { if (mBinder == null) {
applicationContext.startService(mIntentAdvancedUnlockService) try {
applicationContext.startService(mIntentAdvancedUnlockService)
} catch (e: Exception) {
Log.e(TAG, "Unable to start cipher action", e)
}
} }
} }
} }
@@ -173,5 +178,7 @@ class CipherDatabaseAction(context: Context) {
).execute() ).execute()
} }
companion object : SingletonHolderParameter<CipherDatabaseAction, Context>(::CipherDatabaseAction) companion object : SingletonHolderParameter<CipherDatabaseAction, Context>(::CipherDatabaseAction) {
private val TAG = CipherDatabaseAction::class.java.name
}
} }

View File

@@ -223,9 +223,22 @@ class StructureParser(private val structure: AssistStructure) {
usernameValueCandidate = node.autofillValue usernameValueCandidate = node.autofillValue
Log.d(TAG, "Autofill username candidate android text type: ${showHexInputType(inputType)}") Log.d(TAG, "Autofill username candidate android text type: ${showHexInputType(inputType)}")
} }
inputIsVariationType(inputType,
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) -> {
// Some forms used visible password as username
if (usernameCandidate == null && usernameValueCandidate == null) {
usernameCandidate = autofillId
usernameValueCandidate = node.autofillValue
Log.d(TAG, "Autofill visible password android text type (as username): ${showHexInputType(inputType)}")
} else if (result?.passwordId == null && result?.passwordValue == null) {
result?.passwordId = autofillId
result?.passwordValue = node.autofillValue
Log.d(TAG, "Autofill visible password android text type (as password): ${showHexInputType(inputType)}")
usernameNeeded = false
}
}
inputIsVariationType(inputType, inputIsVariationType(inputType,
InputType.TYPE_TEXT_VARIATION_PASSWORD, InputType.TYPE_TEXT_VARIATION_PASSWORD,
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) -> { InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) -> {
result?.passwordId = autofillId result?.passwordId = autofillId
result?.passwordValue = node.autofillValue result?.passwordValue = node.autofillValue

View File

@@ -25,7 +25,10 @@ import android.content.Context.BIND_NOT_FOREGROUND
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
@@ -251,11 +254,16 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
} }
private fun start(bundle: Bundle? = null, actionTask: String) { private fun start(bundle: Bundle? = null, actionTask: String) {
activity.stopService(intentDatabaseTask) try {
if (bundle != null) activity.stopService(intentDatabaseTask)
intentDatabaseTask.putExtras(bundle) if (bundle != null)
intentDatabaseTask.action = actionTask intentDatabaseTask.putExtras(bundle)
activity.startService(intentDatabaseTask) intentDatabaseTask.action = actionTask
activity.startService(intentDatabaseTask)
} catch (e: Exception) {
Log.e(TAG, "Unable to perform database action", e)
Toast.makeText(activity, R.string.error_start_database_action, Toast.LENGTH_LONG).show()
}
} }
/* /*
@@ -591,4 +599,8 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
} }
, ACTION_DATABASE_SAVE) , ACTION_DATABASE_SAVE)
} }
companion object {
private val TAG = ProgressDatabaseTaskProvider::class.java.name
}
} }

View File

@@ -24,7 +24,7 @@ import android.util.Log
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.EntryDatabaseException import com.kunzisoft.keepass.database.exception.MoveEntryDatabaseException
import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException
class MoveNodesRunnable constructor( class MoveNodesRunnable constructor(
@@ -47,8 +47,10 @@ class MoveNodesRunnable constructor(
when (nodeToMove.type) { when (nodeToMove.type) {
Type.GROUP -> { Type.GROUP -> {
val groupToMove = nodeToMove as Group val groupToMove = nodeToMove as Group
// Move group in new parent if not in the current group // Move group if the parent change
if (groupToMove != mNewParent if (mOldParent != mNewParent
// and if not in the current group
&& groupToMove != mNewParent
&& !mNewParent.isContainedIn(groupToMove)) { && !mNewParent.isContainedIn(groupToMove)) {
nodeToMove.touch(modified = true, touchParents = true) nodeToMove.touch(modified = true, touchParents = true)
database.moveGroupTo(groupToMove, mNewParent) database.moveGroupTo(groupToMove, mNewParent)
@@ -68,7 +70,7 @@ class MoveNodesRunnable constructor(
database.moveEntryTo(entryToMove, mNewParent) database.moveEntryTo(entryToMove, mNewParent)
} else { } else {
// Only finish thread // Only finish thread
setError(EntryDatabaseException()) setError(MoveEntryDatabaseException())
break@foreachNode break@foreachNode
} }
} }

View File

@@ -36,6 +36,9 @@ abstract class CipherEngine {
return 16 return 16
} }
// Used only with padding workaround
var forcePaddingCompatibility = false
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class) @Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher

View File

@@ -30,7 +30,7 @@ class TwofishEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class) @Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher { override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getTwofish(opmode, key, IV) return CipherFactory.getTwofish(opmode, key, IV, forcePaddingCompatibility)
} }
override fun getEncryptionAlgorithm(): EncryptionAlgorithm { override fun getEncryptionAlgorithm(): EncryptionAlgorithm {

View File

@@ -23,7 +23,6 @@ import android.content.ContentResolver
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
@@ -55,6 +54,7 @@ import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import java.io.* import java.io.*
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -105,10 +105,6 @@ class Database {
return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache() return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache()
} }
fun setCacheDirectory(cacheDirectory: File) {
binaryCache.cacheDirectory = cacheDirectory
}
private val iconsManager: IconsManager private val iconsManager: IconsManager
get() { get() {
return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager(binaryCache) return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager(binaryCache)
@@ -363,15 +359,10 @@ class Database {
return null return null
} }
fun ensureRecycleBinExists(resources: Resources) { val groupNamesNotAllowed: List<String>
mDatabaseKDB?.ensureBackupExists() get() {
mDatabaseKDBX?.ensureRecycleBinExists(resources) return mDatabaseKDB?.groupNamesNotAllowed ?: ArrayList()
} }
fun removeRecycleBin() {
// Don't allow remove backup in KDB
mDatabaseKDBX?.removeRecycleBin()
}
private fun setDatabaseKDB(databaseKDB: DatabaseKDB) { private fun setDatabaseKDB(databaseKDB: DatabaseKDB) {
this.mDatabaseKDB = databaseKDB this.mDatabaseKDB = databaseKDB
@@ -546,25 +537,28 @@ class Database {
omitBackup: Boolean, omitBackup: Boolean,
max: Int = Integer.MAX_VALUE): Group? { max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this, return mSearchHelper?.createVirtualGroupWithSearchResult(this,
searchQuery, SearchParameters(), omitBackup, max) SearchParameters().apply {
this.searchQuery = searchQuery
}, omitBackup, max)
} }
fun createVirtualGroupFromSearchInfo(searchInfoString: String, fun createVirtualGroupFromSearchInfo(searchInfoString: String,
omitBackup: Boolean, omitBackup: Boolean,
max: Int = Integer.MAX_VALUE): Group? { max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this, return mSearchHelper?.createVirtualGroupWithSearchResult(this,
searchInfoString, SearchParameters().apply { SearchParameters().apply {
searchInTitles = true searchQuery = searchInfoString
searchInUserNames = false searchInTitles = true
searchInPasswords = false searchInUserNames = false
searchInUrls = true searchInPasswords = false
searchInNotes = true searchInUrls = true
searchInOTP = false searchInNotes = true
searchInOther = true searchInOTP = false
searchInUUIDs = false searchInOther = true
searchInTags = false searchInUUIDs = false
ignoreCase = true searchInTags = false
}, omitBackup, max) ignoreCase = true
}, omitBackup, max)
} }
val attachmentPool: AttachmentPool val attachmentPool: AttachmentPool
@@ -794,11 +788,11 @@ class Database {
} }
fun addGroupTo(group: Group, parent: Group) { fun addGroupTo(group: Group, parent: Group) {
group.groupKDB?.let { entryKDB -> group.groupKDB?.let { groupKDB ->
mDatabaseKDB?.addGroupTo(entryKDB, parent.groupKDB) mDatabaseKDB?.addGroupTo(groupKDB, parent.groupKDB)
} }
group.groupKDBX?.let { entryKDBX -> group.groupKDBX?.let { groupKDBX ->
mDatabaseKDBX?.addGroupTo(entryKDBX, parent.groupKDBX) mDatabaseKDBX?.addGroupTo(groupKDBX, parent.groupKDBX)
} }
group.afterAssignNewParent() group.afterAssignNewParent()
} }
@@ -813,11 +807,11 @@ class Database {
} }
fun removeGroupFrom(group: Group, parent: Group) { fun removeGroupFrom(group: Group, parent: Group) {
group.groupKDB?.let { entryKDB -> group.groupKDB?.let { groupKDB ->
mDatabaseKDB?.removeGroupFrom(entryKDB, parent.groupKDB) mDatabaseKDB?.removeGroupFrom(groupKDB, parent.groupKDB)
} }
group.groupKDBX?.let { entryKDBX -> group.groupKDBX?.let { groupKDBX ->
mDatabaseKDBX?.removeGroupFrom(entryKDBX, parent.groupKDBX) mDatabaseKDBX?.removeGroupFrom(groupKDBX, parent.groupKDBX)
} }
group.afterAssignNewParent() group.afterAssignNewParent()
} }
@@ -892,6 +886,16 @@ class Database {
} }
} }
fun ensureRecycleBinExists(resources: Resources) {
mDatabaseKDB?.ensureBackupExists()
mDatabaseKDBX?.ensureRecycleBinExists(resources)
}
fun removeRecycleBin() {
// Don't allow remove backup in KDB
mDatabaseKDBX?.removeRecycleBin()
}
fun canRecycle(entry: Entry): Boolean { fun canRecycle(entry: Entry): Boolean {
var canRecycle: Boolean? = null var canRecycle: Boolean? = null
entry.entryKDB?.let { entry.entryKDB?.let {

View File

@@ -466,16 +466,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
return result return result
} }
companion object {
companion object CREATOR : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
const val PMS_TAN_ENTRY = "<TAN>" const val PMS_TAN_ENTRY = "<TAN>"
/** /**
@@ -484,5 +475,16 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun newExtraFieldNameAllowed(field: Field): Boolean { fun newExtraFieldNameAllowed(field: Field): Boolean {
return EntryKDBX.newCustomNameAllowed(field.name) return EntryKDBX.newCustomNameAllowed(field.name)
} }
@JvmField
val CREATOR: Parcelable.Creator<Entry> = object : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
}
} }
} }

View File

@@ -368,14 +368,6 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupKDB?.nodeId = id groupKDB?.nodeId = id
} }
fun getLevel(): Int {
return groupKDB?.level ?: -1
}
fun setLevel(level: Int) {
groupKDB?.level = level
}
/* /*
------------ ------------
KDBX Methods KDBX Methods

View File

@@ -11,7 +11,7 @@ class BinaryCache {
*/ */
var loadedCipherKey: LoadedKey = LoadedKey.generateNewCipherKey() var loadedCipherKey: LoadedKey = LoadedKey.generateNewCipherKey()
lateinit var cacheDirectory: File var cacheDirectory: File? = null
private val voidBinary = KeyByteArray(UNKNOWN, ByteArray(0)) private val voidBinary = KeyByteArray(UNKNOWN, ByteArray(0))
@@ -19,15 +19,16 @@ class BinaryCache {
smallSize: Boolean = false, smallSize: Boolean = false,
compression: Boolean = false, compression: Boolean = false,
protection: Boolean = false): BinaryData { protection: Boolean = false): BinaryData {
return if (smallSize) { val cacheDir = cacheDirectory
return if (smallSize || cacheDir == null) {
BinaryByte(binaryId, compression, protection) BinaryByte(binaryId, compression, protection)
} else { } else {
val fileInCache = File(cacheDirectory, binaryId) val fileInCache = File(cacheDir, binaryId)
return BinaryFile(fileInCache, compression, protection) BinaryFile(fileInCache, compression, protection)
} }
} }
// Similar to file storage but much faster // Similar to file storage but much faster TODO SparseArray
private val byteArrayList = HashMap<String, ByteArray>() private val byteArrayList = HashMap<String, ByteArray>()
fun getByteArray(key: String): KeyByteArray { fun getByteArray(key: String): KeyByteArray {

View File

@@ -38,8 +38,6 @@ import kotlin.collections.ArrayList
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() { class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
private var kdfListV3: MutableList<KdfEngine> = ArrayList() private var kdfListV3: MutableList<KdfEngine> = ArrayList()
override val version: String override val version: String
@@ -55,13 +53,14 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return getGroupById(NodeIdInt(groupId)) return getGroupById(NodeIdInt(groupId))
} }
// Retrieve backup group in index
val backupGroup: GroupKDB? val backupGroup: GroupKDB?
get() { get() {
return if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID) return retrieveBackup()
null }
else
getGroupById(backupGroupId) val groupNamesNotAllowed: List<String>
get() {
return listOf(BACKUP_FOLDER_TITLE)
} }
override val kdfEngine: KdfEngine override val kdfEngine: KdfEngine
@@ -80,12 +79,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
val rootGroups: List<GroupKDB> val rootGroups: List<GroupKDB>
get() { get() {
val kids = ArrayList<GroupKDB>() return rootGroup?.getChildGroups() ?: ArrayList()
doForEachGroupInIndex { group ->
if (group.level == 0)
kids.add(group)
}
return kids
} }
override val passwordEncoding: String override val passwordEncoding: String
@@ -169,21 +163,14 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
override fun isInRecycleBin(group: GroupKDB): Boolean { override fun isInRecycleBin(group: GroupKDB): Boolean {
var currentGroup: GroupKDB? = group var currentGroup: GroupKDB? = group
val currentBackupGroup = backupGroup ?: return false
// Init backup group variable if (currentGroup == currentBackupGroup)
if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
findBackupGroupId()
if (backupGroup == null)
return false
if (currentGroup == backupGroup)
return true return true
val backupGroupId = currentBackupGroup.id
while (currentGroup != null) { while (currentGroup != null) {
if (currentGroup.level == 0 if (backupGroupId == currentGroup.id) {
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
backupGroupId = currentGroup.id
return true return true
} }
currentGroup = currentGroup.parent currentGroup = currentGroup.parent
@@ -191,12 +178,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return false return false
} }
private fun findBackupGroupId() { /**
rootGroups.forEach { currentGroup -> * Retrieve backup group with his name
if (currentGroup.level == 0 */
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) { private fun retrieveBackup(): GroupKDB? {
backupGroupId = currentGroup.id return rootGroup?.searchChildGroup {
} it.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)
} }
} }
@@ -205,8 +192,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
* if it doesn't exist * if it doesn't exist
*/ */
fun ensureBackupExists() { fun ensureBackupExists() {
findBackupGroupId()
if (backupGroup == null) { if (backupGroup == null) {
// Create recycle bin // Create recycle bin
val recycleBinGroup = createGroup().apply { val recycleBinGroup = createGroup().apply {
@@ -214,7 +199,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
icon.standard = getStandardIcon(IconImageStandard.TRASH_ID) icon.standard = getStandardIcon(IconImageStandard.TRASH_ID)
} }
addGroupTo(recycleBinGroup, rootGroup) addGroupTo(recycleBinGroup, rootGroup)
backupGroupId = recycleBinGroup.id
} }
} }
@@ -268,6 +252,5 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
val TYPE = DatabaseKDB::class.java val TYPE = DatabaseKDB::class.java
const val BACKUP_FOLDER_TITLE = "Backup" const val BACKUP_FOLDER_TITLE = "Backup"
private const val BACKUP_FOLDER_UNDEFINED_ID = -1
} }
} }

View File

@@ -23,8 +23,6 @@ import android.content.res.Resources
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.kunzisoft.encrypt.HashManager import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.longTo8Bytes
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.AesEngine import com.kunzisoft.keepass.database.crypto.AesEngine
@@ -50,6 +48,8 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VER
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.longTo8Bytes
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.w3c.dom.Node import org.w3c.dom.Node
import java.io.IOException import java.io.IOException
@@ -132,7 +132,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID) icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID)
} }
rootGroup = group rootGroup = group
addGroupIndex(group)
} }
override val version: String override val version: String
@@ -615,6 +614,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return false return false
if (recycleBin == null) if (recycleBin == null)
return false return false
if (node is GroupKDBX
&& recycleBin!!.isContainedIn(node))
return false
if (!node.isContainedIn(recycleBin!!)) if (!node.isContainedIn(recycleBin!!))
return true return true
return false return false

View File

@@ -35,7 +35,6 @@ import java.io.ByteArrayInputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.UnsupportedEncodingException import java.io.UnsupportedEncodingException
import java.security.MessageDigest
import java.util.* import java.util.*
abstract class DatabaseVersioned< abstract class DatabaseVersioned<
@@ -87,6 +86,12 @@ abstract class DatabaseVersioned<
abstract val availableEncryptionAlgorithms: List<EncryptionAlgorithm> abstract val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
var rootGroup: Group? = null var rootGroup: Group? = null
set(value) {
field = value
value?.let {
addGroupIndex(it)
}
}
@Throws(IOException::class) @Throws(IOException::class)
protected abstract fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray protected abstract fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray

View File

@@ -90,7 +90,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
url = parcel.readString() ?: url url = parcel.readString() ?: url
notes = parcel.readString() ?: notes notes = parcel.readString() ?: notes
binaryDescription = parcel.readString() ?: binaryDescription binaryDescription = parcel.readString() ?: binaryDescription
binaryDataId = parcel.readInt() val rawBinaryDataId = parcel.readInt()
binaryDataId = if (rawBinaryDataId == -1) null else rawBinaryDataId
} }
override fun readParentParcelable(parcel: Parcel): GroupKDB? { override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -109,9 +110,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeString(url) dest.writeString(url)
dest.writeString(notes) dest.writeString(notes)
dest.writeString(binaryDescription) dest.writeString(binaryDescription)
binaryDataId?.let { dest.writeInt(binaryDataId ?: -1)
dest.writeInt(it)
}
} }
fun updateWith(source: EntryKDB) { fun updateWith(source: EntryKDB) {

View File

@@ -56,32 +56,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
var additional = "" var additional = ""
var tags = "" var tags = ""
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) {
size += entry.key.length.toLong()
size += entry.value.length().toLong()
}
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.getSize(attachmentPool)
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
return size
}
override var expires: Boolean = false override var expires: Boolean = false
constructor() : super() constructor() : super()
@@ -102,6 +76,14 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
tags = parcel.readString() ?: tags tags = parcel.readString() ?: tags
} }
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeLong(usageCount.toKotlinLong()) dest.writeLong(usageCount.toKotlinLong())
@@ -164,14 +146,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return NodeIdUUID(nodeId.id) return NodeIdUUID(nodeId.id)
} }
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
/** /**
* Decode a reference key with the FieldReferencesEngine * Decode a reference key with the FieldReferencesEngine
* @param decodeRef * @param decodeRef
@@ -228,6 +202,32 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override var locationChanged = DateInstant() override var locationChanged = DateInstant()
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) {
size += entry.key.length.toLong()
size += entry.value.length().toLong()
}
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.getSize(attachmentPool)
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
return size
}
fun afterChangeParent() { fun afterChangeParent() {
locationChanged = DateInstant() locationChanged = DateInstant()
} }
@@ -349,6 +349,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
const val STR_URL = "URL" const val STR_URL = "URL"
const val STR_NOTES = "Notes" const val STR_NOTES = "Notes"
private const val FIXED_LENGTH_SIZE: Long = 128 // Approximate fixed length size
fun newCustomNameAllowed(name: String): Boolean { fun newCustomNameAllowed(name: String): Boolean {
return !(name.equals(STR_TITLE, true) return !(name.equals(STR_TITLE, true)
|| name.equals(STR_USERNAME, true) || name.equals(STR_USERNAME, true)
@@ -367,7 +369,5 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return arrayOfNulls(size) return arrayOfNulls(size)
} }
} }
private const val FIXED_LENGTH_SIZE: Long = 128 // Approximate fixed length size
} }
} }

View File

@@ -36,6 +36,10 @@ abstract class EntryVersioned
constructor(parcel: Parcel) : super(parcel) constructor(parcel: Parcel) : super(parcel)
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
}
override fun nodeIndexInParentForNaturalOrder(): Int { override fun nodeIndexInParentForNaturalOrder(): Int {
if (nodeIndexInParentForNaturalOrder == -1) { if (nodeIndexInParentForNaturalOrder == -1) {
val numberOfGroups = parent?.getChildGroups()?.size val numberOfGroups = parent?.getChildGroups()?.size

View File

@@ -19,61 +19,42 @@
*/ */
package com.kunzisoft.keepass.database.element.entry package com.kunzisoft.keepass.database.element.entry
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler
import com.kunzisoft.keepass.database.search.SearchParameters import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.utils.UuidUtil
import java.util.* import java.util.*
class FieldReferencesEngine { class FieldReferencesEngine {
inner class TargetResult(var entry: EntryKDBX?, var wanted: Char)
private inner class SprContextV4 {
var databaseV4: DatabaseKDBX? = null
var entry: EntryKDBX
var refsCache: MutableMap<String, String> = HashMap()
internal constructor(db: DatabaseKDBX, entry: EntryKDBX) {
this.databaseV4 = db
this.entry = entry
}
internal constructor(source: SprContextV4) {
this.databaseV4 = source.databaseV4
this.entry = source.entry
this.refsCache = source.refsCache
}
}
fun compile(text: String, entry: EntryKDBX, database: DatabaseKDBX): String { fun compile(text: String, entry: EntryKDBX, database: DatabaseKDBX): String {
return compileInternal(text, SprContextV4(database, entry), 0) return compileInternal(text, SprContextKDBX(database, entry), 0)
} }
private fun compileInternal(text: String?, sprContextV4: SprContextV4?, recursionLevel: Int): String { private fun compileInternal(text: String?, sprContextKDBX: SprContextKDBX?, recursionLevel: Int): String {
if (text == null) { if (text == null) {
return "" return ""
} }
if (sprContextV4 == null) { if (sprContextKDBX == null) {
return "" return ""
} }
return if (recursionLevel >= MAX_RECURSION_DEPTH) { return if (recursionLevel >= MAX_RECURSION_DEPTH) {
"" ""
} else fillRefPlaceholders(text, sprContextV4, recursionLevel) } else fillRefPlaceholders(text, sprContextKDBX, recursionLevel)
} }
private fun fillRefPlaceholders(textReference: String, contextV4: SprContextV4, recursionLevel: Int): String { private fun fillRefPlaceholders(textReference: String, contextKDBX: SprContextKDBX, recursionLevel: Int): String {
var text = textReference var text = textReference
if (contextV4.databaseV4 == null) { if (contextKDBX.databaseKDBX == null) {
return text return text
} }
var offset = 0 var offset = 0
for (i in 0..19) { for (i in 0..19) {
text = fillRefsUsingCache(text, contextV4) text = fillRefsUsingCache(text, contextKDBX)
val start = text.indexOf(STR_REF_START, offset, true) val start = text.indexOf(STR_REF_START, offset, true)
if (start < 0) { if (start < 0) {
@@ -85,7 +66,7 @@ class FieldReferencesEngine {
} }
val fullRef = text.substring(start, end + 1) val fullRef = text.substring(start, end + 1)
val result = findRefTarget(fullRef, contextV4) val result = findRefTarget(fullRef, contextKDBX)
if (result != null) { if (result != null) {
val found = result.entry val found = result.entry
@@ -103,12 +84,12 @@ class FieldReferencesEngine {
} }
if (data != null && found != null) { if (data != null && found != null) {
val subCtx = SprContextV4(contextV4) val subCtx = SprContextKDBX(contextKDBX)
subCtx.entry = found subCtx.entryKDBX = found
val innerContent = compileInternal(data, subCtx, recursionLevel + 1) val innerContent = compileInternal(data, subCtx, recursionLevel + 1)
addRefsToCache(fullRef, innerContent, contextV4) addRefsToCache(fullRef, innerContent, contextKDBX)
text = fillRefsUsingCache(text, contextV4) text = fillRefsUsingCache(text, contextKDBX)
} else { } else {
offset = start + 1 offset = start + 1
} }
@@ -119,7 +100,7 @@ class FieldReferencesEngine {
return text return text
} }
private fun findRefTarget(fullReference: String?, contextV4: SprContextV4): TargetResult? { private fun findRefTarget(fullReference: String?, contextKDBX: SprContextKDBX): TargetResult? {
var fullRef: String? = fullReference ?: return null var fullRef: String? = fullReference ?: return null
fullRef = fullRef!!.toUpperCase(Locale.ENGLISH) fullRef = fullRef!!.toUpperCase(Locale.ENGLISH)
@@ -144,7 +125,7 @@ class FieldReferencesEngine {
val searchParameters = SearchParameters() val searchParameters = SearchParameters()
searchParameters.setupNone() searchParameters.setupNone()
searchParameters.searchString = ref.substring(4) searchParameters.searchQuery = ref.substring(4)
when (scan) { when (scan) {
'T' -> searchParameters.searchInTitles = true 'T' -> searchParameters.searchInTitles = true
'U' -> searchParameters.searchInUserNames = true 'U' -> searchParameters.searchInUserNames = true
@@ -157,7 +138,7 @@ class FieldReferencesEngine {
} }
val list = ArrayList<EntryKDBX>() val list = ArrayList<EntryKDBX>()
searchEntries(contextV4.databaseV4?.rootGroup, searchParameters, list) searchEntries(contextKDBX, searchParameters, list)
return if (list.size > 0) { return if (list.size > 0) {
TargetResult(list[0], wanted) TargetResult(list[0], wanted)
@@ -165,7 +146,7 @@ class FieldReferencesEngine {
} }
private fun addRefsToCache(ref: String?, value: String?, ctx: SprContextV4?) { private fun addRefsToCache(ref: String?, value: String?, ctx: SprContextKDBX?) {
if (ref == null) { if (ref == null) {
return return
} }
@@ -181,15 +162,19 @@ class FieldReferencesEngine {
} }
} }
private fun fillRefsUsingCache(text: String, sprContextV4: SprContextV4): String { private fun fillRefsUsingCache(text: String, sprContextKDBX: SprContextKDBX): String {
var newText = text var newText = text
for ((key, value) in sprContextV4.refsCache) { for ((key, value) in sprContextKDBX.refsCache) {
newText = text.replace(key, value, true) newText = text.replace(key, value, true)
} }
return newText return newText
} }
private fun searchEntries(root: GroupKDBX?, searchParameters: SearchParameters?, listStorage: MutableList<EntryKDBX>?) { private fun searchEntries(contextKDBX: SprContextKDBX,
searchParameters: SearchParameters?,
listStorage: MutableList<EntryKDBX>?) {
val root = contextKDBX.databaseKDBX?.rootGroup
if (searchParameters == null) { if (searchParameters == null) {
return return
} }
@@ -197,7 +182,7 @@ class FieldReferencesEngine {
return return
} }
val terms = splitStringTerms(searchParameters.searchString) val terms = splitStringTerms(searchParameters.searchQuery)
if (terms.size <= 1 || searchParameters.regularExpression) { if (terms.size <= 1 || searchParameters.regularExpression) {
root!!.doForEachChild(EntryKDBXSearchHandler(searchParameters, listStorage), null) root!!.doForEachChild(EntryKDBXSearchHandler(searchParameters, listStorage), null)
return return
@@ -207,17 +192,17 @@ class FieldReferencesEngine {
val stringLengthComparator = Comparator<String> { lhs, rhs -> lhs.length - rhs.length } val stringLengthComparator = Comparator<String> { lhs, rhs -> lhs.length - rhs.length }
Collections.sort(terms, stringLengthComparator) Collections.sort(terms, stringLengthComparator)
val fullSearch = searchParameters.searchString val fullSearch = searchParameters.searchQuery
var childEntries: List<EntryKDBX>? = root!!.getChildEntries() var childEntries: List<EntryKDBX>? = root!!.getChildEntries()
for (i in terms.indices) { for (i in terms.indices) {
val pgNew = ArrayList<EntryKDBX>() val pgNew = ArrayList<EntryKDBX>()
searchParameters.searchString = terms[i] searchParameters.searchQuery = terms[i]
var negate = false var negate = false
if (searchParameters.searchString.startsWith("-")) { if (searchParameters.searchQuery.startsWith("-")) {
searchParameters.searchString = searchParameters.searchString.substring(1) searchParameters.searchQuery = searchParameters.searchQuery.substring(1)
negate = searchParameters.searchString.isNotEmpty() negate = searchParameters.searchQuery.isNotEmpty()
} }
if (!root.doForEachChild(EntryKDBXSearchHandler(searchParameters, pgNew), null)) { if (!root.doForEachChild(EntryKDBXSearchHandler(searchParameters, pgNew), null)) {
@@ -241,7 +226,7 @@ class FieldReferencesEngine {
if (childEntries != null) { if (childEntries != null) {
listStorage.addAll(childEntries) listStorage.addAll(childEntries)
} }
searchParameters.searchString = fullSearch searchParameters.searchQuery = fullSearch
} }
/** /**
@@ -279,6 +264,97 @@ class FieldReferencesEngine {
return list return list
} }
inner class TargetResult(var entry: EntryKDBX?, var wanted: Char)
private inner class SprContextKDBX {
var databaseKDBX: DatabaseKDBX? = null
var entryKDBX: EntryKDBX
var refsCache: MutableMap<String, String> = HashMap()
constructor(databaseKDBX: DatabaseKDBX, entry: EntryKDBX) {
this.databaseKDBX = databaseKDBX
this.entryKDBX = entry
}
constructor(source: SprContextKDBX) {
this.databaseKDBX = source.databaseKDBX
this.entryKDBX = source.entryKDBX
this.refsCache = source.refsCache
}
}
private class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters,
private val mListStorage: MutableList<EntryKDBX>)
: NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean {
if (mSearchParametersKDBX.excludeExpired
&& node.isCurrentlyExpires) {
return true
}
if (searchStrings(node)) {
mListStorage.add(node)
return true
}
if (searchInGroupNames(node)) {
mListStorage.add(node)
return true
}
if (searchInUUID(node)) {
mListStorage.add(node)
return true
}
return true
}
private fun searchStrings(entry: EntryKDBX): Boolean {
var searchFound = false
// Search all strings in the KDBX entry
EntryFieldsLoop@ for((key, value) in entry.fields) {
if (entryKDBXKeyIsAllowedToSearch(key, mSearchParametersKDBX)) {
val currentString = value.toString()
if (SearchHelper.checkSearchQuery(currentString, mSearchParametersKDBX)) {
searchFound = true
break@EntryFieldsLoop
}
}
}
return searchFound
}
private fun entryKDBXKeyIsAllowedToSearch(key: String, searchParameters: SearchParameters): Boolean {
return when (key) {
EntryKDBX.STR_TITLE -> searchParameters.searchInTitles
EntryKDBX.STR_USERNAME -> searchParameters.searchInUserNames
EntryKDBX.STR_PASSWORD -> searchParameters.searchInPasswords
EntryKDBX.STR_URL -> searchParameters.searchInUrls
EntryKDBX.STR_NOTES -> searchParameters.searchInNotes
else -> searchParameters.searchInOther
}
}
private fun searchInGroupNames(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInGroupNames) {
val parent = entry.parent
if (parent != null) {
return parent.title
.contains(mSearchParametersKDBX.searchQuery,
mSearchParametersKDBX.ignoreCase)
}
}
return false
}
private fun searchInUUID(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInUUIDs) {
return UuidUtil.toHexString(entry.id)
.contains(mSearchParametersKDBX.searchQuery, true)
}
return false
}
}
companion object { companion object {
private const val MAX_RECURSION_DEPTH = 12 private const val MAX_RECURSION_DEPTH = 12
private const val STR_REF_START = "{REF:" private const val STR_REF_START = "{REF:"

View File

@@ -31,14 +31,12 @@ import java.util.*
class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface { class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
var level = 0 // short
// Used by KeePass internally, don't use // Used by KeePass internally, don't use
var groupFlags = 0 var groupFlags = 0
constructor() : super() constructor() : super()
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
level = parcel.readInt()
groupFlags = parcel.readInt() groupFlags = parcel.readInt()
} }
@@ -52,13 +50,11 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeInt(level)
dest.writeInt(groupFlags) dest.writeInt(groupFlags)
} }
fun updateWith(source: GroupKDB) { fun updateWith(source: GroupKDB) {
super.updateWith(source) super.updateWith(source)
level = source.level
groupFlags = source.groupFlags groupFlags = source.groupFlags
} }
@@ -73,15 +69,12 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
return NodeIdInt(nodeId.id) return NodeIdInt(nodeId.id)
} }
override fun afterAssignNewParent() {
if (parent != null)
level = parent!!.level + 1
}
fun setGroupId(groupId: Int) { fun setGroupId(groupId: Int) {
this.nodeId = NodeIdInt(groupId) this.nodeId = NodeIdInt(groupId)
} }
override fun afterAssignNewParent() {}
companion object { companion object {
@JvmField @JvmField

View File

@@ -63,6 +63,17 @@ abstract class GroupVersioned
get() = titleGroup get() = titleGroup
set(value) { titleGroup = value } set(value) { titleGroup = value }
/**
* To determine the level from the root group (root group level is -1)
*/
fun getLevel(): Int {
var level = -1
parent?.let { parent ->
level = parent.getLevel() + 1
}
return level
}
override fun getChildGroups(): List<Group> { override fun getChildGroups(): List<Group> {
return childGroups return childGroups
} }

View File

@@ -45,23 +45,44 @@ interface GroupVersionedInterface<Group: GroupVersionedInterface<Group, Entry>,
groupHandler.operate(this as Group) groupHandler.operate(this as Group)
} }
fun doForEachChild(entryHandler: NodeHandler<Entry>, fun doForEachChild(entryHandler: NodeHandler<Entry>?,
groupHandler: NodeHandler<Group>?, groupHandler: NodeHandler<Group>?,
stopIterationWhenGroupHandlerFails: Boolean = true): Boolean { stopIterationWhenGroupHandlerOperateFalse: Boolean = true): Boolean {
for (entry in this.getChildEntries()) { if (entryHandler != null) {
if (!entryHandler.operate(entry)) for (entry in this.getChildEntries()) {
return false if (!entryHandler.operate(entry))
return false
}
} }
for (group in this.getChildGroups()) { for (group in this.getChildGroups()) {
var doActionForChild = true var doActionForChild = true
if (groupHandler != null && !groupHandler.operate(group)) { if (groupHandler != null && !groupHandler.operate(group)) {
doActionForChild = false doActionForChild = false
if (stopIterationWhenGroupHandlerFails) if (stopIterationWhenGroupHandlerOperateFalse)
return false return false
} }
if (doActionForChild) if (doActionForChild)
group.doForEachChild(entryHandler, groupHandler) group.doForEachChild(entryHandler, groupHandler, stopIterationWhenGroupHandlerOperateFalse)
} }
return true return true
} }
fun searchChildGroup(criteria: (group: Group) -> Boolean): Group? {
return searchChildGroup(this, criteria)
}
private fun searchChildGroup(rootGroup: GroupVersionedInterface<Group, Entry>,
criteria: (group: Group) -> Boolean): Group? {
for (childGroup in rootGroup.getChildGroups()) {
if (criteria.invoke(childGroup)) {
return childGroup
} else {
val subGroup = searchChildGroup(childGroup, criteria)
if (subGroup != null) {
return subGroup
}
}
}
return null
}
} }

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
class IconImage() : IconImageDraw(), Parcelable { class IconImage() : IconImageDraw() {
var standard: IconImageStandard = IconImageStandard() var standard: IconImageStandard = IconImageStandard()
var custom: IconImageCustom = IconImageCustom() var custom: IconImageCustom = IconImageCustom()

View File

@@ -20,11 +20,12 @@
package com.kunzisoft.keepass.database.element.icon package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.* import java.util.*
class IconImageCustom : Parcelable, IconImageDraw { class IconImageCustom : IconImageDraw {
var uuid: UUID var uuid: UUID
@@ -37,17 +38,17 @@ class IconImageCustom : Parcelable, IconImageDraw {
} }
constructor(parcel: Parcel) { constructor(parcel: Parcel) {
uuid = parcel.readSerializable() as UUID uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(ParcelUuid(uuid), flags)
} }
override fun describeContents(): Int { override fun describeContents(): Int {
return 0 return 0
} }
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(uuid)
}
override fun hashCode(): Int { override fun hashCode(): Int {
val prime = 31 val prime = 31
var result = 1 var result = 1

View File

@@ -19,7 +19,9 @@
*/ */
package com.kunzisoft.keepass.database.element.icon package com.kunzisoft.keepass.database.element.icon
abstract class IconImageDraw { import android.os.Parcelable
abstract class IconImageDraw : Parcelable {
var selected = false var selected = false
/** /**

View File

@@ -23,7 +23,7 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
class IconImageStandard : Parcelable, IconImageDraw { class IconImageStandard : IconImageDraw {
val id: Int val id: Int

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.element.node package com.kunzisoft.keepass.database.element.node
import android.os.Parcel import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable import android.os.Parcelable
import java.util.* import java.util.*
@@ -35,12 +36,12 @@ class NodeIdUUID : NodeId<UUID> {
} }
constructor(parcel: Parcel) { constructor(parcel: Parcel) {
id = parcel.readSerializable() as UUID id = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: id
} }
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeSerializable(id) dest.writeParcelable(ParcelUuid(id), flags)
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@@ -114,7 +114,7 @@ class NoMemoryDatabaseException: LoadDatabaseException {
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class EntryDatabaseException: LoadDatabaseException { class MoveEntryDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_move_entry_here override var errorId: Int = R.string.error_move_entry_here
constructor() : super() constructor() : super()
@@ -123,7 +123,7 @@ class EntryDatabaseException: LoadDatabaseException {
class MoveGroupDatabaseException: LoadDatabaseException { class MoveGroupDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_move_folder_in_itself override var errorId: Int = R.string.error_move_group_here
constructor() : super() constructor() : super()
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }

View File

@@ -40,6 +40,7 @@ import java.security.MessageDigest
import java.util.* import java.util.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherInputStream import javax.crypto.CipherInputStream
import kotlin.collections.HashMap
/** /**
@@ -154,11 +155,10 @@ class DatabaseInputKDB(cacheDirectory: File,
// New manual root because KDB contains multiple root groups (here available with getRootGroups()) // New manual root because KDB contains multiple root groups (here available with getRootGroups())
val newRoot = mDatabase.createGroup() val newRoot = mDatabase.createGroup()
newRoot.level = -1
mDatabase.rootGroup = newRoot mDatabase.rootGroup = newRoot
mDatabase.addGroupIndex(newRoot)
// Import all nodes // Import all nodes
val groupLevelList = HashMap<GroupKDB, Int>()
var newGroup: GroupKDB? = null var newGroup: GroupKDB? = null
var newEntry: EntryKDB? = null var newEntry: EntryKDB? = null
var currentGroupNumber = 0 var currentGroupNumber = 0
@@ -248,7 +248,7 @@ class DatabaseInputKDB(cacheDirectory: File,
} }
0x0008 -> { 0x0008 -> {
newGroup?.let { group -> newGroup?.let { group ->
group.level = cipherInputStream.readBytes2ToUShort() groupLevelList.put(group, cipherInputStream.readBytes2ToUShort())
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
entry.notes = cipherInputStream.readBytesToString(fieldSize) entry.notes = cipherInputStream.readBytesToString(fieldSize)
@@ -318,7 +318,7 @@ class DatabaseInputKDB(cacheDirectory: File,
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) { if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
constructTreeFromIndex() constructTreeFromIndex(groupLevelList)
stopContentTimer() stopContentTimer()
@@ -339,34 +339,40 @@ class DatabaseInputKDB(cacheDirectory: File,
return mDatabase return mDatabase
} }
private fun buildTreeGroups(previousGroup: GroupKDB, currentGroup: GroupKDB, groupIterator: Iterator<GroupKDB>) { private fun buildTreeGroups(groupLevelList: HashMap<GroupKDB, Int>,
previousGroup: GroupKDB,
currentGroup: GroupKDB,
groupIterator: Iterator<GroupKDB>) {
if (currentGroup.parent == null && (previousGroup.level + 1) == currentGroup.level) { val previousGroupLevel = groupLevelList[previousGroup] ?: -1
val currentGroupLevel = groupLevelList[currentGroup] ?: -1
if (currentGroup.parent == null && (previousGroupLevel + 1) == currentGroupLevel) {
// Current group has an increment level compare to the previous, current group is a child // Current group has an increment level compare to the previous, current group is a child
previousGroup.addChildGroup(currentGroup) previousGroup.addChildGroup(currentGroup)
currentGroup.parent = previousGroup currentGroup.parent = previousGroup
} else if (previousGroup.parent != null && previousGroup.level == currentGroup.level) { } else if (previousGroup.parent != null && previousGroupLevel == currentGroupLevel) {
// In the same level, previous parent is the same as previous group // In the same level, previous parent is the same as previous group
previousGroup.parent!!.addChildGroup(currentGroup) previousGroup.parent!!.addChildGroup(currentGroup)
currentGroup.parent = previousGroup.parent currentGroup.parent = previousGroup.parent
} else if (previousGroup.parent != null) { } else if (previousGroup.parent != null) {
// Previous group has a higher level than the current group, check it's parent // Previous group has a higher level than the current group, check it's parent
buildTreeGroups(previousGroup.parent!!, currentGroup, groupIterator) buildTreeGroups(groupLevelList, previousGroup.parent!!, currentGroup, groupIterator)
} }
// Next current group // Next current group
if (groupIterator.hasNext()){ if (groupIterator.hasNext()){
buildTreeGroups(currentGroup, groupIterator.next(), groupIterator) buildTreeGroups(groupLevelList, currentGroup, groupIterator.next(), groupIterator)
} }
} }
private fun constructTreeFromIndex() { private fun constructTreeFromIndex(groupLevelList: HashMap<GroupKDB, Int>) {
mDatabase.rootGroup?.let { mDatabase.rootGroup?.let { root ->
// add each group // add each group
val groupIterator = mDatabase.getGroupIndexes().iterator() val groupIterator = mDatabase.getGroupIndexes().iterator()
if (groupIterator.hasNext()) if (groupIterator.hasNext())
buildTreeGroups(it, groupIterator.next(), groupIterator) buildTreeGroups(groupLevelList, root, groupIterator.next(), groupIterator)
// add each child // add each child
for (currentEntry in mDatabase.getEntryIndexes()) { for (currentEntry in mDatabase.getEntryIndexes()) {

View File

@@ -151,9 +151,11 @@ class DatabaseInputKDBX(cacheDirectory: File,
val cipher: Cipher val cipher: Cipher
try { try {
engine = EncryptionAlgorithm.getFrom(mDatabase.cipherUuid).cipherEngine engine = EncryptionAlgorithm.getFrom(mDatabase.cipherUuid).cipherEngine
engine.forcePaddingCompatibility = true
mDatabase.setDataEngine(engine) mDatabase.setDataEngine(engine)
mDatabase.encryptionAlgorithm = engine.getEncryptionAlgorithm() mDatabase.encryptionAlgorithm = engine.getEncryptionAlgorithm()
cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV) cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV)
engine.forcePaddingCompatibility = false
} catch (e: Exception) { } catch (e: Exception) {
throw InvalidAlgorithmDatabaseException(e) throw InvalidAlgorithmDatabaseException(e)
} }

View File

@@ -20,15 +20,15 @@
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.encrypt.HashManager import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.write2BytesUShort
import com.kunzisoft.keepass.utils.write4BytesUInt
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDB import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.write2BytesUShort
import com.kunzisoft.keepass.utils.write4BytesUInt
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
@@ -59,6 +59,8 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
override fun output() { override fun output() {
// Before we output the header, we should sort our list of groups // Before we output the header, we should sort our list of groups
// and remove any orphaned nodes that are no longer part of the tree hierarchy // and remove any orphaned nodes that are no longer part of the tree hierarchy
// also remove the virtual root not present in kdb
val rootGroup = mDatabaseKDB.rootGroup
sortGroupsForOutput() sortGroupsForOutput()
val header = outputHeader(mOutputStream) val header = outputHeader(mOutputStream)
@@ -86,8 +88,10 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
throw DatabaseOutputException("Invalid algorithm parameter.", e) throw DatabaseOutputException("Invalid algorithm parameter.", e)
} catch (e: IOException) { } catch (e: IOException) {
throw DatabaseOutputException("Failed to output final encrypted part.", e) throw DatabaseOutputException("Failed to output final encrypted part.", e)
} finally {
// Add again the virtual root group for better management
mDatabaseKDB.rootGroup = rootGroup
} }
} }
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
@@ -201,7 +205,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
private fun sortGroupsForOutput() { private fun sortGroupsForOutput() {
val groupList = ArrayList<GroupKDB>() val groupList = ArrayList<GroupKDB>()
// Rebuild list according to coalation sorting order removing any orphaned groups // Rebuild list according to sorting order removing any orphaned groups
for (rootGroup in mDatabaseKDB.rootGroups) { for (rootGroup in mDatabaseKDB.rootGroups) {
sortGroup(rootGroup, groupList) sortGroup(rootGroup, groupList)
} }

View File

@@ -19,13 +19,9 @@
*/ */
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.dateTo5Bytes
import com.kunzisoft.keepass.utils.uIntTo4Bytes
import com.kunzisoft.keepass.utils.uShortTo2Bytes
import com.kunzisoft.keepass.utils.writeStringToStream
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.utils.*
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
@@ -77,7 +73,7 @@ class GroupOutputKDB(private val mGroup: GroupKDB,
// Level // Level
mOutputStream.write(LEVEL_FIELD_TYPE) mOutputStream.write(LEVEL_FIELD_TYPE)
mOutputStream.write(LEVEL_FIELD_SIZE) mOutputStream.write(LEVEL_FIELD_SIZE)
mOutputStream.write(uShortTo2Bytes(mGroup.level)) mOutputStream.write(uShortTo2Bytes(mGroup.getLevel()))
// Flags // Flags
mOutputStream.write(FLAGS_FIELD_TYPE) mOutputStream.write(FLAGS_FIELD_TYPE)

View File

@@ -1,89 +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.database.search
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters,
private val mListStorage: MutableList<EntryKDBX>)
: NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean {
if (mSearchParametersKDBX.excludeExpired
&& node.isCurrentlyExpires) {
return true
}
if (searchStrings(node)) {
mListStorage.add(node)
return true
}
if (searchInGroupNames(node)) {
mListStorage.add(node)
return true
}
if (searchInUUID(node)) {
mListStorage.add(node)
return true
}
return true
}
private fun searchInGroupNames(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInGroupNames) {
val parent = entry.parent
if (parent != null) {
return parent.title
.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)
}
}
return false
}
private fun searchInUUID(entry: EntryKDBX): Boolean {
if (mSearchParametersKDBX.searchInUUIDs) {
return UuidUtil.toHexString(entry.id)
.contains(mSearchParametersKDBX.searchString, true)
}
return false
}
private fun searchStrings(entry: EntryKDBX): Boolean {
val iterator = EntrySearchStringIteratorKDBX(entry, mSearchParametersKDBX)
while (iterator.hasNext()) {
val stringValue = iterator.next()
if (stringValue.isNotEmpty()) {
if (stringValue.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)) {
return true
}
}
}
return false
}
}

View File

@@ -24,15 +24,106 @@ import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDB
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_FIELD
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.StringUtil.removeDiacriticalMarks
class SearchHelper { class SearchHelper {
private var incrementEntry = 0
fun createVirtualGroupWithSearchResult(database: Database,
searchParameters: SearchParameters,
omitBackup: Boolean,
max: Int): Group? {
val searchGroup = database.createGroup()
searchGroup?.isVirtual = true
searchGroup?.title = "\"" + searchParameters.searchQuery + "\""
// Search all entries
incrementEntry = 0
database.rootGroup?.doForEachChild(
object : NodeHandler<Entry>() {
override fun operate(node: Entry): Boolean {
if (incrementEntry >= max)
return false
if (entryContainsString(database, node, searchParameters)) {
searchGroup?.addChildEntry(node)
incrementEntry++
}
// Stop searching when we have max entries
return incrementEntry < max
}
},
object : NodeHandler<Group>() {
override fun operate(node: Group): Boolean {
return when {
incrementEntry >= max -> false
database.isGroupSearchable(node, omitBackup) -> true
else -> false
}
}
},
false)
return searchGroup
}
private fun entryContainsString(database: Database,
entry: Entry,
searchParameters: SearchParameters): Boolean {
val searchQuery = searchParameters.searchQuery
// Entry don't contains string if the search string is empty
if (searchQuery.isEmpty())
return false
database.startManageEntry(entry)
// Search all strings in the entry
val searchFound = searchInEntry(entry, searchParameters)
database.stopManageEntry(entry)
return searchFound
}
private fun searchInEntry(entry: Entry,
searchParameters: SearchParameters): Boolean {
// Search all strings in the KDBX entry
if (searchParameters.searchInTitles) {
if (checkSearchQuery(entry.title, searchParameters))
return true
}
if (searchParameters.searchInUserNames) {
if (checkSearchQuery(entry.username, searchParameters))
return true
}
if (searchParameters.searchInPasswords) {
if (checkSearchQuery(entry.password, searchParameters))
return true
}
if (searchParameters.searchInUrls) {
if (checkSearchQuery(entry.url, searchParameters))
return true
}
if (searchParameters.searchInNotes) {
if (checkSearchQuery(entry.notes, searchParameters))
return true
}
if (searchParameters.searchInOther) {
entry.getExtraFields().forEach { field ->
if (field.name != OTP_FIELD
|| (field.name == OTP_FIELD && searchParameters.searchInOTP)) {
if (checkSearchQuery(field.protectedValue.toString(), searchParameters))
return true
}
}
}
return false
}
companion object { companion object {
const val MAX_SEARCH_ENTRY = 10 const val MAX_SEARCH_ENTRY = 10
@@ -70,75 +161,17 @@ class SearchHelper {
onDatabaseClosed.invoke() onDatabaseClosed.invoke()
} }
} }
}
private var incrementEntry = 0 fun checkSearchQuery(stringToCheck: String, searchParameters: SearchParameters): Boolean {
if (stringToCheck.isNotEmpty()
fun createVirtualGroupWithSearchResult(database: Database, && stringToCheck
searchQuery: String, .removeDiacriticalMarks()
searchParameters: SearchParameters, .contains(searchParameters.searchQuery
omitBackup: Boolean, .removeDiacriticalMarks(),
max: Int): Group? { searchParameters.ignoreCase)) {
return true
val searchGroup = database.createGroup()
searchGroup?.isVirtual = true
searchGroup?.title = "\"" + searchQuery + "\""
// Search all entries
incrementEntry = 0
database.rootGroup?.doForEachChild(
object : NodeHandler<Entry>() {
override fun operate(node: Entry): Boolean {
if (incrementEntry >= max)
return false
if (entryContainsString(node, searchQuery, searchParameters)) {
searchGroup?.addChildEntry(node)
incrementEntry++
}
// Stop searching when we have max entries
return incrementEntry < max
}
},
object : NodeHandler<Group>() {
override fun operate(node: Group): Boolean {
return when {
incrementEntry >= max -> false
database.isGroupSearchable(node, omitBackup) -> true
else -> false
}
}
},
false)
return searchGroup
}
private fun entryContainsString(entry: Entry,
searchQuery: String,
searchParameters: SearchParameters): Boolean {
// Entry don't contains string if the search string is empty
if (searchQuery.isEmpty())
return false
// Search all strings in the entry
var iterator: Iterator<String>? = null
entry.entryKDB?.let {
iterator = EntrySearchStringIteratorKDB(it, searchParameters)
}
entry.entryKDBX?.let {
iterator = EntrySearchStringIteratorKDBX(it, searchParameters)
}
iterator?.let {
while (it.hasNext()) {
val currentString = it.next()
if (currentString.isNotEmpty()
&& currentString.contains(searchQuery, true)) {
return true
}
} }
return false
} }
return false
} }
} }

View File

@@ -24,7 +24,7 @@ package com.kunzisoft.keepass.database.search
*/ */
class SearchParameters { class SearchParameters {
var searchString: String = "" var searchQuery: String = ""
var regularExpression = false var regularExpression = false
var searchInTitles = true var searchInTitles = true

View File

@@ -1,87 +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.database.search.iterator
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.search.SearchParameters
import java.util.NoSuchElementException
class EntrySearchStringIteratorKDB(
private val mEntry: EntryKDB,
private val mSearchParameters: SearchParameters)
: Iterator<String> {
private var current = 0
private val currentString: String
get() {
return when (current) {
title -> mEntry.title
url -> mEntry.url
username -> mEntry.username
notes -> mEntry.notes
else -> ""
}
}
override fun hasNext(): Boolean {
return current < maxEntries
}
override fun next(): String {
// Past the end of the list
if (current == maxEntries) {
throw NoSuchElementException("Past final string")
}
useSearchParameters()
val str = currentString
current++
return str
}
private fun useSearchParameters() {
var found = false
while (!found) {
found = when (current) {
title -> mSearchParameters.searchInTitles
url -> mSearchParameters.searchInUrls
username -> mSearchParameters.searchInUserNames
notes -> mSearchParameters.searchInNotes
else -> true
}
if (!found) {
current++
}
}
}
companion object {
private const val title = 0
private const val url = 1
private const val username = 2
private const val notes = 3
private const val maxEntries = 4
}
}

View File

@@ -1,84 +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.database.search.iterator
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.otp.OtpEntryFields
import java.util.*
import kotlin.collections.Map.Entry
class EntrySearchStringIteratorKDBX(
entry: EntryKDBX,
private val mSearchParameters: SearchParameters)
: Iterator<String> {
private var mCurrent: String? = null
private var mSetIterator: Iterator<Entry<String, ProtectedString>>? = null
init {
mSetIterator = entry.fields.entries.iterator()
advance()
}
override fun hasNext(): Boolean {
return mCurrent != null
}
override fun next(): String {
if (mCurrent == null) {
throw NoSuchElementException("Past the end of the list.")
}
val next:String = mCurrent!!
advance()
return next
}
private fun advance() {
mSetIterator?.let {
while (it.hasNext()) {
val entry = it.next()
val key = entry.key
if (searchInField(key)) {
mCurrent = entry.value.toString()
return
}
}
}
mCurrent = null
}
private fun searchInField(key: String): Boolean {
return when (key) {
EntryKDBX.STR_TITLE -> mSearchParameters.searchInTitles
EntryKDBX.STR_USERNAME -> mSearchParameters.searchInUserNames
EntryKDBX.STR_PASSWORD -> mSearchParameters.searchInPasswords
EntryKDBX.STR_URL -> mSearchParameters.searchInUrls
EntryKDBX.STR_NOTES -> mSearchParameters.searchInNotes
OtpEntryFields.OTP_FIELD -> mSearchParameters.searchInOTP
else -> mSearchParameters.searchInOther
}
}
}

View File

@@ -10,7 +10,6 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import kotlinx.coroutines.*
class AdvancedUnlockNotificationService : NotificationService() { class AdvancedUnlockNotificationService : NotificationService() {
@@ -18,9 +17,6 @@ class AdvancedUnlockNotificationService : NotificationService() {
private var mActionTaskBinder = AdvancedUnlockBinder() private var mActionTaskBinder = AdvancedUnlockBinder()
private var notificationTimeoutMilliSecs: Long = 0
private var mTimerJob: Job? = null
inner class AdvancedUnlockBinder: Binder() { inner class AdvancedUnlockBinder: Binder() {
fun getCipherDatabase(databaseUri: Uri): CipherDatabaseEntity? { fun getCipherDatabase(databaseUri: Uri): CipherDatabaseEntity? {
return mTempCipherDao.firstOrNull { it.databaseUri == databaseUri.toString()} return mTempCipherDao.firstOrNull { it.databaseUri == databaseUri.toString()}
@@ -80,23 +76,11 @@ class AdvancedUnlockNotificationService : NotificationService() {
when (intent?.action) { when (intent?.action) {
ACTION_TIMEOUT -> { ACTION_TIMEOUT -> {
notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this) val notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this)
// Not necessarily a foreground service // Not necessarily a foreground service
if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) { if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
mTimerJob = CoroutineScope(Dispatchers.Main).launch { defineTimerJob(notificationBuilder, notificationTimeoutMilliSecs) {
val maxPos = 100 stopSelf()
val posDurationMills = notificationTimeoutMilliSecs / maxPos
for (pos in maxPos downTo 0) {
notificationBuilder.setProgress(maxPos, pos, false)
startForeground(notificationId, notificationBuilder.build())
delay(posDurationMills)
if (pos <= 0) {
stopSelf()
}
}
notificationManager?.cancel(notificationId)
mTimerJob = null
cancel()
} }
} else { } else {
startForeground(notificationId, notificationBuilder.build()) startForeground(notificationId, notificationBuilder.build())
@@ -118,7 +102,6 @@ class AdvancedUnlockNotificationService : NotificationService() {
override fun onDestroy() { override fun onDestroy() {
mTempCipherDao.clear() mTempCipherDao.clear()
mTimerJob?.cancel()
super.onDestroy() super.onDestroy()
} }

View File

@@ -26,7 +26,6 @@ import android.net.Uri
import android.os.Binder import android.os.Binder
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
@@ -34,6 +33,7 @@ import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
@@ -173,7 +173,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
putExtra(FILE_URI_KEY, attachmentNotification.uri) putExtra(FILE_URI_KEY, attachmentNotification.uri)
}, PendingIntent.FLAG_CANCEL_CURRENT) }, PendingIntent.FLAG_CANCEL_CURRENT)
val fileName = DocumentFile.fromSingleUri(this, attachmentNotification.uri)?.name ?: "" val fileName = UriUtil.getFileData(this, attachmentNotification.uri)?.name
?: attachmentNotification.uri.path
val builder = buildNewNotification().apply { val builder = buildNewNotification().apply {
when (attachmentNotification.entryAttachmentState.streamDirection) { when (attachmentNotification.entryAttachmentState.streamDirection) {

View File

@@ -37,8 +37,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
override val notificationId = 485 override val notificationId = 485
private var mEntryInfo: EntryInfo? = null private var mEntryInfo: EntryInfo? = null
private var clipboardHelper: ClipboardHelper? = null private var clipboardHelper: ClipboardHelper? = null
private var notificationTimeoutMilliSecs: Long = 0 private var mNotificationTimeoutMilliSecs: Long = 0
private var cleanCopyNotificationTimerTask: Thread? = null
override fun retrieveChannelId(): String { override fun retrieveChannelId(): String {
return CHANNEL_CLIPBOARD_ID return CHANNEL_CLIPBOARD_ID
@@ -70,7 +69,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
mEntryInfo = intent?.getParcelableExtra(EXTRA_ENTRY_INFO) mEntryInfo = intent?.getParcelableExtra(EXTRA_ENTRY_INFO)
//Get settings //Get settings
notificationTimeoutMilliSecs = PreferencesUtil.getClipboardTimeout(this) mNotificationTimeoutMilliSecs = PreferencesUtil.getClipboardTimeout(this)
when { when {
intent == null -> Log.w(TAG, "null intent") intent == null -> Log.w(TAG, "null intent")
@@ -78,7 +77,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
newNotification(mEntryInfo?.title, constructListOfField(intent)) newNotification(mEntryInfo?.title, constructListOfField(intent))
} }
ACTION_CLEAN_CLIPBOARD == intent.action -> { ACTION_CLEAN_CLIPBOARD == intent.action -> {
stopTask(cleanCopyNotificationTimerTask) mTimerJob?.cancel()
cleanClipboard() cleanClipboard()
stopNotificationAndSendLockIfNeeded() stopNotificationAndSendLockIfNeeded()
} }
@@ -121,7 +120,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
} }
private fun newNotification(title: String?, fieldsToAdd: ArrayList<ClipboardEntryNotificationField>) { private fun newNotification(title: String?, fieldsToAdd: ArrayList<ClipboardEntryNotificationField>) {
stopTask(cleanCopyNotificationTimerTask) mTimerJob?.cancel()
val builder = buildNewNotification() val builder = buildNewNotification()
.setSmallIcon(R.drawable.notification_ic_clipboard_key_24dp) .setSmallIcon(R.drawable.notification_ic_clipboard_key_24dp)
@@ -147,7 +146,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
} }
private fun copyField(fieldToCopy: ClipboardEntryNotificationField, nextFields: ArrayList<ClipboardEntryNotificationField>) { private fun copyField(fieldToCopy: ClipboardEntryNotificationField, nextFields: ArrayList<ClipboardEntryNotificationField>) {
stopTask(cleanCopyNotificationTimerTask) mTimerJob?.cancel()
try { try {
var generatedValue = fieldToCopy.getGeneratedValue(mEntryInfo) var generatedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
@@ -170,40 +169,23 @@ class ClipboardEntryNotificationService : LockNotificationService() {
this, 0, cleanIntent, PendingIntent.FLAG_UPDATE_CURRENT) this, 0, cleanIntent, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setDeleteIntent(cleanPendingIntent) builder.setDeleteIntent(cleanPendingIntent)
val myNotificationId = notificationId if (mNotificationTimeoutMilliSecs != NEVER) {
defineTimerJob(builder, mNotificationTimeoutMilliSecs, {
if (notificationTimeoutMilliSecs != NEVER) { val newGeneratedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
cleanCopyNotificationTimerTask = Thread { // New auto generated value
val maxPos = 100 if (generatedValue != newGeneratedValue) {
val posDurationMills = notificationTimeoutMilliSecs / maxPos generatedValue = newGeneratedValue
for (pos in maxPos downTo 0) { clipboardHelper?.copyToClipboard(fieldToCopy.label, generatedValue)
val newGeneratedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
// New auto generated value
if (generatedValue != newGeneratedValue) {
generatedValue = newGeneratedValue
clipboardHelper?.copyToClipboard(fieldToCopy.label, generatedValue)
}
builder.setProgress(maxPos, pos, false)
notificationManager?.notify(myNotificationId, builder.build())
try {
Thread.sleep(posDurationMills)
} catch (e: InterruptedException) {
break
}
if (pos <= 0) {
stopNotificationAndSendLockIfNeeded()
}
} }
stopTask(cleanCopyNotificationTimerTask) }) {
notificationManager?.cancel(myNotificationId) stopNotificationAndSendLockIfNeeded()
// Clean password only if no next field // Clean password only if no next field
if (nextFields.size <= 0) if (nextFields.size <= 0)
cleanClipboard() cleanClipboard()
} }
cleanCopyNotificationTimerTask?.start()
} else { } else {
// No timer // No timer
notificationManager?.notify(myNotificationId, builder.build()) notificationManager?.notify(notificationId, builder.build())
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -228,10 +210,6 @@ class ClipboardEntryNotificationService : LockNotificationService() {
override fun onDestroy() { override fun onDestroy() {
cleanClipboard() cleanClipboard()
stopTask(cleanCopyNotificationTimerTask)
cleanCopyNotificationTimerTask = null
super.onDestroy() super.onDestroy()
} }

View File

@@ -35,8 +35,7 @@ import com.kunzisoft.keepass.utils.LOCK_ACTION
class KeyboardEntryNotificationService : LockNotificationService() { class KeyboardEntryNotificationService : LockNotificationService() {
override val notificationId = 486 override val notificationId = 486
private var cleanNotificationTimerTask: Thread? = null private var mNotificationTimeoutMilliSecs: Long = 0
private var notificationTimeoutMilliSecs: Long = 0
private var pendingDeleteIntent: PendingIntent? = null private var pendingDeleteIntent: PendingIntent? = null
@@ -61,7 +60,7 @@ class KeyboardEntryNotificationService : LockNotificationService() {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
//Get settings //Get settings
notificationTimeoutMilliSecs = PreferenceManager.getDefaultSharedPreferences(this) mNotificationTimeoutMilliSecs = PreferenceManager.getDefaultSharedPreferences(this)
.getString(getString(R.string.keyboard_entry_timeout_key), .getString(getString(R.string.keyboard_entry_timeout_key),
getString(R.string.timeout_default))?.toLong() ?: TimeoutHelper.DEFAULT_TIMEOUT getString(R.string.timeout_default))?.toLong() ?: TimeoutHelper.DEFAULT_TIMEOUT
@@ -107,27 +106,12 @@ class KeyboardEntryNotificationService : LockNotificationService() {
notificationManager?.cancel(notificationId) notificationManager?.cancel(notificationId)
notificationManager?.notify(notificationId, builder.build()) notificationManager?.notify(notificationId, builder.build())
stopTask(cleanNotificationTimerTask)
// Timeout only if notification clear is available // Timeout only if notification clear is available
if (PreferencesUtil.isClearKeyboardNotificationEnable(this)) { if (PreferencesUtil.isClearKeyboardNotificationEnable(this)) {
if (notificationTimeoutMilliSecs != TimeoutHelper.NEVER) { if (mNotificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
cleanNotificationTimerTask = Thread { defineTimerJob(builder, mNotificationTimeoutMilliSecs) {
val maxPos = 100 stopNotificationAndSendLockIfNeeded()
val posDurationMills = notificationTimeoutMilliSecs / maxPos
for (pos in maxPos downTo 0) {
builder.setProgress(maxPos, pos, false)
notificationManager?.notify(notificationId, builder.build())
try {
Thread.sleep(posDurationMills)
} catch (e: InterruptedException) {
break
}
if (pos <= 0) {
stopNotificationAndSendLockIfNeeded()
}
}
} }
cleanNotificationTimerTask?.start()
} }
} }
} }
@@ -142,8 +126,6 @@ class KeyboardEntryNotificationService : LockNotificationService() {
// Remove the entry from the keyboard // Remove the entry from the keyboard
MagikIME.removeEntry(this) MagikIME.removeEntry(this)
stopTask(cleanNotificationTimerTask)
cleanNotificationTimerTask = null
pendingDeleteIntent?.cancel() pendingDeleteIntent?.cancel()
super.onDestroy() super.onDestroy()

View File

@@ -50,11 +50,6 @@ abstract class LockNotificationService : NotificationService() {
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
} }
protected fun stopTask(task: Thread?) {
if (task != null && task.isAlive)
task.interrupt()
}
override fun onTaskRemoved(rootIntent: Intent?) { override fun onTaskRemoved(rootIntent: Intent?) {
notificationManager?.cancel(notificationId) notificationManager?.cancel(notificationId)

View File

@@ -11,6 +11,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.Stylish import com.kunzisoft.keepass.activities.stylish.Stylish
import kotlinx.coroutines.*
abstract class NotificationService : Service() { abstract class NotificationService : Service() {
@@ -18,6 +19,8 @@ abstract class NotificationService : Service() {
protected var notificationManager: NotificationManagerCompat? = null protected var notificationManager: NotificationManagerCompat? = null
private var colorNotificationAccent: Int = 0 private var colorNotificationAccent: Int = 0
protected var mTimerJob: Job? = null
protected abstract val notificationId: Int protected abstract val notificationId: Int
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
@@ -71,7 +74,33 @@ abstract class NotificationService : Service() {
} }
} }
protected fun defineTimerJob(builder: NotificationCompat.Builder,
timeoutMilliseconds: Long,
actionAfterASecond: (() -> Unit)? = null,
actionEnd: () -> Unit) {
mTimerJob?.cancel()
mTimerJob = CoroutineScope(Dispatchers.Main).launch {
val timeoutInSeconds = timeoutMilliseconds / 1000L
for (currentTime in timeoutInSeconds downTo 0) {
actionAfterASecond?.invoke()
builder.setProgress(100,
(currentTime * 100 / timeoutInSeconds).toInt(),
false)
startForeground(notificationId, builder.build())
delay(1000)
if (currentTime <= 0) {
actionEnd()
}
}
notificationManager?.cancel(notificationId)
mTimerJob = null
cancel()
}
}
override fun onDestroy() { override fun onDestroy() {
mTimerJob?.cancel()
mTimerJob = null
notificationManager?.cancel(notificationId) notificationManager?.cancel(notificationId)
super.onDestroy() super.onDestroy()

View File

@@ -20,9 +20,12 @@
package com.kunzisoft.keepass.settings package com.kunzisoft.keepass.settings
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
class MagikeyboardSettingsFragment : PreferenceFragmentCompat() { class MagikeyboardSettingsFragment : PreferenceFragmentCompat() {
@@ -30,4 +33,31 @@ class MagikeyboardSettingsFragment : PreferenceFragmentCompat() {
// Load the preferences from an XML resource // Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences_keyboard, rootKey) setPreferencesFromResource(R.xml.preferences_keyboard, rootKey)
} }
override fun onDisplayPreferenceDialog(preference: Preference?) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null
// Main Preferences
when (preference?.key) {
getString(R.string.keyboard_entry_timeout_key) -> {
dialogFragment = DurationDialogFragmentCompat.newInstance(preference.key)
}
else -> otherDialogFragment = true
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}
// Could not be handled here. Try with the super method.
else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference)
}
}
companion object {
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
}
} }

View File

@@ -30,6 +30,7 @@ import android.view.autofill.AutofillManager
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
@@ -46,6 +47,7 @@ import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.icons.IconPackChooser import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.preference.IconPackListPreference import com.kunzisoft.keepass.settings.preference.IconPackListPreference
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
@@ -90,6 +92,20 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
true true
} }
findPreference<Preference>(getString(R.string.import_app_properties_key))?.setOnPreferenceClickListener { _ ->
(activity as? SettingsActivity?)?.apply {
importAppProperties()
}
true
}
findPreference<Preference>(getString(R.string.export_app_properties_key))?.setOnPreferenceClickListener { _ ->
(activity as? SettingsActivity?)?.apply {
exportAppProperties()
}
true
}
} }
} }
@@ -388,10 +404,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
Stylish.assignStyle(activity, styleIdString) Stylish.assignStyle(activity, styleIdString)
// Relaunch the current activity to redraw theme // Relaunch the current activity to redraw theme
(activity as? SettingsActivity?)?.apply { (activity as? SettingsActivity?)?.apply {
keepCurrentScreen() relaunchCurrentScreen()
startActivity(intent)
finish()
activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
} }
} }
styleEnabled styleEnabled
@@ -399,10 +412,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
findPreference<ListPreference>(getString(R.string.setting_style_brightness_key))?.setOnPreferenceChangeListener { _, _ -> findPreference<ListPreference>(getString(R.string.setting_style_brightness_key))?.setOnPreferenceChangeListener { _, _ ->
(activity as? SettingsActivity?)?.apply { (activity as? SettingsActivity?)?.apply {
keepCurrentScreen() relaunchCurrentScreen()
startActivity(intent)
finish()
activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
} }
true true
} }
@@ -440,6 +450,31 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
} }
override fun onDisplayPreferenceDialog(preference: Preference?) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null
// Main Preferences
when (preference?.key) {
getString(R.string.app_timeout_key),
getString(R.string.clipboard_timeout_key),
getString(R.string.temp_advanced_unlock_timeout_key) -> {
dialogFragment = DurationDialogFragmentCompat.newInstance(preference.key)
}
else -> otherDialogFragment = true
}
if (dialogFragment != null) {
dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}
// Could not be handled here. Try with the super method.
else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference)
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
activity?.let { activity -> activity?.let { activity ->
@@ -470,7 +505,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
companion object { companion object {
private const val REQUEST_CODE_AUTOFILL = 5201 private const val REQUEST_CODE_AUTOFILL = 5201
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
} }
} }

View File

@@ -576,7 +576,6 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
} }
companion object { companion object {
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT" private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
} }
} }

View File

@@ -21,14 +21,17 @@ package com.kunzisoft.keepass.settings
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.util.Log
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.Stylish import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import java.util.* import java.util.*
@@ -134,28 +137,48 @@ object PreferencesUtil {
} }
fun getStyle(context: Context): String { fun getStyle(context: Context): String {
val stylishPrefKey = context.getString(R.string.setting_style_key) val defaultStyleString = Stylish.defaultStyle(context)
val defaultStyleString = context.getString(R.string.list_style_name_light)
val styleString = PreferenceManager.getDefaultSharedPreferences(context) val styleString = PreferenceManager.getDefaultSharedPreferences(context)
.getString(stylishPrefKey, defaultStyleString) .getString(context.getString(R.string.setting_style_key), defaultStyleString)
?: defaultStyleString ?: defaultStyleString
return Stylish.retrieveEquivalentLightStyle(context, styleString) // Return the system style
return Stylish.retrieveEquivalentSystemStyle(context, styleString)
}
fun setStyle(context: Context, styleString: String) {
var tempThemeString = styleString
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context)) {
if (tempThemeString in BuildConfig.STYLES_DISABLED) {
tempThemeString = Stylish.defaultStyle(context)
}
}
// Store light style to show selection in array list
tempThemeString = Stylish.retrieveEquivalentLightStyle(context, tempThemeString)
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putString(context.getString(R.string.setting_style_key), tempThemeString)
.apply()
Stylish.load(context)
} }
fun getStyleBrightness(context: Context): String? { fun getStyleBrightness(context: Context): String? {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getString(context.getString(R.string.setting_style_brightness_key), return prefs.getString(context.getString(R.string.setting_style_brightness_key),
context.resources.getString(R.string.list_style_brightness_follow_system)) context.getString(R.string.list_style_brightness_follow_system))
} }
/** /**
* Retrieve the text size in % (1 for 100%) * Retrieve the text size in % (1 for 100%)
*/ */
fun getListTextSize(context: Context): Float { fun getListTextSize(context: Context): Float {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val index = try {
val listSizeString = prefs.getString(context.getString(R.string.list_size_key), val prefs = PreferenceManager.getDefaultSharedPreferences(context)
context.getString(R.string.list_size_string_medium)) val listSizeString = prefs.getString(context.getString(R.string.list_size_key),
val index = context.resources.getStringArray(R.array.list_size_string_values).indexOf(listSizeString) context.getString(R.string.list_size_string_medium))
context.resources.getStringArray(R.array.list_size_string_values).indexOf(listSizeString)
} catch (e: Exception) {
1
}
val typedArray = context.resources.obtainTypedArray(R.array.list_size_values) val typedArray = context.resources.obtainTypedArray(R.array.list_size_values)
val listSize = typedArray.getFloat(index, 1.0F) val listSize = typedArray.getFloat(index, 1.0F)
typedArray.recycle() typedArray.recycle()
@@ -289,11 +312,13 @@ object PreferencesUtil {
} }
fun getListSort(context: Context): SortNodeEnum { fun getListSort(context: Context): SortNodeEnum {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) try {
prefs.getString(context.getString(R.string.sort_node_key), val prefs = PreferenceManager.getDefaultSharedPreferences(context)
SortNodeEnum.DB.name)?.let { prefs.getString(context.getString(R.string.sort_node_key),
return SortNodeEnum.valueOf(it) SortNodeEnum.DB.name)?.let {
} return SortNodeEnum.valueOf(it)
}
} catch (e: Exception) {}
return SortNodeEnum.DB return SortNodeEnum.DB
} }
@@ -517,4 +542,133 @@ object PreferencesUtil {
.putStringSet(context.getString(R.string.autofill_web_domain_blocklist_key), setItems) .putStringSet(context.getString(R.string.autofill_web_domain_blocklist_key), setItems)
.apply() .apply()
} }
fun getAppProperties(context: Context): Properties {
val properties = Properties()
for ((name, value) in PreferenceManager.getDefaultSharedPreferences(context).all) {
properties[name] = value.toString()
}
for ((name, value) in Education.getEducationSharedPreferences(context).all) {
properties[name] = value.toString()
}
return properties
}
private fun getStringSetFromProperties(value: String): Set<String> {
return value.removePrefix("[")
.removeSuffix("]")
.split(", ")
.toSet()
}
private fun putPropertiesInPreferences(properties: Properties,
preferences: SharedPreferences,
putProperty: (editor: SharedPreferences.Editor,
name: String,
value: String) -> Unit) {
preferences.edit().apply {
for ((name, value) in properties) {
try {
putProperty(this, name as String, value as String)
} catch (e:Exception) {
Log.e("PreferencesUtil", "Error when trying to parse app property $name=$value", e)
}
}
}.apply()
}
fun setAppProperties(context: Context, properties: Properties) {
putPropertiesInPreferences(properties,
PreferenceManager.getDefaultSharedPreferences(context)) { editor, name, value ->
when (name) {
context.getString(R.string.allow_no_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.delete_entered_password_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_read_only_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_auto_save_database_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.omit_backup_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.auto_focus_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.subdomain_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.app_timeout_key) -> editor.putString(name, value.toLong().toString())
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())
context.getString(R.string.hide_broken_locations_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.remember_keyfile_locations_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.biometric_unlock_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.device_credential_unlock_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.biometric_auto_open_prompt_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.temp_advanced_unlock_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.temp_advanced_unlock_timeout_key) -> editor.putString(name, value.toLong().toString())
context.getString(R.string.magic_keyboard_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.clipboard_notifications_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.clear_clipboard_notification_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.clipboard_timeout_key) -> editor.putString(name, value.toLong().toString())
context.getString(R.string.settings_autofill_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_notification_entry_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_notification_entry_clear_close_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_entry_timeout_key) -> editor.putString(name, value.toLong().toString())
context.getString(R.string.keyboard_selection_entry_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_search_share_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_save_search_info_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_auto_go_action_key) -> editor.putBoolean(name, value.toBoolean())
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_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_save_search_info_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_ask_to_save_data_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_application_id_blocklist_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
context.getString(R.string.autofill_web_domain_blocklist_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
context.getString(R.string.setting_style_key) -> setStyle(context, value)
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.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.list_size_key) -> editor.putString(name, value)
context.getString(R.string.monospace_font_fields_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.hide_expired_entries_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.show_uuid_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_education_screens_key) -> editor.putBoolean(name, value.toBoolean())
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())
context.getString(R.string.sort_recycle_bin_bottom_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.allow_copy_password_first_time_key) -> editor.putBoolean(name, value.toBoolean())
}
}
putPropertiesInPreferences(properties,
Education.getEducationSharedPreferences(context)) { editor, name, value ->
when (name) {
context.getString(R.string.education_create_db_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_select_db_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_unlock_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_read_only_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_biometric_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_new_node_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_sort_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_lock_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_copy_username_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_entry_edit_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_password_generator_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_entry_new_field_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_add_attachment_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.education_setup_OTP_key) -> editor.putBoolean(name, value.toBoolean())
}
}
}
} }

View File

@@ -24,22 +24,27 @@ import android.app.backup.BackupManager
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import java.util.*
open class SettingsActivity open class SettingsActivity
: LockingActivity(), : LockingActivity(),
@@ -48,6 +53,8 @@ open class SettingsActivity
PasswordEncodingDialogFragment.Listener { PasswordEncodingDialogFragment.Listener {
private var backupManager: BackupManager? = null private var backupManager: BackupManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null
private var appPropertiesFileCreationRequestCode: Int? = null
private var coordinatorLayout: CoordinatorLayout? = null private var coordinatorLayout: CoordinatorLayout? = null
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
@@ -70,6 +77,8 @@ open class SettingsActivity
coordinatorLayout = findViewById(R.id.toolbar_coordinator) coordinatorLayout = findViewById(R.id.toolbar_coordinator)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
mExternalFileHelper = ExternalFileHelper(this)
if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty()) if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty())
toolbar?.setTitle(R.string.settings) toolbar?.setTitle(R.string.settings)
else else
@@ -216,6 +225,13 @@ open class SettingsActivity
hideOrShowLockButton(key) hideOrShowLockButton(key)
} }
fun relaunchCurrentScreen() {
keepCurrentScreen()
startActivity(intent)
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
}
/** /**
* To keep the current screen when activity is reloaded * To keep the current screen when activity is reloaded
*/ */
@@ -235,6 +251,58 @@ open class SettingsActivity
replaceFragment(key, reload) replaceFragment(key, reload)
} }
fun importAppProperties() {
mExternalFileHelper?.openDocument()
}
fun exportAppProperties() {
appPropertiesFileCreationRequestCode = mExternalFileHelper?.createDocument(getString(R.string.app_properties_file_name))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Import app properties result
try {
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { selectedfileUri ->
selectedfileUri?.let { uri ->
val appProperties = Properties()
contentResolver?.openInputStream(uri)?.use { inputStream ->
appProperties.load(inputStream)
}
PreferencesUtil.setAppProperties(this, appProperties)
// Restart the current activity
relaunchCurrentScreen()
Toast.makeText(this, R.string.success_import_app_properties, Toast.LENGTH_LONG).show()
}
}
} catch (e: Exception) {
Toast.makeText(this, R.string.error_import_app_properties, Toast.LENGTH_LONG).show()
Log.e(TAG, "Unable to import app properties", e)
}
// Export app properties result
try {
if (requestCode == appPropertiesFileCreationRequestCode) {
mExternalFileHelper?.onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
createdFileUri?.let { uri ->
contentResolver?.openOutputStream(uri)?.use { outputStream ->
PreferencesUtil
.getAppProperties(this)
.store(outputStream, getString(R.string.description_app_properties))
}
Toast.makeText(this, R.string.success_export_app_properties, Toast.LENGTH_LONG).show()
}
}
appPropertiesFileCreationRequestCode = null
}
} catch (e: Exception) {
Toast.makeText(this, R.string.error_export_app_properties, Toast.LENGTH_LONG).show()
Log.e(LockingActivity.TAG, "Unable to export app properties", e)
}
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
@@ -244,6 +312,8 @@ open class SettingsActivity
companion object { companion object {
private val TAG = SettingsActivity::class.java.name
private const val SHOW_LOCK = "SHOW_LOCK" private const val SHOW_LOCK = "SHOW_LOCK"
private const val TITLE_KEY = "TITLE_KEY" private const val TITLE_KEY = "TITLE_KEY"
private const val TAG_NESTED = "TAG_NESTED" private const val TAG_NESTED = "TAG_NESTED"

View File

@@ -0,0 +1,77 @@
/*
* 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.settings.preference
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import androidx.preference.DialogPreference
import com.kunzisoft.keepass.R
class DurationDialogPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
private var mDuration: Long = 0L
override fun getDialogLayoutResource(): Int {
return R.layout.pref_dialog_duration
}
/**
* Get current duration of preference
*/
fun getDuration(): Long {
return if (mDuration >= 0) mDuration else -1
}
/**
* Assign [duration] of preference
*/
fun setDuration(duration: Long) {
persistString(duration.toString())
notifyChanged()
}
override fun onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any?) {
if (restorePersistedValue) {
mDuration = getPersistedString(mDuration.toString()).toLongOrNull() ?: mDuration
} else {
mDuration = defaultValue?.toString()?.toLongOrNull() ?: mDuration
persistString(mDuration.toString())
}
}
override fun onGetDefaultValue(a: TypedArray?, index: Int): Any {
return try {
a?.getString(index)?.toLongOrNull() ?: mDuration
} catch (e: Exception) {
mDuration
}
}
// Was previously a string
override fun persistString(value: String?): Boolean {
mDuration = value?.toLongOrNull() ?: mDuration
return super.persistString(value)
}
}

View File

@@ -32,7 +32,7 @@ class InputKdfSizePreference @JvmOverloads constructor(context: Context,
override fun setSummary(summary: CharSequence) { override fun setSummary(summary: CharSequence) {
if (summary == UNKNOWN_VALUE_STRING) { if (summary == UNKNOWN_VALUE_STRING) {
super.setSummary("") super.setSummary(summary)
} else { } else {
var summaryString = summary var summaryString = summary
try { try {

View File

@@ -0,0 +1,180 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import android.widget.NumberPicker
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.preference.DurationDialogPreference
class DurationDialogFragmentCompat : InputPreferenceDialogFragmentCompat() {
private var mEnabled = true
private var mDays = 0
private var mHours = 0
private var mMinutes = 0
private var mSeconds = 0
private var daysNumberPicker: NumberPicker? = null
private var hoursNumberPicker: NumberPicker? = null
private var minutesNumberPicker: NumberPicker? = null
private var secondsNumberPicker: NumberPicker? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// To get items from saved instance state
if (savedInstanceState != null
&& savedInstanceState.containsKey(ENABLE_KEY)
&& savedInstanceState.containsKey(DAYS_KEY)
&& savedInstanceState.containsKey(HOURS_KEY)
&& savedInstanceState.containsKey(MINUTES_KEY)
&& savedInstanceState.containsKey(SECONDS_KEY)) {
mEnabled = savedInstanceState.getBoolean(ENABLE_KEY)
mDays = savedInstanceState.getInt(DAYS_KEY)
mHours = savedInstanceState.getInt(HOURS_KEY)
mMinutes = savedInstanceState.getInt(MINUTES_KEY)
mSeconds = savedInstanceState.getInt(SECONDS_KEY)
} else {
val currentPreference = preference
if (currentPreference is DurationDialogPreference) {
durationToDaysHoursMinutesSeconds(currentPreference.getDuration())
}
}
}
private fun durationToDaysHoursMinutesSeconds(duration: Long) {
if (duration < 0) {
mDays = 0
mHours = 0
mMinutes = 0
mSeconds = 0
} else {
mDays = (duration / (24L * 60L * 60L * 1000L)).toInt()
val daysMilliseconds = mDays * 24L * 60L * 60L * 1000L
mHours = ((duration - daysMilliseconds) / (60L * 60L * 1000L)).toInt()
val hoursMilliseconds = mHours * 60L * 60L * 1000L
mMinutes = ((duration - daysMilliseconds - hoursMilliseconds) / (60L * 1000L)).toInt()
val minutesMilliseconds = mMinutes * 60L * 1000L
mSeconds = ((duration - daysMilliseconds - hoursMilliseconds - minutesMilliseconds) / (1000L)).toInt()
}
}
private fun assignValuesInViews() {
daysNumberPicker?.value = mDays
hoursNumberPicker?.value = mHours
minutesNumberPicker?.value = mMinutes
secondsNumberPicker?.value = mSeconds
}
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
daysNumberPicker = view.findViewById<NumberPicker>(R.id.days_picker).apply {
minValue = 0
maxValue = 364
setOnValueChangedListener { _, _, newVal ->
mDays = newVal
activateSwitch()
}
}
hoursNumberPicker = view.findViewById<NumberPicker>(R.id.hours_picker).apply {
minValue = 0
maxValue = 23
setOnValueChangedListener { _, _, newVal ->
mHours = newVal
activateSwitch()
}
}
minutesNumberPicker = view.findViewById<NumberPicker>(R.id.minutes_picker).apply {
minValue = 0
maxValue = 59
setOnValueChangedListener { _, _, newVal ->
mMinutes = newVal
activateSwitch()
}
}
secondsNumberPicker = view.findViewById<NumberPicker>(R.id.seconds_picker).apply {
minValue = 0
maxValue = 59
setOnValueChangedListener { _, _, newVal ->
mSeconds = newVal
activateSwitch()
}
}
setSwitchAction({ isChecked ->
mEnabled = isChecked
}, mDays + mHours + mMinutes + mSeconds > 0)
assignValuesInViews()
}
private fun buildDuration(): Long {
return if (mEnabled) {
mDays * 24L * 60L * 60L * 1000L +
mHours * 60L * 60L * 1000L +
mMinutes * 60L * 1000L +
mSeconds * 1000L
} else {
-1
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(ENABLE_KEY, mEnabled)
outState.putInt(DAYS_KEY, mDays)
outState.putInt(HOURS_KEY, mHours)
outState.putInt(MINUTES_KEY, mMinutes)
outState.putInt(SECONDS_KEY, mSeconds)
}
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
val currentPreference = preference
if (currentPreference is DurationDialogPreference) {
currentPreference.setDuration(buildDuration())
}
}
}
companion object {
private const val ENABLE_KEY = "ENABLE_KEY"
private const val DAYS_KEY = "DAYS_KEY"
private const val HOURS_KEY = "HOURS_KEY"
private const val MINUTES_KEY = "MINUTES_KEY"
private const val SECONDS_KEY = "SECONDS_KEY"
fun newInstance(key: String): DurationDialogFragmentCompat {
val fragment = DurationDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -154,4 +154,14 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
onCheckedChange?.invoke(isChecked) onCheckedChange?.invoke(isChecked)
} }
} }
fun activateSwitch() {
if (switchElementView?.isChecked != true)
switchElementView?.isChecked = true
}
fun deactivateSwitch() {
if (switchElementView?.isChecked == true)
switchElementView?.isChecked = false
}
} }

View File

@@ -67,7 +67,7 @@ class ClipboardHelper(private val context: Context) {
fun getClipboard(context: Context): CharSequence { fun getClipboard(context: Context): CharSequence {
if (getClipboardManager()?.hasPrimaryClip() == true) { if (getClipboardManager()?.hasPrimaryClip() == true) {
val data = getClipboardManager()?.primaryClip val data = getClipboardManager()?.primaryClip
if (data!!.itemCount > 0) { if (data != null && data.itemCount > 0) {
val text = data.getItemAt(0).coerceToText(context) val text = data.getItemAt(0).coerceToText(context)
if (text != null) { if (text != null) {
return text return text

View File

@@ -1,86 +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.utils
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
private var CREATE_FILE_REQUEST_CODE_DEFAULT = 3853
private var fileRequestCodes = ArrayList<Int>()
fun getUnusedCreateFileRequestCode(): Int {
val newCreateFileRequestCode = CREATE_FILE_REQUEST_CODE_DEFAULT++
fileRequestCodes.add(newCreateFileRequestCode)
return newCreateFileRequestCode
}
@SuppressLint("InlinedApi")
fun allowCreateDocumentByStorageAccessFramework(packageManager: PackageManager): Boolean {
return when {
// To check if a custom file manager can manage the ACTION_CREATE_DOCUMENT
Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT -> {
packageManager.queryIntentActivities(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
}, PackageManager.MATCH_DEFAULT_ONLY).isNotEmpty()
}
else -> true
}
}
fun createDocument(activity: FragmentActivity,
titleString: String,
typeString: String = "application/octet-stream"): Int? {
val idCode = getUnusedCreateFileRequestCode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
activity.startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
putExtra(Intent.EXTRA_TITLE, titleString)
}, idCode)
return idCode
} catch (e: Exception) {
FileManagerDialogFragment().show(activity.supportFragmentManager, "browserDialog")
}
} else {
FileManagerDialogFragment().show(activity.supportFragmentManager, "browserDialog")
}
return null
}
fun onCreateDocumentResult(requestCode: Int, resultCode: Int, data: Intent?,
action: (fileCreated: Uri?)->Unit) {
// Retrieve the created URI from the file manager
if (fileRequestCodes.contains(requestCode) && resultCode == Activity.RESULT_OK) {
action.invoke(data?.data)
fileRequestCodes.remove(requestCode)
}
}

View File

@@ -1,5 +1,7 @@
package com.kunzisoft.keepass.utils package com.kunzisoft.keepass.utils
import java.text.Normalizer
object StringUtil { object StringUtil {
fun String.removeLineChars(): String { fun String.removeLineChars(): String {
@@ -10,5 +12,10 @@ object StringUtil {
return this.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "") return this.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "")
} }
fun String.removeDiacriticalMarks(): String {
return "\\p{InCombiningDiacriticalMarks}+".toRegex()
.replace(Normalizer.normalize(this, Normalizer.Form.NFD), "")
}
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) } fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
} }

View File

@@ -38,16 +38,26 @@ object UriUtil {
fun getFileData(context: Context, fileUri: Uri?): DocumentFile? { fun getFileData(context: Context, fileUri: Uri?): DocumentFile? {
if (fileUri == null) if (fileUri == null)
return null return null
return when { return try {
isFileScheme(fileUri) -> { when {
fileUri.path?.let { isFileScheme(fileUri) -> {
File(it).let { file -> fileUri.path?.let {
return DocumentFile.fromFile(file) File(it).let { file ->
return DocumentFile.fromFile(file)
}
} }
} }
isContentScheme(fileUri) -> {
DocumentFile.fromSingleUri(context, fileUri)
}
else -> {
Log.e("FileData", "Content scheme not known")
null
}
} }
isContentScheme(fileUri) -> DocumentFile.fromSingleUri(context, fileUri) } catch (e: Exception) {
else -> null Log.e("FileData", "Unable to get document file", e)
null
} }
} }

View File

@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.search; package com.kunzisoft.keepass.utils;
import java.util.UUID; import java.util.UUID;

View File

@@ -39,7 +39,7 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.search.UuidUtil import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement

View File

@@ -6,9 +6,9 @@ import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.documentfile.provider.DocumentFile
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
class KeyFileSelectionView @JvmOverloads constructor(context: Context, class KeyFileSelectionView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
@@ -51,7 +51,7 @@ class KeyFileSelectionView @JvmOverloads constructor(context: Context,
set(value) { set(value) {
mUri = value mUri = value
keyFileNameView.text = value?.let { keyFileNameView.text = value?.let {
DocumentFile.fromSingleUri(context, value)?.name ?: value.path UriUtil.getFileData(context, value)?.name ?: value.path
} ?: "" } ?: ""
} }
} }

View File

@@ -50,14 +50,18 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
).execute() ).execute()
} }
private fun getDatabaseFilesLoadedValue(): DatabaseFileData {
var newValue = databaseFilesLoaded.value
if (newValue == null) {
newValue = DatabaseFileData()
}
return newValue
}
fun loadListOfDatabases() { fun loadListOfDatabases() {
checkDefaultDatabase() checkDefaultDatabase()
mFileDatabaseHistoryAction?.getDatabaseFileList { databaseFileListRetrieved -> mFileDatabaseHistoryAction?.getDatabaseFileList { databaseFileListRetrieved ->
var newValue = databaseFilesLoaded.value databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
if (newValue == null) {
newValue = DatabaseFileData()
}
newValue.apply {
databaseFileAction = DatabaseFileAction.NONE databaseFileAction = DatabaseFileAction.NONE
databaseFileToActivate = null databaseFileToActivate = null
databaseFileList.apply { databaseFileList.apply {
@@ -65,14 +69,13 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
addAll(databaseFileListRetrieved) addAll(databaseFileListRetrieved)
} }
} }
databaseFilesLoaded.value = newValue
} }
} }
fun addDatabaseFile(databaseUri: Uri, keyFileUri: Uri?) { fun addDatabaseFile(databaseUri: Uri, keyFileUri: Uri?) {
mFileDatabaseHistoryAction?.addOrUpdateDatabaseUri(databaseUri, keyFileUri) { databaseFileAdded -> mFileDatabaseHistoryAction?.addOrUpdateDatabaseUri(databaseUri, keyFileUri) { databaseFileAdded ->
databaseFileAdded?.let { _ -> databaseFileAdded?.let { _ ->
databaseFilesLoaded.value = databaseFilesLoaded.value?.apply { databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
this.databaseFileAction = DatabaseFileAction.ADD this.databaseFileAction = DatabaseFileAction.ADD
this.databaseFileList.add(databaseFileAdded) this.databaseFileList.add(databaseFileAdded)
this.databaseFileToActivate = databaseFileAdded this.databaseFileToActivate = databaseFileAdded
@@ -84,7 +87,7 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
fun updateDatabaseFile(databaseFileToUpdate: DatabaseFile) { fun updateDatabaseFile(databaseFileToUpdate: DatabaseFile) {
mFileDatabaseHistoryAction?.addOrUpdateDatabaseFile(databaseFileToUpdate) { databaseFileUpdated -> mFileDatabaseHistoryAction?.addOrUpdateDatabaseFile(databaseFileToUpdate) { databaseFileUpdated ->
databaseFileUpdated?.let { _ -> databaseFileUpdated?.let { _ ->
databaseFilesLoaded.value = databaseFilesLoaded.value?.apply { databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
this.databaseFileAction = DatabaseFileAction.UPDATE this.databaseFileAction = DatabaseFileAction.UPDATE
this.databaseFileList this.databaseFileList
.find { it.databaseUri == databaseFileUpdated.databaseUri } .find { it.databaseUri == databaseFileUpdated.databaseUri }
@@ -104,7 +107,7 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
fun deleteDatabaseFile(databaseFileToDelete: DatabaseFile) { fun deleteDatabaseFile(databaseFileToDelete: DatabaseFile) {
mFileDatabaseHistoryAction?.deleteDatabaseFile(databaseFileToDelete) { databaseFileDeleted -> mFileDatabaseHistoryAction?.deleteDatabaseFile(databaseFileToDelete) { databaseFileDeleted ->
databaseFileDeleted?.let { _ -> databaseFileDeleted?.let { _ ->
databaseFilesLoaded.value = databaseFilesLoaded.value?.apply { databaseFilesLoaded.value = getDatabaseFilesLoadedValue().apply {
databaseFileAction = DatabaseFileAction.DELETE databaseFileAction = DatabaseFileAction.DELETE
databaseFileToActivate = databaseFileDeleted databaseFileToActivate = databaseFileDeleted
databaseFileList.remove(databaseFileDeleted) databaseFileList.remove(databaseFileDeleted)

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11zM7,10h5v5L7,15z"/>
</vector>

View File

@@ -0,0 +1,149 @@
<?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/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/edit"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/explanation_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:gravity="center"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/explanation_text" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/switch_element">
<LinearLayout
android:id="@+id/duration_days_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toStartOf="@+id/duration_hours_picker"
app:layout_constraintRight_toLeftOf="@+id/duration_hours_picker">
<NumberPicker
android:id="@+id/days_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:src="@drawable/ic_day_white_24dp"
app:tint="?android:attr/textColor"
android:contentDescription="@string/digits" />
</LinearLayout>
<LinearLayout
android:id="@+id/duration_hours_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="HardcodedText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/duration_days_picker"
app:layout_constraintLeft_toRightOf="@+id/duration_days_picker"
app:layout_constraintEnd_toStartOf="@+id/duration_time_picker"
app:layout_constraintRight_toLeftOf="@+id/duration_time_picker">
<NumberPicker
android:id="@+id/hours_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textStyle="bold"
android:text=":" />
</LinearLayout>
<LinearLayout
android:id="@+id/duration_time_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="HardcodedText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/duration_hours_picker"
app:layout_constraintLeft_toRightOf="@+id/duration_hours_picker"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent">
<NumberPicker
android:id="@+id/minutes_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp"
android:text="'"/>
<NumberPicker
android:id="@+id/seconds_picker"
android:scrollbarFadeDuration="0"
android:scrollbarDefaultDelayBeforeFade="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="28sp"
android:text="''"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -137,7 +137,6 @@
<string name="error_string_key">يجب أن يكون لكل سلسلة اسم حقل.</string> <string name="error_string_key">يجب أن يكون لكل سلسلة اسم حقل.</string>
<string name="error_wrong_length">أدخل عددًا صحيحًا موجبًا في حقل «الطول».</string> <string name="error_wrong_length">أدخل عددًا صحيحًا موجبًا في حقل «الطول».</string>
<string name="error_autofill_enable_service">تعذر تمكين خدمة الملء التلقائي.</string> <string name="error_autofill_enable_service">تعذر تمكين خدمة الملء التلقائي.</string>
<string name="error_move_folder_in_itself">لا يمكن نقل مجموعة إلى نفسها.</string>
<string name="file_not_found_content">تعذر إيجاد الملف. جرِّب فتحه من متصفح ملفات.</string> <string name="file_not_found_content">تعذر إيجاد الملف. جرِّب فتحه من متصفح ملفات.</string>
<string name="file_browser">مدير الملفات</string> <string name="file_browser">مدير الملفات</string>
<string name="invalid_credentials">تعذر قراءة الإعتمادات.</string> <string name="invalid_credentials">تعذر قراءة الإعتمادات.</string>

View File

@@ -85,7 +85,6 @@
<string name="error_copy_group_here">Ovde ne možete kopirati grupu.</string> <string name="error_copy_group_here">Ovde ne možete kopirati grupu.</string>
<string name="error_copy_entry_here">Ovde ne možete kopirati unos.</string> <string name="error_copy_entry_here">Ovde ne možete kopirati unos.</string>
<string name="error_move_entry_here">Ovde ne možete premestiti unos.</string> <string name="error_move_entry_here">Ovde ne možete premestiti unos.</string>
<string name="error_move_folder_in_itself">Ne možete premestiti grupu u samu sebe.</string>
<string name="error_autofill_enable_service">Nije moguće omogućiti uslugu automatskog popunjavanja.</string> <string name="error_autofill_enable_service">Nije moguće omogućiti uslugu automatskog popunjavanja.</string>
<string name="error_wrong_length">Unesite pozitivan ceo broj u polje \"Dužina\".</string> <string name="error_wrong_length">Unesite pozitivan ceo broj u polje \"Dužina\".</string>
<string name="error_label_exists">Ova oznaka već postoji.</string> <string name="error_label_exists">Ova oznaka već postoji.</string>

View File

@@ -19,4 +19,7 @@
<string name="add_entry">এন্টরি যোগ করুন</string> <string name="add_entry">এন্টরি যোগ করুন</string>
<string name="accept">গ্রহণ</string> <string name="accept">গ্রহণ</string>
<string name="about_description">কিপাস পাসওয়ার্ড ম্যানেজারের অ্যান্ড্রয়েড বাস্তবায়ন</string> <string name="about_description">কিপাস পাসওয়ার্ড ম্যানেজারের অ্যান্ড্রয়েড বাস্তবায়ন</string>
<string name="clipboard_error_title">ক্লিপবোর্ড ত্রুটি</string>
<string name="allow">অনুমোদন</string>
<string name="file_manager_install_description">ACTION_CREATE_DOCUMENT এবং ACTION_OPEN_DOCUMENT অভিপ্রায় গ্রহণ করে এমন একটি ফাইল ম্যানেজার ডাটাবেস ফাইলগুলো তৈরি করা, খোলা এবং সংরক্ষণ করতে প্রয়োজন।</string>
</resources> </resources>

View File

@@ -19,21 +19,21 @@
Catalan translation by Oriol Garrote Catalan translation by Oriol Garrote
--><resources> --><resources>
<string name="feedback">Comentaris</string> <string name="feedback">Comentaris</string>
<string name="homepage">Pàgina inici:</string> <string name="homepage">Pàgina d\'inici</string>
<string name="about_description">KeePassDX és una implementació per a Android de KeePass password manager</string> <string name="about_description">KeePassDX és una implementació per a Android de KeePass password manager</string>
<string name="accept">Accepta</string> <string name="accept">Accepta</string>
<string name="add_entry">Afegeix entrada</string> <string name="add_entry">Afegeix entrada</string>
<string name="add_group">Afegeix grup</string> <string name="add_group">Afegeix grup</string>
<string name="encryption_algorithm">Algorisme de xifratge</string> <string name="encryption_algorithm">Algoritme de xifrat</string>
<string name="app_timeout">Temps d\'espera de l\'aplicació</string> <string name="app_timeout">Temps d\'espera de l\'aplicació</string>
<string name="app_timeout_summary">Temps d\'inactivitat abans de blocar la base de dades</string> <string name="app_timeout_summary">Temps d\'inactivitat abans de blocar la base de dades</string>
<string name="application">Aplicació</string> <string name="application">Aplicació</string>
<string name="menu_app_settings">Paràmetres de l\'aplicació</string> <string name="menu_app_settings">Configuració de l\'aplicació</string>
<string name="brackets">Parèntesis</string> <string name="brackets">Parèntesis</string>
<string name="file_manager_install_description">L\'exploració d\'arxius necessita l\'aplicació Open Intents File Manager, clica a sota per instal·lar-la. Degut a peculiaritats de l\'explorador d\'arxius pot ser que no funcioni correctament la primera execució.</string> <string name="file_manager_install_description">L\'exploració d\'arxius necessita l\'aplicació Open Intents File Manager, clica a sota per instal·lar-la. Degut a peculiaritats de l\'explorador d\'arxius pot ser que no funcioni correctament la primera execució.</string>
<string name="clipboard_cleared">Porta-retalls netejat.</string> <string name="clipboard_cleared">Porta-retalls netejat.</string>
<string name="clipboard_timeout">Temps d\'espera del porta-retalls</string> <string name="clipboard_timeout">Temps d\'espera del porta-retalls</string>
<string name="clipboard_timeout_summary">Temps abans de netejar el porta-retalls</string> <string name="clipboard_timeout_summary">Temps abans de netejar el porta-retalls (si el teu dispositiu ho suporta)</string>
<string name="select_to_copy">Selecciona per copiar %1$s al porta-retalls</string> <string name="select_to_copy">Selecciona per copiar %1$s al porta-retalls</string>
<string name="retrieving_db_key">Creant clau de base de dades…</string> <string name="retrieving_db_key">Creant clau de base de dades…</string>
<string name="database">Base de dades</string> <string name="database">Base de dades</string>
@@ -115,24 +115,13 @@
<string name="search_label">Cerca</string> <string name="search_label">Cerca</string>
<string name="sort_db">Ordre natural</string> <string name="sort_db">Ordre natural</string>
<string name="special">Especial</string> <string name="special">Especial</string>
<string name="search">Títol/descripció d\'entrada</string> <string name="search">Cercar</string>
<string name="search_results">Resultats de cerca</string> <string name="search_results">Resultats de cerca</string>
<string name="underline">Subratllat</string> <string name="underline">Subratllat</string>
<string name="unsupported_db_version">Versió de la base de dades no suportada.</string> <string name="unsupported_db_version">Versió de la base de dades no suportada.</string>
<string name="uppercase">Majúscules</string> <string name="uppercase">Majúscules</string>
<string name="version_label">Versió %1$s</string> <string name="version_label">Versió %1$s</string>
<string name="education_unlock_summary">Introdueix una contrasenya i/o un arxiu clau per desbloquejar la base de dades.</string> <string name="education_unlock_summary">Introdueix una contrasenya i/o un arxiu clau per desbloquejar la base de dades.</string>
<string-array name="timeout_options">
<item>5 segons</item>
<item>10 segons</item>
<item>20 segons</item>
<item>30 segons</item>
<item>1 minut</item>
<item>5 minuts</item>
<item>15 minuts</item>
<item>30 minuts</item>
<item>Mai</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Petita</item> <item>Petita</item>
<item>Mitjana</item> <item>Mitjana</item>
@@ -144,7 +133,7 @@
<string name="edit_entry">Edita l\'entrada</string> <string name="edit_entry">Edita l\'entrada</string>
<string name="contribution">Contribució</string> <string name="contribution">Contribució</string>
<string name="contact">Contacte</string> <string name="contact">Contacte</string>
<string name="extended_ASCII">ASCII ampliat</string> <string name="extended_ASCII">ASCII estès</string>
<string name="sort_username">Nom d\'usuari</string> <string name="sort_username">Nom d\'usuari</string>
<string name="sort_title">Títol</string> <string name="sort_title">Títol</string>
<string name="sort_menu">Ordena</string> <string name="sort_menu">Ordena</string>
@@ -199,8 +188,8 @@
<string name="sort_last_access_time">Accés</string> <string name="sort_last_access_time">Accés</string>
<string name="sort_last_modify_time">Modificació</string> <string name="sort_last_modify_time">Modificació</string>
<string name="sort_creation_time">Creació</string> <string name="sort_creation_time">Creació</string>
<string name="sort_recycle_bin_bottom">Paperera a baix</string> <string name="sort_recycle_bin_bottom">La paperera de reciclatge es troba a la part inferior</string>
<string name="sort_groups_before">Primer els grups</string> <string name="sort_groups_before">Grups primer</string>
<string name="sort_ascending">El menor primer ↓</string> <string name="sort_ascending">El menor primer ↓</string>
<string name="command_execution">S\'executa l\'ordre…</string> <string name="command_execution">S\'executa l\'ordre…</string>
<string name="parallelism_explanation">Grau de paral·lelisme (és a dir, nombre de fils) fets servir per la funció de derivació de la clau.</string> <string name="parallelism_explanation">Grau de paral·lelisme (és a dir, nombre de fils) fets servir per la funció de derivació de la clau.</string>
@@ -228,21 +217,21 @@
<string name="menu_open_file_read_and_write">Modificable</string> <string name="menu_open_file_read_and_write">Modificable</string>
<string name="menu_file_selection_read_only">Protegit contra escriptura</string> <string name="menu_file_selection_read_only">Protegit contra escriptura</string>
<string name="menu_save_database">Desa la base de dades</string> <string name="menu_save_database">Desa la base de dades</string>
<string name="menu_cancel">Cancel·la</string> <string name="menu_cancel">Cancel·lar</string>
<string name="menu_paste">Enganxa</string> <string name="menu_paste">Enganxar</string>
<string name="menu_move">Mou</string> <string name="menu_move">Moure</string>
<string name="menu_copy">Copia</string> <string name="menu_copy">Copiar</string>
<string name="menu_master_key_settings">Paràmetres de la contrasenya mestra</string> <string name="menu_master_key_settings">Paràmetres de la contrasenya mestra</string>
<string name="menu_security_settings">Paràmetres de seguretat</string> <string name="menu_security_settings">Paràmetres de seguretat</string>
<string name="menu_advanced_unlock_settings">Desblocatge avançat</string> <string name="menu_advanced_unlock_settings">Desblocatge avançat</string>
<string name="menu_form_filling_settings">Reompliment de formularis</string> <string name="menu_form_filling_settings">Emplenat de formularis</string>
<string name="copy_field">Còpia de %1$s</string> <string name="copy_field">Còpia de %1$s</string>
<string name="creating_database">Es crea la base de dades…</string> <string name="creating_database">Es crea la base de dades…</string>
<string name="list_groups_show_number_entries_summary">Mostra el nombre d\'entrades en un grup</string> <string name="list_groups_show_number_entries_summary">Mostra el nombre d\'entrades en un grup</string>
<string name="list_groups_show_number_entries_title">Mostra el nombre d\'entades</string> <string name="list_groups_show_number_entries_title">Mostra el nombre d\'entades</string>
<string name="list_entries_show_username_summary">Mostra els noms d\'usuari en les llistes d\'entrades</string> <string name="list_entries_show_username_summary">Mostra els noms d\'usuari en les llistes d\'entrades</string>
<string name="list_entries_show_username_title">Mostra noms d\'usuari</string> <string name="list_entries_show_username_title">Mostra noms d\'usuari</string>
<string name="keyfile_is_empty">El fitxer de claus és buit.</string> <string name="keyfile_is_empty">El fitxer de clau és buit.</string>
<string name="invalid_algorithm">Algorisme incorrecte.</string> <string name="invalid_algorithm">Algorisme incorrecte.</string>
<string name="file_not_found_content">No s\'ha trobat el fitxer. Mireu de reobrir-lo des de l\'explorador de fitxers.</string> <string name="file_not_found_content">No s\'ha trobat el fitxer. Mireu de reobrir-lo des de l\'explorador de fitxers.</string>
<string name="field_value">Valor del camp</string> <string name="field_value">Valor del camp</string>
@@ -290,5 +279,14 @@
<string name="content_description_background">Fons</string> <string name="content_description_background">Fons</string>
<string name="clipboard_error_clear">No ha estat possible netejar el porta-retalls</string> <string name="clipboard_error_clear">No ha estat possible netejar el porta-retalls</string>
<string name="clipboard_error">Alguns dispositius no permeten que les aplicacions facin servir el porta-retalls.</string> <string name="clipboard_error">Alguns dispositius no permeten que les aplicacions facin servir el porta-retalls.</string>
<string name="clipboard_error_title">Error del porta-retalls</string> <string name="clipboard_error_title">Error del Porta Retalls</string>
<string name="error_string_key">Cada cadena ha de tenir un nom de camp.</string>
<string name="error_rebuild_list">La llista no s\'ha pogut reconstruir correctament.</string>
<string name="error_database_uri_null">No es pot recuperar l\'URI de la base de dades.</string>
<string name="error_field_name_already_exists">El nom del camp ja existeix.</string>
<string name="error_registration_read_only">No es permet desar un element nou en una base de dades de només lectura</string>
<string name="error_string_type">Aquest text no coincideix amb l\'element sol·licitat.</string>
<string name="error_otp_type">L\'OTP existent no està reconegut per aquest formulari, la seva validació ja no pot generar correctament el token.</string>
<string name="error_create_database_file">No s\'ha pogut crear una base de dades amb aquesta contrasenya i arxiu de clau.</string>
<string name="error_autofill_enable_service">No s\'ha pogut habilitar el servei d\'autocompletat.</string>
</resources> </resources>

View File

@@ -39,7 +39,7 @@
<string name="select_to_copy">Vyberte zkopírovat %1$s do schránky</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ám klíč databáze…</string>
<string name="database">Databáze</string> <string name="database">Databáze</string>
<string name="decrypting_db">Dešifruji obsah databáze…</string> <string name="decrypting_db">Dešifrování obsahu databáze…</string>
<string name="default_checkbox">Použít jako výchozí databázi</string> <string name="default_checkbox">Použít jako výchozí databázi</string>
<string name="digits">Číslice</string> <string name="digits">Číslice</string>
<string name="select_database_file">Otevřít existující databázi</string> <string name="select_database_file">Otevřít existující databázi</string>
@@ -88,7 +88,7 @@
<string name="length">Délka</string> <string name="length">Délka</string>
<string name="list_size_title">Velikost položek seznamu</string> <string name="list_size_title">Velikost položek seznamu</string>
<string name="list_size_summary">Velikost textu v seznamu prvků</string> <string name="list_size_summary">Velikost textu v seznamu prvků</string>
<string name="loading_database">Načítám databázi</string> <string name="loading_database">Načítá databáze</string>
<string name="lowercase">Malá písmena</string> <string name="lowercase">Malá písmena</string>
<string name="hide_password_title">Skrýt hesla</string> <string name="hide_password_title">Skrýt hesla</string>
<string name="hide_password_summary">Ve výchozím stavu zobrazit (***) místo hesla</string> <string name="hide_password_summary">Ve výchozím stavu zobrazit (***) místo hesla</string>
@@ -111,7 +111,7 @@
<string name="no_url_handler">Pro otevření tohoto URL nainstalujte webový prohlížeč.</string> <string name="no_url_handler">Pro otevření tohoto URL nainstalujte webový prohlížeč.</string>
<string name="omit_backup_search_title">Neprohledávat položky v záloze</string> <string name="omit_backup_search_title">Neprohledávat položky v záloze</string>
<string name="omit_backup_search_summary">Vynechat skupiny „Záloha“ a \"Koš\" z výsledků vyhledávání</string> <string name="omit_backup_search_summary">Vynechat skupiny „Záloha“ a \"Koš\" z výsledků vyhledávání</string>
<string name="progress_create">Zakládám novou databázi</string> <string name="progress_create">Zakládá nové databáze</string>
<string name="progress_title">Pracuji…</string> <string name="progress_title">Pracuji…</string>
<string name="protection">Ochrana</string> <string name="protection">Ochrana</string>
<string name="read_only_warning">Ke změně v databáze potřebuje KeePassDX oprávnění pro zápis.</string> <string name="read_only_warning">Ke změně v databáze potřebuje KeePassDX oprávnění pro zápis.</string>
@@ -119,8 +119,8 @@
<string name="root">Kořen</string> <string name="root">Kořen</string>
<string name="rounds">Transformační průchody</string> <string name="rounds">Transformační průchody</string>
<string name="rounds_explanation">Vyšší počet šifrovacích průchodů zvýší odolnost proti útoku zkoušením všech možných hesel, ale může výrazně zpomalit načítání a ukládání.</string> <string name="rounds_explanation">Vyšší počet šifrovacích průchodů zvýší odolnost proti útoku zkoušením všech možných hesel, ale může výrazně zpomalit načítání a ukládání.</string>
<string name="saving_database">Ukládám databázi</string> <string name="saving_database">Ukládá databáze</string>
<string name="space">Místo</string> <string name="space">Mezera</string>
<string name="search_label">Hledat</string> <string name="search_label">Hledat</string>
<string name="sort_db">Přirozené řazení</string> <string name="sort_db">Přirozené řazení</string>
<string name="special">Speciální</string> <string name="special">Speciální</string>
@@ -133,17 +133,6 @@
<string name="education_unlock_summary">Databázi odemknete zadáním hesla a/nebo souboru s klíčem. <string name="education_unlock_summary">Databázi odemknete zadáním hesla a/nebo souboru s klíčem.
\n \n
\nNezapomeňte po každé úpravě zálohovat kopii svého .kdbx souboru na bezpečné místo.</string> \nNezapomeňte po každé úpravě zálohovat kopii svého .kdbx souboru na bezpečné místo.</string>
<string-array name="timeout_options">
<item>5 sekund</item>
<item>10 sekund</item>
<item>20 sekund</item>
<item>30 sekund</item>
<item>1 minuta</item>
<item>5 minut</item>
<item>15 minut</item>
<item>30 minut</item>
<item>Nikdy</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Malý</item> <item>Malý</item>
<item>Střední</item> <item>Střední</item>
@@ -157,7 +146,6 @@
<string name="error_load_database">Databázi se nepodařilo načíst.</string> <string name="error_load_database">Databázi se nepodařilo načíst.</string>
<string name="error_load_database_KDF_memory">Klíč se nepodařilo načíst, zkuste snížit \"využití paměti\" pro KDF.</string> <string name="error_load_database_KDF_memory">Klíč se nepodařilo načíst, zkuste snížit \"využití paměti\" pro KDF.</string>
<string name="error_autofill_enable_service">Službu automatického vyplňování se nepodařilo zapnout.</string> <string name="error_autofill_enable_service">Službu automatického vyplňování se nepodařilo zapnout.</string>
<string name="error_move_folder_in_itself">Není možné přesunout skupinu do ní samotné.</string>
<string name="file_not_found_content">Soubor nenalezen. Zkuste jej otevřít ze správce souborů.</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_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">V seznamech záznamů zobrazit uživatelská jména</string>
@@ -374,7 +362,7 @@
<string name="error_otp_period">Interval musít být mezi %1$d a %2$d vteřinami.</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> <string name="error_otp_digits">Token musí obsahovat mezi %1$d a %2$d číslicemi.</string>
<string name="invalid_db_same_uuid">%1$s s totožným UUID %2$s již existuje.</string> <string name="invalid_db_same_uuid">%1$s s totožným UUID %2$s již existuje.</string>
<string name="creating_database">Zakládám databázi</string> <string name="creating_database">Zakládá databáze</string>
<string name="menu_security_settings">Nastavení zabezpečení</string> <string name="menu_security_settings">Nastavení zabezpečení</string>
<string name="menu_master_key_settings">Nastavení hlavního klíče</string> <string name="menu_master_key_settings">Nastavení hlavního klíče</string>
<string name="contains_duplicate_uuid">Databáze obsahuje duplikátní UUID.</string> <string name="contains_duplicate_uuid">Databáze obsahuje duplikátní UUID.</string>
@@ -403,11 +391,11 @@
<string name="error_save_database">Nebylo možno uložit databázi.</string> <string name="error_save_database">Nebylo možno uložit databázi.</string>
<string name="menu_save_database">Uložit databázi</string> <string name="menu_save_database">Uložit databázi</string>
<string name="menu_empty_recycle_bin">Vysypat koš</string> <string name="menu_empty_recycle_bin">Vysypat koš</string>
<string name="command_execution">Provádím příkaz…</string> <string name="command_execution">Provádění příkazu</string>
<string name="warning_permanently_delete_nodes">Natrvalo smazat vybrané uzly\?</string> <string name="warning_permanently_delete_nodes">Natrvalo smazat vybrané uzly\?</string>
<string name="keystore_not_accessible">Úložiště klíčů není řádně inicializováno.</string> <string name="keystore_not_accessible">Úložiště klíčů není řádně inicializováno.</string>
<string name="recycle_bin_group_title">Název skupiny</string> <string name="recycle_bin_group_title">Název skupiny</string>
<string name="enable_auto_save_database_title">Uložit databázi automaticky</string> <string name="enable_auto_save_database_title">Automatické ukládání</string>
<string name="enable_auto_save_database_summary">Uložit databázi po každé důležité akci (v režimu \"Zápis\")</string> <string name="enable_auto_save_database_summary">Uložit databázi po každé důležité akci (v režimu \"Zápis\")</string>
<string name="entry_attachments">Přílohy</string> <string name="entry_attachments">Přílohy</string>
<string name="menu_restore_entry_history">Obnovit historii</string> <string name="menu_restore_entry_history">Obnovit historii</string>
@@ -415,9 +403,9 @@
<string name="keyboard_auto_go_action_title">Akce auto-klávesy</string> <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="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_attachment">Stáhnout %1$s</string>
<string name="download_initialization">Zahajuji</string> <string name="download_initialization">Zahájení</string>
<string name="download_progression">Probíhá: %1$d%%</string> <string name="download_progression">Probíhá: %1$d%%</string>
<string name="download_finalization">Dokončuji</string> <string name="download_finalization">Dokončování</string>
<string name="download_complete">Kompletní!</string> <string name="download_complete">Kompletní!</string>
<string name="hide_expired_entries_title">Skrýt propadlé záznamy</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="hide_expired_entries_summary">Propadlé záznamy nebudou ukázány</string>
@@ -437,7 +425,7 @@
<string name="warning_database_read_only">Udělit právo zápisu pro uložení změn v databázi</string> <string name="warning_database_read_only">Udělit právo zápisu pro uložení změn v databázi</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft je <strong>open source</strong> a <strong>bez reklam</strong>. <string name="html_about_licence">KeePassDX © %1$d Kunzisoft je <strong>open source</strong> a <strong>bez reklam</strong>.
\nJe poskytován jak je, od licencí <strong>GPLv3</strong>, bez jakékoli záruky.</string> \nJe poskytován jak je, od licencí <strong>GPLv3</strong>, bez jakékoli záruky.</string>
<string name="html_about_contribution">Abychom si &lt;strong&gt;udrželi svoji svobodu&lt;/strong&gt;, &lt;strong&gt;opravili chyby&lt;/strong&gt;,&lt;strong&gt;doplnili funkce&lt;/strong&gt; a &lt;strong&gt;byli vždy aktivní&lt;/strong&gt;, počítáme s Vaším &lt;strong&gt;přispěním&lt;/strong&gt;.</string> <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="error_create_database">Nepodařilo se vytvořit soubor databáze.</string>
<string name="entry_add_attachment">Přidat přílohu</string> <string name="entry_add_attachment">Přidat přílohu</string>
<string name="discard">Zavrhnout</string> <string name="discard">Zavrhnout</string>
@@ -523,11 +511,11 @@
<string name="advanced_unlock_not_recognized">Otisk pro rozšířené odemknutí nebyl rozpoznán</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">Nelze načíst klíč rozšířeného odemknutí. Prosím, smažte jej 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_message">Načíst důvěrný údaj pomocí dat rozšířeného odemknutí</string>
<string name="advanced_unlock_prompt_extract_credential_title">Otevřít databázi pomocí rozpoznání 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> <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>
<string name="advanced_unlock_prompt_store_credential_title">Rozpoznání rozšířeného odemknutí</string> <string name="advanced_unlock_prompt_store_credential_title">Rozpoznání rozšířeného odemknutí</string>
<string name="open_advanced_unlock_prompt_store_credential">Pro uložení důvěrných údajů otevřete pobídku rozšířeného odemknutí</string> <string name="open_advanced_unlock_prompt_store_credential">Pro uložení důvěrných údajů otevřete pobídku rozšířeného odemknutí</string>
<string name="open_advanced_unlock_prompt_unlock_database">Pro odemknutí databáze otevřete pobídku rozšířeného odemknutí</string> <string name="open_advanced_unlock_prompt_unlock_database">Databázi otevřete i pomocí nabídky rozšířeného odemykání</string>
<string name="menu_keystore_remove_key">Smazat klíč rozšířeného odemknutí</string> <string name="menu_keystore_remove_key">Smazat klíč rozšířeného odemknutí</string>
<string name="education_advanced_unlock_title">Rozšířené odemknutí databáze</string> <string name="education_advanced_unlock_title">Rozšířené odemknutí databáze</string>
<string name="advanced_unlock_timeout">Časový limit rozšířeného odemknutí</string> <string name="advanced_unlock_timeout">Časový limit rozšířeného odemknutí</string>

View File

@@ -24,7 +24,7 @@
<string name="add_entry">Tilføj post</string> <string name="add_entry">Tilføj post</string>
<string name="add_group">Tilføj gruppe</string> <string name="add_group">Tilføj gruppe</string>
<string name="encryption_algorithm">Krypteringsalgoritme</string> <string name="encryption_algorithm">Krypteringsalgoritme</string>
<string name="app_timeout">Timeout</string> <string name="app_timeout">Tid udløbet</string>
<string name="app_timeout_summary">Inaktiv tid, før databasen låses</string> <string name="app_timeout_summary">Inaktiv tid, før databasen låses</string>
<string name="application">Program</string> <string name="application">Program</string>
<string name="menu_app_settings">Indstillinger</string> <string name="menu_app_settings">Indstillinger</string>
@@ -132,17 +132,6 @@
<string name="education_unlock_summary">Angiv en adgangskode og/eller en nøglefil til at låse databasen op. <string name="education_unlock_summary">Angiv en adgangskode og/eller en nøglefil til at låse databasen op.
\n \n
\nHusk at gemme en kopi af .kdbx filen i et sikkert sted efter hver ændring.</string> \nHusk at gemme en kopi af .kdbx filen i et sikkert sted efter hver ændring.</string>
<string-array name="timeout_options">
<item>5 sekunder</item>
<item>10 sekunder</item>
<item>20 sekunder</item>
<item>30 sekunder</item>
<item>1 minut</item>
<item>5 minutter</item>
<item>15 minutter</item>
<item>30 minutter</item>
<item>Aldrig</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Lille</item> <item>Lille</item>
<item>Mellem</item> <item>Mellem</item>
@@ -156,7 +145,6 @@
<string name="error_load_database">Databasen kunne ikke indlæses.</string> <string name="error_load_database">Databasen kunne ikke indlæses.</string>
<string name="error_load_database_KDF_memory">Kunne ikke indlæse nøglen. Prøv at reducere KDF \"hukommelsesforbrug\".</string> <string name="error_load_database_KDF_memory">Kunne ikke indlæse nøglen. Prøv at reducere KDF \"hukommelsesforbrug\".</string>
<string name="error_autofill_enable_service">Kunne ikke aktivere autofyld tjenesten.</string> <string name="error_autofill_enable_service">Kunne ikke aktivere autofyld tjenesten.</string>
<string name="error_move_folder_in_itself">Kan ikke flytte en gruppe til sig selv.</string>
<string name="file_not_found_content">Kunne ikke finde filen. Prøv at åbne den fra filhåndtering.</string> <string name="file_not_found_content">Kunne ikke finde filen. Prøv at åbne den fra filhåndtering.</string>
<string name="list_entries_show_username_title">Vis brugernavne</string> <string name="list_entries_show_username_title">Vis brugernavne</string>
<string name="list_entries_show_username_summary">Vis brugernavne i postlister</string> <string name="list_entries_show_username_summary">Vis brugernavne i postlister</string>
@@ -232,7 +220,7 @@
<string name="database_description_title">Database beskrivelse</string> <string name="database_description_title">Database beskrivelse</string>
<string name="database_version_title">Databaseversion</string> <string name="database_version_title">Databaseversion</string>
<string name="text_appearance">Tekst</string> <string name="text_appearance">Tekst</string>
<string name="application_appearance">Program</string> <string name="application_appearance">Brugerflade</string>
<string name="other">Øvrige</string> <string name="other">Øvrige</string>
<string name="keyboard">Tastatur</string> <string name="keyboard">Tastatur</string>
<string name="magic_keyboard_title">Magikeyboard</string> <string name="magic_keyboard_title">Magikeyboard</string>
@@ -281,14 +269,14 @@
<string name="html_text_ad_free">I modsætning til andre programmer til adgangskodeadministration er denne &lt;strong&gt;annoncefri&lt;/strong&gt;, &lt;strong&gt;copyleft fri software&lt;/strong&gt;, og indsamler ikke personlige data, uanset hvilken version der bruges.</string> <string name="html_text_ad_free">I modsætning til andre programmer til adgangskodeadministration er denne &lt;strong&gt;annoncefri&lt;/strong&gt;, &lt;strong&gt;copyleft fri software&lt;/strong&gt;, og indsamler ikke personlige data, uanset hvilken version der bruges.</string>
<string name="html_text_buy_pro">Ved at købe pro-versionen, er der adgang til &lt;strong&gt;visuel stil&lt;/strong&gt;, og det vil især hjælpe &lt;strong&gt;gennemførelsen af lokale projekter.&lt;/strong&gt;</string> <string name="html_text_buy_pro">Ved at købe pro-versionen, er der adgang til &lt;strong&gt;visuel stil&lt;/strong&gt;, og det vil især hjælpe &lt;strong&gt;gennemførelsen af lokale projekter.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Denne &lt;strong&gt;visuelle stil&lt;/strong&gt; er tilgængelige takket være bidrag.</string> <string name="html_text_feature_generosity">Denne &lt;strong&gt;visuelle stil&lt;/strong&gt; er tilgængelige takket være bidrag.</string>
<string name="html_text_donation">For at bevare uafhængighed og altid at være aktiv, regner vi med &lt;strong&gt;bidrag.&lt;/strong&gt;</string> <string name="html_text_donation">For at bevare uafhængighed og altid at være aktiv, håber vi på dit <strong>bidrag.</strong></string>
<string name="html_text_dev_feature">Funktionen er &lt;strong&gt;under udvikling&lt;/strong&gt;, og det kræver &lt;strong&gt;bidrag&lt;/strong&gt;, for snart at være tilgængelig.</string> <string name="html_text_dev_feature">Funktionen er &lt;strong&gt;under udvikling&lt;/strong&gt;, og det kræver &lt;strong&gt;bidrag&lt;/strong&gt;, for snart at være tilgængelig.</string>
<string name="html_text_dev_feature_buy_pro">Ved at købe &lt;strong&gt;pro&lt;/strong&gt; versionen,</string> <string name="html_text_dev_feature_buy_pro">Ved at købe &lt;strong&gt;pro&lt;/strong&gt; versionen,</string>
<string name="html_text_dev_feature_contibute">Ved at &lt;strong&gt;bidrage&lt;/strong&gt;,</string> <string name="html_text_dev_feature_contibute">Ved at &lt;strong&gt;bidrage&lt;/strong&gt;,</string>
<string name="html_text_dev_feature_encourage">tilskyndes udviklerne til at lave &lt;strong&gt;nye funktioner&lt;/strong&gt; og &lt;strong&gt;rette fejl&lt;/strong&gt; i henhold bemærkninger.</string> <string name="html_text_dev_feature_encourage">tilskyndes udviklerne til at lave &lt;strong&gt;nye funktioner&lt;/strong&gt; og &lt;strong&gt;rette fejl&lt;/strong&gt; i henhold bemærkninger.</string>
<string name="html_text_dev_feature_thanks">Tak for bidrag.</string> <string name="html_text_dev_feature_thanks">Tak for bidrag.</string>
<string name="html_text_dev_feature_work_hard">Vi arbejder hårdt på hurtigt at frigive denne funktion.</string> <string name="html_text_dev_feature_work_hard">Vi arbejder hårdt på hurtigt at frigive denne funktion.</string>
<string name="html_text_dev_feature_upgrade">Glem ikke at holde appen opdateret ved at installere nye versioner.</string> <string name="html_text_dev_feature_upgrade">Husk at holde din app opdateret ved at installere den nyeste version.</string>
<string name="download">Hent</string> <string name="download">Hent</string>
<string name="contribute">Bidrag</string> <string name="contribute">Bidrag</string>
<string name="style_choose_title">Tema</string> <string name="style_choose_title">Tema</string>
@@ -352,7 +340,7 @@
<string name="menu_advanced_unlock_settings">Avanceret oplåsning</string> <string name="menu_advanced_unlock_settings">Avanceret oplåsning</string>
<string name="biometric">Biometrisk</string> <string name="biometric">Biometrisk</string>
<string name="biometric_auto_open_prompt_title">Åbn automatisk biometrisk prompt</string> <string name="biometric_auto_open_prompt_title">Åbn automatisk biometrisk prompt</string>
<string name="biometric_auto_open_prompt_summary">Spørg automatisk efter biometri, hvis databasen er konfigureret til at bruge den</string> <string name="biometric_auto_open_prompt_summary">Spørg automatisk efter biometrisk oplåsning, hvis databasen er konfigureret til at bruge det</string>
<string name="enable">Aktiver</string> <string name="enable">Aktiver</string>
<string name="disable">Deaktiver</string> <string name="disable">Deaktiver</string>
<string name="master_key">Hovednøgle</string> <string name="master_key">Hovednøgle</string>
@@ -438,7 +426,7 @@
<string name="hide_broken_locations_title">Skjule brudte databaselinks</string> <string name="hide_broken_locations_title">Skjule brudte databaselinks</string>
<string name="hide_broken_locations_summary">Skjul brudte links på listen over seneste databaser</string> <string name="hide_broken_locations_summary">Skjul brudte links på listen over seneste databaser</string>
<string name="warning_database_read_only">Giv fil skriveadgang for at gemme databasændringer</string> <string name="warning_database_read_only">Giv fil skriveadgang for at gemme databasændringer</string>
<string name="education_setup_OTP_summary">Opsætning af engangsadgangskodestyring (HOTP / TOTP) for at generere et token anmodet om tofaktorautentisering (2FA).</string> <string name="education_setup_OTP_summary">Opsætning af engangs-adgangskode-styring (HOTP / TOTP) for at generere et token anmodet af tofaktor-autentisering (2FA).</string>
<string name="education_setup_OTP_title">Opsætning af OTP</string> <string name="education_setup_OTP_title">Opsætning af OTP</string>
<string name="error_create_database">Databasefilen kunne ikke oprettes.</string> <string name="error_create_database">Databasefilen kunne ikke oprettes.</string>
<string name="entry_add_attachment">Tilføj vedhæng</string> <string name="entry_add_attachment">Tilføj vedhæng</string>
@@ -508,4 +496,69 @@
<string name="autofill_save_search_info_summary">Forsøger at gemme delte oplysninger, når der foretages et manuelt indtastningsvalg</string> <string name="autofill_save_search_info_summary">Forsøger at gemme delte oplysninger, når der foretages et manuelt indtastningsvalg</string>
<string name="keyboard_save_search_info_summary">Forsøger at gemme delte oplysninger, når der foretages et manuelt indtastningsvalg</string> <string name="keyboard_save_search_info_summary">Forsøger at gemme delte oplysninger, når der foretages et manuelt indtastningsvalg</string>
<string name="error_field_name_already_exists">Feltnavnet findes allerede.</string> <string name="error_field_name_already_exists">Feltnavnet findes allerede.</string>
<string name="icon_section_custom">Brugerdefineret</string>
<string name="icon_section_standard">Standard</string>
<string name="style_brightness_summary">Vælg lys eller mørk tema</string>
<string name="style_brightness_title">Temalysstyrke</string>
<string name="unit_gibibyte">GiB</string>
<string name="unit_mebibyte">MiB</string>
<string name="unit_kibibyte">KiB</string>
<string name="unit_byte">B</string>
<string name="download_canceled">Annulleret!</string>
<string name="education_advanced_unlock_title">Avanceret database-oplåsning</string>
<string name="autofill_inline_suggestions_keyboard">Forslag til autofyld tilføjet.</string>
<string name="select_entry">Vælg post</string>
<string name="back_to_previous_keyboard">Tilbage til forrige tastatur</string>
<string name="custom_fields">Brugerdefinerede felter</string>
<string name="advanced_unlock_delete_all_key_warning">Slet alle krypteringsnøgler relateret til avanceret oplåsningsgenkendelse\?</string>
<string name="advanced_unlock_tap_delete">Tryk for at slette avancerede oplåsningstaster</string>
<string name="content">Indhold</string>
<string name="credential_before_click_advanced_unlock_button">Indtast adgangskoden, og klik derefter på denne knap.</string>
<string name="advanced_unlock_prompt_not_initialized">Kunne ikke initialisere avanceret oplåsningsprompt.</string>
<string name="advanced_unlock_scanning_error">Fejl ved avanceret oplåsning: %1$s</string>
<string name="advanced_unlock_not_recognized">Kunne ikke genkende avanceret oplåsning</string>
<string name="advanced_unlock_invalid_key">Den avancerede oplåsningsnøgle kan ikke læses. Slet den, og gentag proceduren for genkendelse af oplåsning.</string>
<string name="advanced_unlock_prompt_extract_credential_title">Åbn database med avanceret oplåsningsgenkendelse</string>
<string name="advanced_unlock_prompt_store_credential_title">Avanceret oplåsningsgenkendelse</string>
<string name="warning_database_info_changed_options">Overskriv de eksterne ændringer ved at gemme databasen eller genindlæse den med de seneste ændringer.</string>
<string name="warning_database_info_changed">Oplysningerne i databasefilen er blevet ændret uden for appen.</string>
<string name="menu_keystore_remove_key">Slet avanceret oplåsningsnøgle</string>
<string name="menu_reload_database">Genindlæs database</string>
<string name="error_duplicate_file">Fildataene findes allerede.</string>
<string name="error_upload_file">Der opstod en fejl under overførsel af fildataene.</string>
<string name="error_file_to_big">Filen, du prøver at overføre, er for stor.</string>
<string name="error_rebuild_list">Listen kan ikke genopbygges korrekt.</string>
<string name="error_database_uri_null">Database-URI kan ikke hentes.</string>
<string name="content_description_otp_information">Oplysninger om engangsadgangskode</string>
<string name="advanced_unlock_prompt_extract_credential_message">Uddrag databasens legitimationsoplysninger med biometriske data</string>
<string name="advanced_unlock_prompt_store_credential_message">Advarsel: hovedadgangskoden skal stadig huskes, hvis der bruges biometrisk genkendelse.</string>
<string name="open_advanced_unlock_prompt_unlock_database">Åbn biometriske forespørgsel for at låse databasen op</string>
<string name="warning_database_revoked">Adgang til filen tilbagekaldt af filhåndteringsprogrammet, luk databasen og genåbn den fra dens placering.</string>
<string name="error_start_database_action">Der opstod en fejl under udførelsen af en handling på databasen.</string>
<string name="error_remove_file">Der opstod en fejl med at fjerne fildata.</string>
<string name="error_otp_type">Den existerende OTP type kunne ikke genkendes, den kan være tiden er udløbet for at lave dette token.</string>
<string name="education_advanced_unlock_summary">Sammenkæd din adgangskode, med din scannede biometriske oplysninger eller enheds legitimationsoplysninger for, hurtigt at låse din database op.</string>
<string name="enter">Enter</string>
<string name="temp_advanced_unlock_timeout_summary">Varigheden af avanceret oplåsning, før indholdet slettes</string>
<string name="device_credential_unlock_enable_summary">Giver dig mulighed for at bruge dine enhedsoplysninger for at åbne databasen</string>
<string name="device_credential_unlock_enable_title">Oplåsning via enhedsoplysninger</string>
<string name="autofill_inline_suggestions_summary">Forsøg på at vise forslag til automatisk udfyldning direkte fra et kompatibelt tastatur</string>
<string name="autofill_inline_suggestions_title">Indbyggede forslag</string>
<string name="backspace">Tilbagetast</string>
<string name="advanced_unlock_timeout">Avanceret oplåsningstimeout</string>
<string name="temp_advanced_unlock_timeout_title">Avanceret oplåsning udløbsdato</string>
<string name="temp_advanced_unlock_enable_summary">Gem ikke krypteret indhold for at bruge avanceret oplåsning</string>
<string name="temp_advanced_unlock_enable_title">Midlertidig biometrisk oplåsning</string>
<string name="device_credential">Enhedsoplysninger</string>
<string name="properties">Egenskaber</string>
<string name="open_advanced_unlock_prompt_store_credential">Åbn den biometrisk genkendelse for at gemme legitimationsoplysninger</string>
<string name="error_export_app_properties">Fejl under eksport af app-egenskaber</string>
<string name="success_export_app_properties">App-egenskaber eksporteret</string>
<string name="error_import_app_properties">Fejl under importering af app-egenskaber</string>
<string name="success_import_app_properties">App-egenskaber importeret</string>
<string name="description_app_properties">KeePassDX-egenskaber til at administrere app-indstillinger</string>
<string name="export_app_properties_summary">Opret en fil til at eksportere app-egenskaber</string>
<string name="export_app_properties_title">Eksporter app-egenskaber</string>
<string name="import_app_properties_summary">Vælg en fil for at importere app-egenskaber</string>
<string name="import_app_properties_title">Importer appegenskaber</string>
</resources> </resources>

View File

@@ -30,7 +30,7 @@
<string name="add_entry">Eintrag hinzufügen</string> <string name="add_entry">Eintrag hinzufügen</string>
<string name="add_group">Gruppe hinzufügen</string> <string name="add_group">Gruppe hinzufügen</string>
<string name="encryption_algorithm">Verschlüsselungsalgorithmus</string> <string name="encryption_algorithm">Verschlüsselungsalgorithmus</string>
<string name="app_timeout">App-Timeout</string> <string name="app_timeout">Zeitüberschreitung</string>
<string name="app_timeout_summary">Inaktivitätszeit vor dem Sperren der Datenbank</string> <string name="app_timeout_summary">Inaktivitätszeit vor dem Sperren der Datenbank</string>
<string name="application">App</string> <string name="application">App</string>
<string name="menu_app_settings">App-Einstellungen</string> <string name="menu_app_settings">App-Einstellungen</string>
@@ -67,7 +67,7 @@
<string name="entry_user_name">Benutzername</string> <string name="entry_user_name">Benutzername</string>
<string name="error_arc4">Die RC4/Arcfour-Stromverschlüsselung wird nicht unterstützt.</string> <string name="error_arc4">Die RC4/Arcfour-Stromverschlüsselung wird nicht unterstützt.</string>
<string name="error_can_not_handle_uri">KeePassDX kann diese URI-Adresse nicht verarbeiten.</string> <string name="error_can_not_handle_uri">KeePassDX kann diese URI-Adresse nicht verarbeiten.</string>
<string name="error_file_not_create">Konnte Datei nicht erstellen</string> <string name="error_file_not_create">Datei konnte nicht erstellt werden</string>
<string name="error_invalid_db">Datenbank nicht lesbar.</string> <string name="error_invalid_db">Datenbank nicht lesbar.</string>
<string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string> <string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string>
<string name="error_no_name">Namen eingeben.</string> <string name="error_no_name">Namen eingeben.</string>
@@ -147,17 +147,6 @@
<string name="education_unlock_summary">Geben Sie das Passwort und/oder die Schlüsseldatei ein, um Ihre Datenbank zu entsperren. <string name="education_unlock_summary">Geben Sie das Passwort und/oder die Schlüsseldatei ein, um Ihre Datenbank zu entsperren.
\n \n
\nSichern Sie Ihre Datenbankdatei nach jeder Änderung an einem sicheren Ort.</string> \nSichern Sie Ihre Datenbankdatei nach jeder Änderung an einem sicheren Ort.</string>
<string-array name="timeout_options">
<item>5 Sekunden</item>
<item>10 Sekunden</item>
<item>20 Sekunden</item>
<item>30 Sekunden</item>
<item>1 Minute</item>
<item>5 Minuten</item>
<item>15 Minuten</item>
<item>30 Minuten</item>
<item>Nie</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Klein</item> <item>Klein</item>
<item>Mittel</item> <item>Mittel</item>
@@ -197,7 +186,7 @@
<string name="encryption_explanation">Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet.</string> <string name="encryption_explanation">Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet.</string>
<string name="kdf_explanation">Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird.</string> <string name="kdf_explanation">Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird.</string>
<string name="memory_usage">Speichernutzung</string> <string name="memory_usage">Speichernutzung</string>
<string name="memory_usage_explanation">Größe des Speichers der für die Schlüsselableitung genutzt wird.</string> <string name="memory_usage_explanation">Größe des Speichers, der für die Schlüsselableitung genutzt wird.</string>
<string name="parallelism">Parallelismus</string> <string name="parallelism">Parallelismus</string>
<string name="parallelism_explanation">Grad des Parallelismus (d. h. Anzahl der Threads), der für die Schlüsselableitung genutzt wird.</string> <string name="parallelism_explanation">Grad des Parallelismus (d. h. Anzahl der Threads), der für die Schlüsselableitung genutzt wird.</string>
<string name="sort_menu">Sortieren</string> <string name="sort_menu">Sortieren</string>
@@ -279,7 +268,6 @@
<string name="contribute">Unterstützen</string> <string name="contribute">Unterstützen</string>
<string name="icon_pack_choose_title">Symbolpaket</string> <string name="icon_pack_choose_title">Symbolpaket</string>
<string name="icon_pack_choose_summary">In der App verwendetes Symbolpaket</string> <string name="icon_pack_choose_summary">In der App verwendetes Symbolpaket</string>
<string name="error_move_folder_in_itself">Eine Gruppe kann nicht in sich selbst verschoben werden.</string>
<string name="menu_copy">Kopieren</string> <string name="menu_copy">Kopieren</string>
<string name="menu_move">Verschieben</string> <string name="menu_move">Verschieben</string>
<string name="menu_paste">Einfügen</string> <string name="menu_paste">Einfügen</string>
@@ -384,7 +372,7 @@
<string name="entry_otp">OTP</string> <string name="entry_otp">OTP</string>
<string name="error_invalid_OTP">Ungültiges OTP-Geheimnis.</string> <string name="error_invalid_OTP">Ungültiges OTP-Geheimnis.</string>
<string name="error_disallow_no_credentials">Mindestens eine Anmeldeinformation muss festgelegt sein.</string> <string name="error_disallow_no_credentials">Mindestens eine Anmeldeinformation muss festgelegt sein.</string>
<string name="error_copy_group_here">Eine Gruppe kann nicht hierher kopiert werden.</string> <string name="error_copy_group_here">Hierher kann keine Gruppe kopiert werden.</string>
<string name="error_otp_secret_key">Geheimschlüssel muss im Base32-Format vorliegen.</string> <string name="error_otp_secret_key">Geheimschlüssel muss im Base32-Format vorliegen.</string>
<string name="error_otp_counter">Zähler muss zwischen %1$d und %2$d eingestellt sein.</string> <string name="error_otp_counter">Zähler muss zwischen %1$d und %2$d eingestellt sein.</string>
<string name="error_otp_period">Zeitraum muss zwischen %1$d und %2$d Sekunden liegen.</string> <string name="error_otp_period">Zeitraum muss zwischen %1$d und %2$d Sekunden liegen.</string>
@@ -470,7 +458,7 @@
<string name="autofill_web_domain_blocklist_title">Liste gesperrter Web-Domains</string> <string name="autofill_web_domain_blocklist_title">Liste gesperrter Web-Domains</string>
<string name="autofill_application_id_blocklist_summary">Liste der Apps, in denen ein automatisches Ausfüllen verhindert wird</string> <string name="autofill_application_id_blocklist_summary">Liste der Apps, in denen ein automatisches Ausfüllen verhindert wird</string>
<string name="autofill_application_id_blocklist_title">Liste gesperrter Anwendungen</string> <string name="autofill_application_id_blocklist_title">Liste gesperrter Anwendungen</string>
<string name="subdomain_search_summary">Suche Web-Domains mit Subdomain-Beschränkungen</string> <string name="subdomain_search_summary">Web-Domains mit Subdomain-Beschränkungen durchsuchen</string>
<string name="subdomain_search_title">Subdomain-Suche</string> <string name="subdomain_search_title">Subdomain-Suche</string>
<string name="error_string_type">Dieser Text stimmt nicht mit dem angeforderten Element überein.</string> <string name="error_string_type">Dieser Text stimmt nicht mit dem angeforderten Element überein.</string>
<string name="content_description_add_item">Element hinzufügen</string> <string name="content_description_add_item">Element hinzufügen</string>
@@ -487,7 +475,7 @@
<string name="warning_empty_keyfile">Es wird nicht empfohlen, eine leere Schlüsseldatei hinzuzufügen.</string> <string name="warning_empty_keyfile">Es wird nicht empfohlen, eine leere Schlüsseldatei hinzuzufügen.</string>
<string name="warning_empty_keyfile_explanation">Der Inhalt der Key-Datei sollte nie geändert werden und im besten Fall zufällig generierte Daten enthalten.</string> <string name="warning_empty_keyfile_explanation">Der Inhalt der Key-Datei sollte nie geändert werden und im besten Fall zufällig generierte Daten enthalten.</string>
<string name="warning_sure_remove_data">Sollen diese Daten trotzdem entfernt werden\?</string> <string name="warning_sure_remove_data">Sollen diese Daten trotzdem entfernt werden\?</string>
<string name="warning_remove_unlinked_attachment">Das Entfernen ungekoppelter Daten könnte die Größe Ihrer Datenbank reduzieren, jedoch auch Daten, die von KeePass-Plugins genutzt werden, entfernen.</string> <string name="warning_remove_unlinked_attachment">Das Entfernen nicht verknüpfter Daten kann die Größe Ihrer Datenbank reduzieren, allerdings auch Daten löschen, die von KeePass-Plugins genutzt werden.</string>
<string name="warning_replace_file">Das Hochladen dieser Datei wird die bereits Existierende ersetzen.</string> <string name="warning_replace_file">Das Hochladen dieser Datei wird die bereits Existierende ersetzen.</string>
<string name="warning_file_too_big">Eine KeePass-Datenbank sollte nur kleine Dienstprogrammdateien beinhalten (zum Beispiel PGP-Schlüsseldateien). <string name="warning_file_too_big">Eine KeePass-Datenbank sollte nur kleine Dienstprogrammdateien beinhalten (zum Beispiel PGP-Schlüsseldateien).
\n \n
@@ -518,12 +506,12 @@
<string name="keyboard_save_search_info_title">Gemeinsam genutzte Informationen speichern</string> <string name="keyboard_save_search_info_title">Gemeinsam genutzte Informationen speichern</string>
<string name="warning_empty_recycle_bin">Sollen alle ausgewählten Knoten wirklich aus dem Papierkorb gelöscht werden\?</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="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 erweiterte Entsperrerkennung verwenden.</string> <string name="advanced_unlock_prompt_store_credential_message">Warnung: Sie müssen sich immer noch an Ihr Masterpasswort erinnern, wenn Sie die erweiterte Entsperrerkennung verwenden.</string>
<string name="open_advanced_unlock_prompt_store_credential">Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen</string> <string name="open_advanced_unlock_prompt_store_credential">Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen</string>
<string name="open_advanced_unlock_prompt_unlock_database">Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank</string> <string name="open_advanced_unlock_prompt_unlock_database">Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank</string>
<string name="menu_keystore_remove_key">Löschen des Schlüssels zum erweiterten Entsperren</string> <string name="menu_keystore_remove_key">Schlüssel für modernes Entsperren löschen</string>
<string name="advanced_unlock_prompt_store_credential_title">Erweiterte Entsperrerkennung</string> <string name="advanced_unlock_prompt_store_credential_title">Erweiterte Entsperrerkennung</string>
<string name="education_advanced_unlock_summary">Ihr Passwort verbinden mit Ihrem gescannten biometrischen oder berechtigen Gerät um schnell Ihre Datenbank zu entsperren.</string> <string name="education_advanced_unlock_summary">Verknüpfen Sie Ihr Passwort mit Ihren gescannten Biometriedaten oder Daten zur Geräteanmeldung, um schnell Ihre Datenbank zu entsperren.</string>
<string name="education_advanced_unlock_title">Erweiterte Entsperrung der Datenbank</string> <string name="education_advanced_unlock_title">Erweiterte Entsperrung der Datenbank</string>
<string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</string> <string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</string>
<string name="temp_advanced_unlock_timeout_summary">Dauer der erweiterten Entsperrung bevor sein Inhalt gelöscht wird</string> <string name="temp_advanced_unlock_timeout_summary">Dauer der erweiterten Entsperrung bevor sein Inhalt gelöscht wird</string>
@@ -531,7 +519,7 @@
<string name="temp_advanced_unlock_enable_summary">Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen</string> <string name="temp_advanced_unlock_enable_summary">Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen</string>
<string name="temp_advanced_unlock_enable_title">Temporäre erweiterte Entsperrung</string> <string name="temp_advanced_unlock_enable_title">Temporäre erweiterte Entsperrung</string>
<string name="device_credential_unlock_enable_summary">Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string> <string name="device_credential_unlock_enable_summary">Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string>
<string name="advanced_unlock_tap_delete">Drücken um erweiterte Entsperrschlüssel zu löschen</string> <string name="advanced_unlock_tap_delete">Drücken, um erweiterte Entsperrschlüssel zu löschen</string>
<string name="content">Inhalt</string> <string name="content">Inhalt</string>
<string name="advanced_unlock_prompt_extract_credential_title">Öffne Datenbank mit erweiterter Entsperrerkennung</string> <string name="advanced_unlock_prompt_extract_credential_title">Öffne Datenbank mit erweiterter Entsperrerkennung</string>
<string name="enter">Eingabetaste</string> <string name="enter">Eingabetaste</string>
@@ -542,7 +530,7 @@
<string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen, löschen\?</string> <string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen, löschen\?</string>
<string name="device_credential_unlock_enable_title">Geräteanmeldedaten entsperren</string> <string name="device_credential_unlock_enable_title">Geräteanmeldedaten entsperren</string>
<string name="device_credential">Geräteanmeldedaten</string> <string name="device_credential">Geräteanmeldedaten</string>
<string name="credential_before_click_advanced_unlock_button">Geben sie das Passwort ein, und dann klicken sie den \"Erweitertes Entsperren\" Knopf.</string> <string name="credential_before_click_advanced_unlock_button">Geben Sie das Passwort ein und klicken Sie dann auf diesen Knopf.</string>
<string name="advanced_unlock_prompt_not_initialized">Initialisieren des erweitertes Entsperren Dialogs fehlgeschlagen.</string> <string name="advanced_unlock_prompt_not_initialized">Initialisieren des erweitertes Entsperren Dialogs fehlgeschlagen.</string>
<string name="advanced_unlock_scanning_error">Erweitertes Entsperren Fehler: %1$s</string> <string name="advanced_unlock_scanning_error">Erweitertes Entsperren Fehler: %1$s</string>
<string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string> <string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string>
@@ -551,4 +539,14 @@
<string name="error_rebuild_list">Die Liste kann nicht ordnungsgemäß neu erstellt werden.</string> <string name="error_rebuild_list">Die Liste kann nicht ordnungsgemäß neu erstellt werden.</string>
<string name="error_database_uri_null">Datenbank-URI kann nicht abgerufen werden.</string> <string name="error_database_uri_null">Datenbank-URI kann nicht abgerufen werden.</string>
<string name="menu_reload_database">Datenbank neu laden</string> <string name="menu_reload_database">Datenbank neu laden</string>
<string name="warning_database_info_changed_options">Überschreiben Sie die externen Änderungen, indem Sie die Datenbank speichern, oder laden Sie sie mit den neuesten Änderungen neu.</string>
<string name="error_file_to_big">Die Datei, die hochgeladen werden soll, ist zu groß.</string>
<string name="warning_database_info_changed">Die in Ihrer Datenbank enthaltenen Informationen wurden außerhalb der App geändert.</string>
<string name="error_remove_file">Beim Löschen der Datei trat ein Fehler auf.</string>
<string name="error_duplicate_file">Die Datei gibt es bereits.</string>
<string name="error_upload_file">Beim Hochladen der Datei trat ein Fehler auf.</string>
<string name="import_app_properties_title">Importieren von Anwendungeneigenschaften</string>
<string name="error_start_database_action">Beim Ausführen einer Aktion in der Datenbank ist ein Fehler aufgetreten.</string>
<string name="error_otp_type">Der vorhandene Einmalpassworttyp wird von diesem Formular nicht erkannt, seine Validierung erzeugt das Token möglicherweise nicht mehr korrekt.</string>
<string name="content_description_otp_information">Informationen zu Einmalpasswörtern</string>
</resources> </resources>

View File

@@ -24,7 +24,7 @@
<string name="add_entry">Προσθήκη καταχώρησης</string> <string name="add_entry">Προσθήκη καταχώρησης</string>
<string name="add_group">Προσθήκη ομάδας</string> <string name="add_group">Προσθήκη ομάδας</string>
<string name="encryption_algorithm">Αλγόριθμος κρυπτογράφησης</string> <string name="encryption_algorithm">Αλγόριθμος κρυπτογράφησης</string>
<string name="app_timeout">Χρονικό όριο εφαρμογής</string> <string name="app_timeout">Χρονικό όριο</string>
<string name="app_timeout_summary">Χρόνος αδράνειας πριν από το κλείδωμα της βάσης δεδομένων</string> <string name="app_timeout_summary">Χρόνος αδράνειας πριν από το κλείδωμα της βάσης δεδομένων</string>
<string name="application">Εφαρμογή</string> <string name="application">Εφαρμογή</string>
<string name="menu_app_settings">Ρυθμίσεις εφαρμογής</string> <string name="menu_app_settings">Ρυθμίσεις εφαρμογής</string>
@@ -135,17 +135,6 @@
<string name="education_unlock_summary">Καταχωρίστε τον κωδικό πρόσβασης και /ή το αρχείο-κλειδί για να ξεκλειδώσετε τη βάση δεδομένων σας. <string name="education_unlock_summary">Καταχωρίστε τον κωδικό πρόσβασης και /ή το αρχείο-κλειδί για να ξεκλειδώσετε τη βάση δεδομένων σας.
\n \n
\nΔημιουργήστε αντίγραφα ασφαλείας του αρχείου βάσης δεδομένων σας, σε ασφαλές μέρος μετά από κάθε αλλαγή.</string> \nΔημιουργήστε αντίγραφα ασφαλείας του αρχείου βάσης δεδομένων σας, σε ασφαλές μέρος μετά από κάθε αλλαγή.</string>
<string-array name="timeout_options">
<item>5 δευτερόλεπτα</item>
<item>10 δευτερόλεπτα</item>
<item>20 δευτερόλεπτα</item>
<item>30 δευτερόλεπτα</item>
<item>1 λεπτό</item>
<item>5 λεπτά</item>
<item>15 λεπτά</item>
<item>30 λεπτά</item>
<item>Ποτέ</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Μικρά</item> <item>Μικρά</item>
<item>Μεσαία</item> <item>Μεσαία</item>
@@ -262,7 +251,6 @@
<string name="style_choose_summary">Θέμα που χρησιμοποιείται στην εφαρμογή</string> <string name="style_choose_summary">Θέμα που χρησιμοποιείται στην εφαρμογή</string>
<string name="icon_pack_choose_title">Πακέτο Εικονιδίων</string> <string name="icon_pack_choose_title">Πακέτο Εικονιδίων</string>
<string name="icon_pack_choose_summary">Πακέτο εικονιδίων που χρησιμοποιείται στην εφαρμογή</string> <string name="icon_pack_choose_summary">Πακέτο εικονιδίων που χρησιμοποιείται στην εφαρμογή</string>
<string name="error_move_folder_in_itself">Δεν μπορείτε να μετακινήσετε μια ομάδα μέσα στον εαυτό της.</string>
<string name="menu_copy">Αντιγραφή</string> <string name="menu_copy">Αντιγραφή</string>
<string name="menu_move">Μετακίνηση</string> <string name="menu_move">Μετακίνηση</string>
<string name="menu_paste">Επικόλληση</string> <string name="menu_paste">Επικόλληση</string>
@@ -559,4 +547,17 @@
<string name="error_upload_file">Παρουσιάστηκε σφάλμα κατά τη μεταφόρτωση των δεδομένων αρχείου.</string> <string name="error_upload_file">Παρουσιάστηκε σφάλμα κατά τη μεταφόρτωση των δεδομένων αρχείου.</string>
<string name="error_file_to_big">Το αρχείο που προσπαθείτε να ανεβάσετε είναι πολύ μεγάλο.</string> <string name="error_file_to_big">Το αρχείο που προσπαθείτε να ανεβάσετε είναι πολύ μεγάλο.</string>
<string name="content_description_otp_information">Πληροφορίες One-time κωδικού πρόσβασης</string> <string name="content_description_otp_information">Πληροφορίες One-time κωδικού πρόσβασης</string>
<string name="error_remove_file">Παρουσιάστηκε σφάλμα κατά την κατάργηση των δεδομένων αρχείου.</string>
<string name="error_duplicate_file">Τα δεδομένα αρχείου υπάρχουν ήδη.</string>
<string name="properties">Ιδιότητες</string>
<string name="error_export_app_properties">Σφάλμα κατά την εξαγωγή ιδιοτήτων εφαρμογής</string>
<string name="success_export_app_properties">Έγινε εξαγωγή ιδιοτήτων εφαρμογής</string>
<string name="error_import_app_properties">Σφάλμα κατά την εισαγωγή ιδιοτήτων εφαρμογής</string>
<string name="success_import_app_properties">Έγινε εισαγωγή ιδιοτήτων εφαρμογής</string>
<string name="description_app_properties">Ιδιότητες KeePassDX για διαχείριση ρυθμίσεων εφαρμογής</string>
<string name="export_app_properties_summary">Δημιουργήστε ένα αρχείο για εξαγωγή ιδιοτήτων εφαρμογής</string>
<string name="export_app_properties_title">Εξαγωγή ιδιοτήτων εφαρμογής</string>
<string name="import_app_properties_summary">Επιλέξτε ένα αρχείο για εισαγωγή ιδιοτήτων εφαρμογής</string>
<string name="import_app_properties_title">Εισαγωγή ιδιοτήτων εφαρμογής</string>
<string name="error_start_database_action">Παρουσιάστηκε σφάλμα κατά την εκτέλεση μιας ενέργειας στη βάση δεδομένων.</string>
</resources> </resources>

View File

@@ -125,17 +125,6 @@
<string name="education_unlock_summary">Introduzca la contraseña y/o el archivo clave para desbloquear su base de datos. <string name="education_unlock_summary">Introduzca la contraseña y/o el archivo clave para desbloquear su base de datos.
\n \n
\nHaga una copia de seguridad de su archivo de base de datos en un lugar seguro después de cada cambio.</string> \nHaga una copia de seguridad de su archivo de base de datos en un lugar seguro después de cada cambio.</string>
<string-array name="timeout_options">
<item>5 segundos</item>
<item>10 segundos</item>
<item>20 segundos</item>
<item>30 segundos</item>
<item>1 minuto</item>
<item>5 minutos</item>
<item>15 minutos</item>
<item>30 minutos</item>
<item>Nunca</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Pequeño</item> <item>Pequeño</item>
<item>Mediano</item> <item>Mediano</item>
@@ -278,7 +267,6 @@
<string name="edit_entry">Editar entrada</string> <string name="edit_entry">Editar entrada</string>
<string name="error_load_database">No se pudo cargar la base de datos.</string> <string name="error_load_database">No se pudo cargar la base de datos.</string>
<string name="error_load_database_KDF_memory">No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.</string> <string name="error_load_database_KDF_memory">No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.</string>
<string name="error_move_folder_in_itself">No puede mover un grupo dentro de sí mismo.</string>
<string name="list_entries_show_username_title">Enseña nombres de usuario</string> <string name="list_entries_show_username_title">Enseña nombres de usuario</string>
<string name="list_entries_show_username_summary">Enseña nombres de usuador en las listras de entradas</string> <string name="list_entries_show_username_summary">Enseña nombres de usuador en las listras de entradas</string>
<string name="menu_copy">Copiar</string> <string name="menu_copy">Copiar</string>
@@ -470,7 +458,7 @@
<string name="content">Contenido</string> <string name="content">Contenido</string>
<string name="clipboard_explanation_summary">Copiar los campos de entrada usando el portapapeles de su dispositivo</string> <string name="clipboard_explanation_summary">Copiar los campos de entrada usando el portapapeles de su dispositivo</string>
<string name="device_credential">Credenciales del dispositivo</string> <string name="device_credential">Credenciales del dispositivo</string>
<string name="credential_before_click_advanced_unlock_button">Introduzca la contraseña y luego haga clic en el botón \"Desbloqueo avanzado\".</string> <string name="credential_before_click_advanced_unlock_button">Introduzca la contraseña y luego haga clic en este botón.</string>
<string name="advanced_unlock_prompt_not_initialized">No se pudo inicializar el indicador de desbloqueo avanzado.</string> <string name="advanced_unlock_prompt_not_initialized">No se pudo inicializar el indicador de desbloqueo avanzado.</string>
<string name="advanced_unlock_scanning_error">Error de desbloqueo avanzado: %1$s</string> <string name="advanced_unlock_scanning_error">Error de desbloqueo avanzado: %1$s</string>
<string name="advanced_unlock_not_recognized">No se pudo reconocer la impresión de desbloqueo avanzado</string> <string name="advanced_unlock_not_recognized">No se pudo reconocer la impresión de desbloqueo avanzado</string>
@@ -554,4 +542,6 @@
<string name="menu_reload_database">Recargar la base de datos</string> <string name="menu_reload_database">Recargar la base de datos</string>
<string name="error_otp_type">El tipo de OTP existente no es reconocido por este formulario, su validación ya no puede generar correctamente el token.</string> <string name="error_otp_type">El tipo de OTP existente no es reconocido por este formulario, su validación ya no puede generar correctamente el token.</string>
<string name="download_canceled">¡Cancelado!</string> <string name="download_canceled">¡Cancelado!</string>
<string name="error_duplicate_file">Los datos de archivo ya existen.</string>
<string name="error_upload_file">Ha habido un error al subir el archivo de datos.</string>
</resources> </resources>

View File

@@ -132,17 +132,6 @@
<string name="uppercase">Maiuskulak</string> <string name="uppercase">Maiuskulak</string>
<string name="version_label">Bertsioa %1$s</string> <string name="version_label">Bertsioa %1$s</string>
<string name="education_unlock_summary">Sartu pasahitz eta / edo gako fitxategi bat zure datubasea desblokeatzeko.</string> <string name="education_unlock_summary">Sartu pasahitz eta / edo gako fitxategi bat zure datubasea desblokeatzeko.</string>
<string-array name="timeout_options">
<item>5 segundu</item>
<item>10 segundu</item>
<item>20 segundu</item>
<item>30 segundu</item>
<item>minutu 1</item>
<item>5 minutu</item>
<item>15 minutu</item>
<item>30 minutu</item>
<item>Inoiz ez</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Txikia</item> <item>Txikia</item>
<item>Ertaina</item> <item>Ertaina</item>

View File

@@ -141,7 +141,6 @@
<string name="error_copy_group_here">شما نمی توانید یک گروه را در اینجا کپی کنید.</string> <string name="error_copy_group_here">شما نمی توانید یک گروه را در اینجا کپی کنید.</string>
<string name="error_copy_entry_here">شما نمی توانید یک ورودی را در اینجا کپی کنید.</string> <string name="error_copy_entry_here">شما نمی توانید یک ورودی را در اینجا کپی کنید.</string>
<string name="error_move_entry_here">شما نمی توانید یک ورودی را به اینجا منتقل کنید.</string> <string name="error_move_entry_here">شما نمی توانید یک ورودی را به اینجا منتقل کنید.</string>
<string name="error_move_folder_in_itself">شما نمی توانید یک گروه را به خود منتقل کنید.</string>
<string name="error_autofill_enable_service">قادر به فعال کردن سرویس پر کردن خودکار نبود.</string> <string name="error_autofill_enable_service">قادر به فعال کردن سرویس پر کردن خودکار نبود.</string>
<string name="error_wrong_length">یک عدد کامل مثبت را در زمینه \"طول\" وارد کنید.</string> <string name="error_wrong_length">یک عدد کامل مثبت را در زمینه \"طول\" وارد کنید.</string>
<string name="error_label_exists">این برچسب در حال حاضر وجود دارد.</string> <string name="error_label_exists">این برچسب در حال حاضر وجود دارد.</string>

View File

@@ -132,17 +132,6 @@
<string name="uppercase">Isot kirjaimet</string> <string name="uppercase">Isot kirjaimet</string>
<string name="version_label">Versio %1$s</string> <string name="version_label">Versio %1$s</string>
<string name="education_unlock_summary">Syötä salasana ja/tai avaintiedosto avataksesi tietokantasi.</string> <string name="education_unlock_summary">Syötä salasana ja/tai avaintiedosto avataksesi tietokantasi.</string>
<string-array name="timeout_options">
<item>5 sekuntia</item>
<item>10 sekuntia</item>
<item>20 sekuntia</item>
<item>30 sekuntia</item>
<item>1 minuutti</item>
<item>5 minuttia</item>
<item>15 minuttia</item>
<item>30 minuttia</item>
<item>Ei koskaan</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Pieni</item> <item>Pieni</item>
<item>Keskikokoinen</item> <item>Keskikokoinen</item>
@@ -319,7 +308,6 @@
<string name="error_copy_group_here">Et voi kopioida ryhmää tänne.</string> <string name="error_copy_group_here">Et voi kopioida ryhmää tänne.</string>
<string name="error_copy_entry_here">Et voi kopioida tietuetta tänne.</string> <string name="error_copy_entry_here">Et voi kopioida tietuetta tänne.</string>
<string name="error_move_entry_here">Et voi siirtää tietuetta tänne.</string> <string name="error_move_entry_here">Et voi siirtää tietuetta tänne.</string>
<string name="error_move_folder_in_itself">Et voi siirtää ryhmää itsensä sisälle.</string>
<string name="error_autofill_enable_service">Automaattista täyttöä ei voitu ottaa käyttöön.</string> <string name="error_autofill_enable_service">Automaattista täyttöä ei voitu ottaa käyttöön.</string>
<string name="content_description_node_children">Solmun lapset</string> <string name="content_description_node_children">Solmun lapset</string>
</resources> </resources>

View File

@@ -26,7 +26,7 @@
<string name="encryption">Chiffrement</string> <string name="encryption">Chiffrement</string>
<string name="encryption_algorithm">Algorithme de chiffrement</string> <string name="encryption_algorithm">Algorithme de chiffrement</string>
<string name="key_derivation_function">Fonction de dérivation de clé</string> <string name="key_derivation_function">Fonction de dérivation de clé</string>
<string name="app_timeout">Délai dexpiration de lapplication</string> <string name="app_timeout">Délai dexpiration</string>
<string name="app_timeout_summary">Durée dinactivité avant le verrouillage de la base de données</string> <string name="app_timeout_summary">Durée dinactivité avant le verrouillage de la base de données</string>
<string name="application">Application</string> <string name="application">Application</string>
<string name="menu_app_settings">Paramètres de lapplication</string> <string name="menu_app_settings">Paramètres de lapplication</string>
@@ -258,17 +258,6 @@
<string name="html_text_dev_feature_upgrade">Noubliez pas de garder votre application à jour en installant les nouvelles versions.</string> <string name="html_text_dev_feature_upgrade">Noubliez pas de garder votre application à jour en installant les nouvelles versions.</string>
<string name="download">Télécharger</string> <string name="download">Télécharger</string>
<string name="contribute">Contribuer</string> <string name="contribute">Contribuer</string>
<string-array name="timeout_options">
<item>5 secondes</item>
<item>10 secondes</item>
<item>20 secondes</item>
<item>30 secondes</item>
<item>1 minute</item>
<item>5 minutes</item>
<item>15 minutes</item>
<item>30 minutes</item>
<item>Jamais</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Petit</item> <item>Petit</item>
<item>Moyen</item> <item>Moyen</item>
@@ -286,7 +275,6 @@
</string-array> </string-array>
<string name="icon_pack_choose_title">Collection dicônes</string> <string name="icon_pack_choose_title">Collection dicônes</string>
<string name="icon_pack_choose_summary">Collection dicônes utilisées dans lapplication</string> <string name="icon_pack_choose_summary">Collection dicônes utilisées dans lapplication</string>
<string name="error_move_folder_in_itself">Vous ne pouvez pas déplacer un groupe dans lui-même.</string>
<string name="menu_copy">Copier</string> <string name="menu_copy">Copier</string>
<string name="menu_move">Déplacer</string> <string name="menu_move">Déplacer</string>
<string name="menu_paste">Coller</string> <string name="menu_paste">Coller</string>
@@ -519,7 +507,7 @@
<string name="device_credential_unlock_enable_summary">Vous permet d\'utiliser les informations d\'identification de votre appareil pour ouvrir la base de données</string> <string name="device_credential_unlock_enable_summary">Vous permet d\'utiliser les informations d\'identification de votre appareil pour ouvrir la base de données</string>
<string name="device_credential_unlock_enable_title">Déverrouillage par identifiants de l\'appareil</string> <string name="device_credential_unlock_enable_title">Déverrouillage par identifiants de l\'appareil</string>
<string name="device_credential">Déverouillage de l\'appareil</string> <string name="device_credential">Déverouillage de l\'appareil</string>
<string name="credential_before_click_advanced_unlock_button">Tapez le mot de passe, puis cliquez sur le bouton \"Déverrouillage avancé\".</string> <string name="credential_before_click_advanced_unlock_button">Tapez le mot de passe, puis cliquez sur ce bouton.</string>
<string name="advanced_unlock_prompt_not_initialized">Impossible d\'initialiser l\'invite de déverrouillage avancé.</string> <string name="advanced_unlock_prompt_not_initialized">Impossible d\'initialiser l\'invite de déverrouillage avancé.</string>
<string name="advanced_unlock_scanning_error">Erreur de déverrouillage avancé : %1$s</string> <string name="advanced_unlock_scanning_error">Erreur de déverrouillage avancé : %1$s</string>
<string name="advanced_unlock_not_recognized">Impossible de reconnaître l\'empreinte de déverrouillage avancé</string> <string name="advanced_unlock_not_recognized">Impossible de reconnaître l\'empreinte de déverrouillage avancé</string>
@@ -560,4 +548,24 @@
<string name="unit_byte">Octets</string> <string name="unit_byte">Octets</string>
<string name="error_otp_type">Le type OTP existant n\'est pas reconnu par ce formulaire, sa validation peut ne plus générer correctement le jeton.</string> <string name="error_otp_type">Le type OTP existant n\'est pas reconnu par ce formulaire, sa validation peut ne plus générer correctement le jeton.</string>
<string name="download_canceled">Annulé!</string> <string name="download_canceled">Annulé!</string>
<string name="icon_section_custom">Customisé</string>
<string name="icon_section_standard">Standard</string>
<string name="style_brightness_summary">Sélectionnez des thèmes clairs ou foncés</string>
<string name="style_brightness_title">Luminosité de thème</string>
<string name="error_remove_file">Une erreur s\'est produite lors de la suppression des données du fichier.</string>
<string name="error_duplicate_file">Les données du fichier existent déjà.</string>
<string name="error_upload_file">Une erreur est survenue lors du téléversement des données du fichier.</string>
<string name="error_file_to_big">Le fichier que vous essayez de téléverser est trop volumineux.</string>
<string name="content_description_otp_information">Information sur le mot de passe à usage unique</string>
<string name="properties">Propriétés</string>
<string name="error_export_app_properties">Erreur lors de l\'exportation des propriétés de l\'application</string>
<string name="success_export_app_properties">Propriétés de l\'application exportées</string>
<string name="error_import_app_properties">Erreur lors de l\'importation des propriétés de l\'application</string>
<string name="success_import_app_properties">Propriétés de l\'application importées</string>
<string name="description_app_properties">Propriétés KeePassDX pour gérer les paramètres de l\'application</string>
<string name="export_app_properties_summary">Créer un fichier pour exporter les propriétés de l\'application</string>
<string name="export_app_properties_title">Export des propriétés de l\'app</string>
<string name="import_app_properties_summary">Sélectionner un fichier pour importer les propriétés de l\'application</string>
<string name="import_app_properties_title">Importation des propriétés de l\'appli</string>
<string name="error_start_database_action">Une erreur s\'est produite lors de l\'exécution d\'une action sur la base de données.</string>
</resources> </resources>

View File

@@ -242,7 +242,6 @@
<string name="error_disallow_no_credentials">Barem jedan skup podataka za prijavu mora biti postavljen.</string> <string name="error_disallow_no_credentials">Barem jedan skup podataka za prijavu mora biti postavljen.</string>
<string name="error_string_key">Svaki niz mora imati ime polja.</string> <string name="error_string_key">Svaki niz mora imati ime polja.</string>
<string name="error_autofill_enable_service">Nije moguće aktivirati uslugu automatskog ispunjavanja.</string> <string name="error_autofill_enable_service">Nije moguće aktivirati uslugu automatskog ispunjavanja.</string>
<string name="error_move_folder_in_itself">Nije moguće premjestiti grupu u samu sebe.</string>
<string name="error_move_entry_here">Unos se ne može ovdje premijestiti.</string> <string name="error_move_entry_here">Unos se ne može ovdje premijestiti.</string>
<string name="error_copy_entry_here">Unos se ne može ovdje kopirati.</string> <string name="error_copy_entry_here">Unos se ne može ovdje kopirati.</string>
<string name="error_copy_group_here">Grupa se ne može ovjde kopirati.</string> <string name="error_copy_group_here">Grupa se ne može ovjde kopirati.</string>
@@ -543,4 +542,17 @@
<string name="error_upload_file">Tijekom prijenosa podataka datoteke došlo je do greške.</string> <string name="error_upload_file">Tijekom prijenosa podataka datoteke došlo je do greške.</string>
<string name="error_file_to_big">Datoteka koju pokušavaš prenijeti je prevelika.</string> <string name="error_file_to_big">Datoteka koju pokušavaš prenijeti je prevelika.</string>
<string name="content_description_otp_information">Podaci jednokratne lozinke</string> <string name="content_description_otp_information">Podaci jednokratne lozinke</string>
<string name="error_remove_file">Tijekom uklanjanja podataka datoteke došlo je do greške.</string>
<string name="error_duplicate_file">Podaci datoteke već postoje.</string>
<string name="properties">Svojstva</string>
<string name="error_export_app_properties">Greška tijekom izvoza svojstava aplikacije</string>
<string name="success_export_app_properties">Svojstva aplikacije su izvezena</string>
<string name="error_import_app_properties">Greška tijekom uvoza svojstava aplikacije</string>
<string name="success_import_app_properties">Svojstva aplikacije su uvezena</string>
<string name="description_app_properties">KeePassDX svojstva za upravljanje postavkama aplikacije</string>
<string name="export_app_properties_summary">Stvori datoteku za izvoz svojstva aplikacije</string>
<string name="export_app_properties_title">Izvezi svojstva aplikacije</string>
<string name="import_app_properties_summary">Odaberi datoteku za uvoz svojstva aplikacije</string>
<string name="import_app_properties_title">Uvezi svojstva aplikacije</string>
<string name="error_start_database_action">Došlo je do greške tijekom izvođenja radnje u bazi podataka.</string>
</resources> </resources>

View File

@@ -140,17 +140,6 @@
<string name="education_unlock_summary">Adja meg a jelszót és/vagy a kulcsfájlt, hogy kinyithassa az adatbázist. <string name="education_unlock_summary">Adja meg a jelszót és/vagy a kulcsfájlt, hogy kinyithassa az adatbázist.
\n \n
\nKészítsen biztonsági mentést az adatbázisról minden egyes módosítás után.</string> \nKészítsen biztonsági mentést az adatbázisról minden egyes módosítás után.</string>
<string-array name="timeout_options">
<item>5 másodperc</item>
<item>10 másodperc</item>
<item>20 másodperc</item>
<item>30 másodperc</item>
<item>1 perc</item>
<item>5 perc</item>
<item>15 perc</item>
<item>30 perc</item>
<item>Soha</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Kicsi</item> <item>Kicsi</item>
<item>Közepes</item> <item>Közepes</item>
@@ -165,7 +154,6 @@
<string name="error_load_database">Az adatbázis betöltése meghiúsult.</string> <string name="error_load_database">Az adatbázis betöltése meghiúsult.</string>
<string name="error_load_database_KDF_memory">A kulcs nem tölthető be. Próbálja meg csökkenteni a KDF „Memóriahasználatot”.</string> <string name="error_load_database_KDF_memory">A kulcs nem tölthető be. Próbálja meg csökkenteni a KDF „Memóriahasználatot”.</string>
<string name="error_autofill_enable_service">Az automatikus kitöltési szolgáltatás nem engedélyezhető.</string> <string name="error_autofill_enable_service">Az automatikus kitöltési szolgáltatás nem engedélyezhető.</string>
<string name="error_move_folder_in_itself">Nem helyezheti át a csoportot saját magába.</string>
<string name="list_entries_show_username_title">Felhasználónevek megjelenítése</string> <string name="list_entries_show_username_title">Felhasználónevek megjelenítése</string>
<string name="list_entries_show_username_summary">Felhasználónevek megjelenítése a bejegyzéslistákban</string> <string name="list_entries_show_username_summary">Felhasználónevek megjelenítése a bejegyzéslistákban</string>
<string name="copy_field">%1$s másolata</string> <string name="copy_field">%1$s másolata</string>

View File

@@ -72,7 +72,6 @@
<string name="error_copy_group_here">Anda tidak bisa menyalin grup di sini.</string> <string name="error_copy_group_here">Anda tidak bisa menyalin grup di sini.</string>
<string name="error_copy_entry_here">Anda tidak bisa menyalin entri di sini.</string> <string name="error_copy_entry_here">Anda tidak bisa menyalin entri di sini.</string>
<string name="error_move_entry_here">Anda tidak bisa memindahkan entri ke sini.</string> <string name="error_move_entry_here">Anda tidak bisa memindahkan entri ke sini.</string>
<string name="error_move_folder_in_itself">Anda tidak bisa memindahkan grup ke grup itu sendiri.</string>
<string name="error_autofill_enable_service">Tidak bisa mengaktifkan layanan IsiOtomatis.</string> <string name="error_autofill_enable_service">Tidak bisa mengaktifkan layanan IsiOtomatis.</string>
<string name="error_wrong_length">Masukkan bilangan bulat di bidang \"Panjang\".</string> <string name="error_wrong_length">Masukkan bilangan bulat di bidang \"Panjang\".</string>
<string name="error_label_exists">Label ini sudah ada.</string> <string name="error_label_exists">Label ini sudah ada.</string>
@@ -322,4 +321,67 @@
<string name="error_registration_read_only">Menyimpan item baru tidak diperbolehkan dalam database read-only</string> <string name="error_registration_read_only">Menyimpan item baru tidak diperbolehkan dalam database read-only</string>
<string name="error_otp_type">Tipe OTP yang ada tidak dikenali oleh formulir ini, validasinya mungkin tidak lagi menghasilkan token dengan benar.</string> <string name="error_otp_type">Tipe OTP yang ada tidak dikenali oleh formulir ini, validasinya mungkin tidak lagi menghasilkan token dengan benar.</string>
<string name="content_description_credentials_information">Info kredensial</string> <string name="content_description_credentials_information">Info kredensial</string>
<string name="autofill_inline_suggestions_keyboard">Saran pengisian otomatis ditambahkan.</string>
<string name="autofill_block_restart">Mulai ulang aplikasi yang berisi formulir untuk mengaktifkan pemblokiran.</string>
<string name="autofill_block">Blokir pengisian otomatis</string>
<string name="autofill_web_domain_blocklist_summary">Daftar blokir yang mencegah pengisian otomatis domain web</string>
<string name="autofill_web_domain_blocklist_title">Daftar blokir domain web</string>
<string name="autofill_application_id_blocklist_summary">Daftar blokir yang mencegah pengisian otomatis aplikasi</string>
<string name="autofill_application_id_blocklist_title">Daftar blokir aplikasi</string>
<string name="autofill_ask_to_save_data_summary">Meminta untuk menyimpan data saat formulir divalidasi</string>
<string name="autofill_ask_to_save_data_title">Minta untuk menyimpan data</string>
<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>
<string name="backspace">Hapus</string>
<string name="select_entry">Pilih entri</string>
<string name="back_to_previous_keyboard">Kembali ke papan tik sebelumnya</string>
<string name="custom_fields">Bidang kustom</string>
<string name="keyboard_previous_lock_title">Kunci database</string>
<string name="keyboard_auto_go_action_title">Aksi tombol otomatis</string>
<string name="keyboard_keys_category">Tombol</string>
<string name="keyboard_previous_database_credentials_title">Layar kredensial database</string>
<string name="keyboard_change">Ganti papan tik</string>
<string name="keyboard_key_sound_title">Tombol terdengar</string>
<string name="keyboard_key_vibrate_title">Getar saat menekan tombol</string>
<string name="keyboard_auto_go_action_summary">Tindakan tombol \"Go\" setelah menekan tombol \"Field\"</string>
<string name="keyboard_theme_title">Tema papan tik</string>
<string name="keyboard_appearance_category">Tampilan</string>
<string name="keyboard_notification_entry_content_text">%1$s</string>
<string name="keyboard_notification_entry_content_title">%1$s tersedia di Magikeyboard</string>
<string name="keyboard_notification_entry_content_title_text">Entri</string>
<string name="keyboard_entry_timeout_summary">Waktu habis untuk menghapus entri papan tik</string>
<string name="keyboard_entry_timeout_title">Waktu habis</string>
<string name="keyboard_notification_entry_clear_close_summary">Tutup database saat menutup notifikasi</string>
<string name="keyboard_notification_entry_clear_close_title">Bersihkan saat menutup</string>
<string name="keyboard_search_share_title">Telusuri info yang dibagikan</string>
<string name="keyboard_notification_entry_summary">Tampilkan notifikasi ketika entri tersedia</string>
<string name="keyboard_notification_entry_title">Info notifikasi</string>
<string name="keyboard_selection_entry_summary">Tampilkan bidang input di Magikeyboard saat melihat entri</string>
<string name="keyboard_selection_entry_title">Pilihan entri</string>
<string name="keyboard_entry_category">Entri</string>
<string name="keyboard_setting_label">Pengaturan Magikeyboard</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
<string name="keyboard_name">Magikeyboard</string>
<string name="device_keyboard_setting_title">Pengaturan papan tik perangkat</string>
<string name="education_add_attachment_title">Tambah lampiran</string>
<string name="html_text_dev_feature_work_hard">Kami bekerja keras untuk merilis fitur ini segera.</string>
<string name="icon_section_custom">Kustom</string>
<string name="icon_section_standard">Standar</string>
<string name="style_brightness_summary">Pilih tema terang atau gelap</string>
<string name="style_brightness_title">Kecerahan tema</string>
<string name="download_attachment">Unduh %1$s</string>
<string name="contribute">Kontribusi</string>
<string name="download">Unduh</string>
<string name="html_text_dev_feature_upgrade">Ingatlah untuk selalu memperbarui aplikasi Anda.</string>
<string name="error_remove_file">Timbul galat saat menghapus data berkas.</string>
<string name="error_duplicate_file">Data berkas sudah ada.</string>
<string name="error_upload_file">Timbul galat saat mengunggah data berkas.</string>
<string name="error_file_to_big">File yang Anda unggah terlalu besar.</string>
<string name="content_description_otp_information">Info sandi satu kali</string>
</resources> </resources>

View File

@@ -26,7 +26,7 @@
<string name="add_entry">Aggiungi elemento</string> <string name="add_entry">Aggiungi elemento</string>
<string name="add_group">Aggiungi gruppo</string> <string name="add_group">Aggiungi gruppo</string>
<string name="encryption_algorithm">Algoritmo di cifratura</string> <string name="encryption_algorithm">Algoritmo di cifratura</string>
<string name="app_timeout">Scadenza app</string> <string name="app_timeout">Timeout</string>
<string name="app_timeout_summary">Tempo di inattività prima del blocco del database</string> <string name="app_timeout_summary">Tempo di inattività prima del blocco del database</string>
<string name="application">App</string> <string name="application">App</string>
<string name="menu_app_settings">Impostazioni app</string> <string name="menu_app_settings">Impostazioni app</string>
@@ -142,17 +142,6 @@
<string name="education_unlock_summary">Inserisci la password o il file chiave per sbloccare la base di dati. <string name="education_unlock_summary">Inserisci la password o il file chiave per sbloccare la base di dati.
\n \n
\nEseguire il backup del file del database in un luogo sicuro dopo ogni modifica.</string> \nEseguire il backup del file del database in un luogo sicuro dopo ogni modifica.</string>
<string-array name="timeout_options">
<item>5 secondi</item>
<item>10 secondi</item>
<item>20 secondi</item>
<item>30 secondi</item>
<item>1 minuto</item>
<item>5 minuti</item>
<item>15 minuti</item>
<item>30 minuti</item>
<item>Mai</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Piccolo</item> <item>Piccolo</item>
<item>Medio</item> <item>Medio</item>
@@ -165,7 +154,6 @@
<string name="extended_ASCII">ASCII esteso</string> <string name="extended_ASCII">ASCII esteso</string>
<string name="error_nokeyfile">Seleziona un file chiave.</string> <string name="error_nokeyfile">Seleziona un file chiave.</string>
<string name="error_autofill_enable_service">Attivazione del servizio di auto-completamento fallita.</string> <string name="error_autofill_enable_service">Attivazione del servizio di auto-completamento fallita.</string>
<string name="error_move_folder_in_itself">Non puoi spostare un gruppo in se stesso.</string>
<string name="menu_form_filling_settings">Riempimento campi</string> <string name="menu_form_filling_settings">Riempimento campi</string>
<string name="menu_copy">Copia</string> <string name="menu_copy">Copia</string>
<string name="menu_move">Sposta</string> <string name="menu_move">Sposta</string>
@@ -534,7 +522,7 @@
<string name="advanced_unlock_prompt_store_credential_message">Attenzione: dovrai sempre ricordare la password principale anche se usi lo sblocco avanzato.</string> <string name="advanced_unlock_prompt_store_credential_message">Attenzione: dovrai sempre ricordare la password principale anche se usi lo sblocco avanzato.</string>
<string name="advanced_unlock_prompt_store_credential_title">Riconoscimento con sblocco avanzato</string> <string name="advanced_unlock_prompt_store_credential_title">Riconoscimento con sblocco avanzato</string>
<string name="device_credential">Credenziali del dispositivo</string> <string name="device_credential">Credenziali del dispositivo</string>
<string name="credential_before_click_advanced_unlock_button">Inserisci la password, quindi clicca sull\'icona \"Sblocco avanzato\".</string> <string name="credential_before_click_advanced_unlock_button">Inserisci la password, poi clicca questo pulsante.</string>
<string name="advanced_unlock_scanning_error">Errore sblocco avanzato: %1$s</string> <string name="advanced_unlock_scanning_error">Errore sblocco avanzato: %1$s</string>
<string name="advanced_unlock_prompt_extract_credential_title">Apri il database con lo sblocco avanzato</string> <string name="advanced_unlock_prompt_extract_credential_title">Apri il database con lo sblocco avanzato</string>
<string name="open_advanced_unlock_prompt_unlock_database">Autentica con lo sblocco avanzato per sbloccare il database</string> <string name="open_advanced_unlock_prompt_unlock_database">Autentica con lo sblocco avanzato per sbloccare il database</string>
@@ -546,7 +534,7 @@
<string name="autofill_inline_suggestions_summary">Mostra i suggerimenti di riempimento campi in una tastiera compatibile</string> <string name="autofill_inline_suggestions_summary">Mostra i suggerimenti di riempimento campi in una tastiera compatibile</string>
<string name="autofill_inline_suggestions_title">Suggerimenti in linea</string> <string name="autofill_inline_suggestions_title">Suggerimenti in linea</string>
<string name="warning_database_revoked">L\'accesso al file è stato revocato dal file manager, chiudi il database e riaprilo dalla sua posizione originale.</string> <string name="warning_database_revoked">L\'accesso al file è stato revocato dal file manager, chiudi il database e riaprilo dalla sua posizione originale.</string>
<string name="warning_database_info_changed_options">Sovrascrivi le modifiche esterne salvano il database o ricaricalo con gli ultimi cambiamenti.</string> <string name="warning_database_info_changed_options">Sovrascrivi le modifiche esterne salvando il database o ricaricalo con gli ultimi cambiamenti.</string>
<string name="warning_database_info_changed">I dati nel tuo database sono stati modificati al di fuori di questa app.</string> <string name="warning_database_info_changed">I dati nel tuo database sono stati modificati al di fuori di questa app.</string>
<string name="menu_reload_database">Ricarica database</string> <string name="menu_reload_database">Ricarica database</string>
<string name="error_otp_type">Il tipo di OTP esistente non è riconosciuto da questo modulo, la sua convalida potrebbe non generare più correttamente il token.</string> <string name="error_otp_type">Il tipo di OTP esistente non è riconosciuto da questo modulo, la sua convalida potrebbe non generare più correttamente il token.</string>
@@ -556,4 +544,23 @@
<string name="unit_kibibyte">KiB</string> <string name="unit_kibibyte">KiB</string>
<string name="unit_byte">B</string> <string name="unit_byte">B</string>
<string name="content_description_otp_information">Info password usa e getta</string> <string name="content_description_otp_information">Info password usa e getta</string>
<string name="icon_section_custom">Personalizzato</string>
<string name="icon_section_standard">Standard</string>
<string name="style_brightness_summary">Scegli un tema scuro o chiaro</string>
<string name="style_brightness_title">Luminosità del tema</string>
<string name="error_remove_file">Si è verificato un errore durante la rimozione del file.</string>
<string name="error_duplicate_file">Il file esiste già.</string>
<string name="error_upload_file">Si è verificato un errore durante il caricamento del file.</string>
<string name="error_file_to_big">Il file che stai cercando di caricare è troppo grande.</string>
<string name="error_start_database_action">Si è verificato un errore durante l\'esecuzione di una azione sul database.</string>
<string name="properties">Proprietà</string>
<string name="error_export_app_properties">Errore durante l\'esportazione delle proprietà dell\'app</string>
<string name="success_export_app_properties">Proprietà dell\'app esportate</string>
<string name="error_import_app_properties">Errore durante l\'importazione delle proprietà dell\'app</string>
<string name="success_import_app_properties">Proprietà dell\'app importate</string>
<string name="description_app_properties">Proprietà di KeePassDX per gestire le impostazioni dell\'app</string>
<string name="export_app_properties_summary">Crea un file in cui esportare le proprietà dell\'app</string>
<string name="export_app_properties_title">Esporta le proprietà dell\'app</string>
<string name="import_app_properties_summary">Seleziona un file da cui importare le proprietà dell\'app</string>
<string name="import_app_properties_title">Importa le proprietà dell\'app</string>
</resources> </resources>

View File

@@ -129,17 +129,6 @@
<string name="uppercase">רישית</string> <string name="uppercase">רישית</string>
<string name="version_label">גרסה %1$s</string> <string name="version_label">גרסה %1$s</string>
<string name="education_unlock_summary">הזן סיסמה ו/או קובץ מפתח כדי לפתוח את מסד הנתונים.</string> <string name="education_unlock_summary">הזן סיסמה ו/או קובץ מפתח כדי לפתוח את מסד הנתונים.</string>
<string-array name="timeout_options">
<item>5 שניות</item>
<item>10 שניות</item>
<item>20 שניות</item>
<item>30 שניות</item>
<item>דקה אחת</item>
<item>5 דקות</item>
<item>15 דקות</item>
<item>30 דקות</item>
<item>אף פעם</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>קטן</item> <item>קטן</item>
<item>בינוני</item> <item>בינוני</item>

View File

@@ -57,7 +57,7 @@
<string name="content_description_keyfile_checkbox">キーファイルのチェックボックス</string> <string name="content_description_keyfile_checkbox">キーファイルのチェックボックス</string>
<string name="content_description_repeat_toggle_password_visibility">パスワードの可視性を再び切り替える</string> <string name="content_description_repeat_toggle_password_visibility">パスワードの可視性を再び切り替える</string>
<string name="content_description_entry_icon">エントリーのアイコン</string> <string name="content_description_entry_icon">エントリーのアイコン</string>
<string name="validate">承認</string> <string name="validate">検証</string>
<string name="discard_changes">変更を破棄しますか?</string> <string name="discard_changes">変更を破棄しますか?</string>
<string name="discard">破棄</string> <string name="discard">破棄</string>
<string name="entry_password_generator">パスワード生成機能</string> <string name="entry_password_generator">パスワード生成機能</string>
@@ -80,13 +80,13 @@
<string name="entry_cancel">キャンセル</string> <string name="entry_cancel">キャンセル</string>
<string name="entry_notes">備考</string> <string name="entry_notes">備考</string>
<string name="entry_confpassword">パスワードを確認</string> <string name="entry_confpassword">パスワードを確認</string>
<string name="entry_created">作成日</string> <string name="entry_created">作成日</string>
<string name="entry_expires">有効期限</string> <string name="entry_expires">有効期限</string>
<string name="entry_UUID">UUID</string> <string name="entry_UUID">UUID</string>
<string name="entry_history">履歴</string> <string name="entry_history">履歴</string>
<string name="entry_attachments">添付ファイル</string> <string name="entry_attachments">添付ファイル</string>
<string name="entry_keyfile">キーファイル</string> <string name="entry_keyfile">キーファイル</string>
<string name="entry_modified">変更日</string> <string name="entry_modified">変更日</string>
<string name="entry_not_found">エントリーのデータが見つかりませんでした。</string> <string name="entry_not_found">エントリーのデータが見つかりませんでした。</string>
<string name="entry_password">パスワード</string> <string name="entry_password">パスワード</string>
<string name="save">保存</string> <string name="save">保存</string>
@@ -120,7 +120,6 @@
<string name="error_label_exists">このラベルはすでに存在します。</string> <string name="error_label_exists">このラベルはすでに存在します。</string>
<string name="error_wrong_length">[長さ] フィールドには正の整数を入力してください。</string> <string name="error_wrong_length">[長さ] フィールドには正の整数を入力してください。</string>
<string name="error_autofill_enable_service">自動入力サービスを有効にできませんでした。</string> <string name="error_autofill_enable_service">自動入力サービスを有効にできませんでした。</string>
<string name="error_move_folder_in_itself">グループを自分自身の中に移動することはできません。</string>
<string name="error_move_entry_here">ここではエントリーを移動することはできません。</string> <string name="error_move_entry_here">ここではエントリーを移動することはできません。</string>
<string name="error_copy_entry_here">ここではエントリーをコピーすることはできません。</string> <string name="error_copy_entry_here">ここではエントリーをコピーすることはできません。</string>
<string name="error_copy_group_here">ここではグループをコピーすることはできません。</string> <string name="error_copy_group_here">ここではグループをコピーすることはできません。</string>
@@ -185,7 +184,7 @@
<string name="menu_open">開く</string> <string name="menu_open">開く</string>
<string name="menu_search">検索</string> <string name="menu_search">検索</string>
<string name="menu_showpass">パスワードを表示</string> <string name="menu_showpass">パスワードを表示</string>
<string name="menu_url">URL に移動</string> <string name="menu_url">URL にアクセス</string>
<string name="menu_file_selection_read_only">書き込み禁止</string> <string name="menu_file_selection_read_only">書き込み禁止</string>
<string name="menu_open_file_read_and_write">変更可能</string> <string name="menu_open_file_read_and_write">変更可能</string>
<string name="menu_empty_recycle_bin">ゴミ箱を空にする</string> <string name="menu_empty_recycle_bin">ゴミ箱を空にする</string>
@@ -222,7 +221,7 @@
<string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string> <string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string>
<string name="root">ルート</string> <string name="root">ルート</string>
<string name="encryption_explanation">すべてのデータで使用するデータベース暗号化アルゴリズムです。</string> <string name="encryption_explanation">すべてのデータで使用するデータベース暗号化アルゴリズムです。</string>
<string name="kdf_explanation">暗号化アルゴリズム用の鍵を生成するために、マスターキーはランダムなソルトを加える鍵導出関数を使用して変換されます。</string> <string name="kdf_explanation">暗号化アルゴリズム用の鍵を生成するために、マスターキーはランダムなソルト付き鍵導出関数を使用して変換されます。</string>
<string name="rounds">変換ラウンド</string> <string name="rounds">変換ラウンド</string>
<string name="rounds_explanation">変換ラウンドを増やすことでブルート フォース攻撃に対する保護が強化されますが、読み込みと保存が本当に遅くなる可能性があります。</string> <string name="rounds_explanation">変換ラウンドを増やすことでブルート フォース攻撃に対する保護が強化されますが、読み込みと保存が本当に遅くなる可能性があります。</string>
<string name="memory_usage">メモリ使用量</string> <string name="memory_usage">メモリ使用量</string>
@@ -243,7 +242,7 @@
<string name="sort_title">タイトル</string> <string name="sort_title">タイトル</string>
<string name="sort_username">ユーザー名</string> <string name="sort_username">ユーザー名</string>
<string name="sort_creation_time">作成日</string> <string name="sort_creation_time">作成日</string>
<string name="sort_last_modify_time">変更日</string> <string name="sort_last_modify_time">変更日</string>
<string name="sort_last_access_time">最終アクセス</string> <string name="sort_last_access_time">最終アクセス</string>
<string name="special">特殊文字</string> <string name="special">特殊文字</string>
<string name="search">検索</string> <string name="search">検索</string>
@@ -452,7 +451,7 @@
<string name="education_donation_title">参加</string> <string name="education_donation_title">参加</string>
<string name="education_donation_summary">安定性やセキュリティを向上させ、機能を追加することを支援します。</string> <string name="education_donation_summary">安定性やセキュリティを向上させ、機能を追加することを支援します。</string>
<string name="html_text_ad_free">多くのパスワード管理アプリとは異なり、このアプリは&lt;strong&gt;広告なし&lt;/strong&gt;かつ&lt;strong&gt;コピーレフトの自由ソフトウェア&lt;/strong&gt;です。どのバージョンを使っても、サーバー上で個人情報が収集されることはありません。</string> <string name="html_text_ad_free">多くのパスワード管理アプリとは異なり、このアプリは&lt;strong&gt;広告なし&lt;/strong&gt;かつ&lt;strong&gt;コピーレフトの自由ソフトウェア&lt;/strong&gt;です。どのバージョンを使っても、サーバー上で個人情報が収集されることはありません。</string>
<string name="html_text_buy_pro">pro バージョンを購入すると、この&lt;strong&gt;ビジュアル スタイル&lt;/strong&gt;にアクセスできるようになり、また&lt;strong&gt;コミュニティ プロジェクトの実現&lt;/strong&gt;を特に支援できます。</string> <string name="html_text_buy_pro">Pro バージョンを購入すると、この<strong>ビジュアル スタイル</strong>にアクセスできるようになり、また<strong>コミュニティ プロジェクトの実現</strong>を特に支援できます。</string>
<string name="html_text_feature_generosity">この&lt;strong&gt;ビジュアル スタイル&lt;/strong&gt;はあなたの厚意により利用可能となります。</string> <string name="html_text_feature_generosity">この&lt;strong&gt;ビジュアル スタイル&lt;/strong&gt;はあなたの厚意により利用可能となります。</string>
<string name="html_text_donation">自由を維持し活発に開発し続けるために、私たちはあなたの&lt;strong&gt;貢献&lt;/strong&gt;に期待しています。</string> <string name="html_text_donation">自由を維持し活発に開発し続けるために、私たちはあなたの&lt;strong&gt;貢献&lt;/strong&gt;に期待しています。</string>
<string name="html_text_dev_feature">この機能は&lt;strong&gt;開発中&lt;/strong&gt;であり、早期に提供するにはあなたの&lt;strong&gt;貢献&lt;/strong&gt;が必要です。</string> <string name="html_text_dev_feature">この機能は&lt;strong&gt;開発中&lt;/strong&gt;であり、早期に提供するにはあなたの&lt;strong&gt;貢献&lt;/strong&gt;が必要です。</string>
@@ -470,17 +469,6 @@
<string name="download_progression">進行中:%1$d%%</string> <string name="download_progression">進行中:%1$d%%</string>
<string name="download_finalization">終了しています…</string> <string name="download_finalization">終了しています…</string>
<string name="download_complete">完了しました!</string> <string name="download_complete">完了しました!</string>
<string-array name="timeout_options">
<item>5秒</item>
<item>10秒</item>
<item>20秒</item>
<item>30秒</item>
<item>1分</item>
<item>5分</item>
<item>15分</item>
<item>30分</item>
<item>なし</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item></item> <item></item>
<item></item> <item></item>
@@ -516,7 +504,7 @@
<string name="device_credential_unlock_enable_summary">デバイス認証情報を使用してデータベースを開くことができるようにします</string> <string name="device_credential_unlock_enable_summary">デバイス認証情報を使用してデータベースを開くことができるようにします</string>
<string name="device_credential_unlock_enable_title">デバイス認証情報によるロック解除</string> <string name="device_credential_unlock_enable_title">デバイス認証情報によるロック解除</string>
<string name="device_credential">デバイス認証情報</string> <string name="device_credential">デバイス認証情報</string>
<string name="credential_before_click_advanced_unlock_button">パスワードを入力し、[高度なロック解除] ボタンをタップします。</string> <string name="credential_before_click_advanced_unlock_button">パスワードを入力し、このボタンをタップします。</string>
<string name="advanced_unlock_prompt_not_initialized">高度なロック解除プロンプトを初期化できません。</string> <string name="advanced_unlock_prompt_not_initialized">高度なロック解除プロンプトを初期化できません。</string>
<string name="advanced_unlock_scanning_error">高度なロック解除のエラー:%1$s</string> <string name="advanced_unlock_scanning_error">高度なロック解除のエラー:%1$s</string>
<string name="advanced_unlock_invalid_key">高度なロック解除用の鍵が読み取れません。削除してロック解除の手順をやり直してください。</string> <string name="advanced_unlock_invalid_key">高度なロック解除用の鍵が読み取れません。削除してロック解除の手順をやり直してください。</string>
@@ -549,6 +537,26 @@
<string name="unit_mebibyte">MiB</string> <string name="unit_mebibyte">MiB</string>
<string name="unit_kibibyte">KiB</string> <string name="unit_kibibyte">KiB</string>
<string name="unit_byte">B</string> <string name="unit_byte">B</string>
<string name="download_canceled">キャンセルされました!</string> <string name="download_canceled">キャンセルました</string>
<string name="error_otp_type">既存の OTP 形式がこのフォームで認識されない場合、検証によってトークンが正しく生成されなくなる可能性があります。</string> <string name="error_otp_type">既存の OTP の種類がこのフォームで認識されていないため、フォームの検証によってトークンが正しく生成されなくなる可能性があります。</string>
<string name="icon_section_custom">カスタム</string>
<string name="icon_section_standard">標準</string>
<string name="style_brightness_summary">ライト / ダークテーマを選択します</string>
<string name="style_brightness_title">テーマの明るさ</string>
<string name="error_remove_file">ファイルデータの削除中にエラーが発生しました。</string>
<string name="error_duplicate_file">ファイルデータはすでに存在します。</string>
<string name="error_upload_file">ファイルデータのアップロード中にエラーが発生しました。</string>
<string name="error_file_to_big">アップロードしようとしているファイルが大きすぎます。</string>
<string name="content_description_otp_information">ワンタイムパスワードについて</string>
<string name="import_app_properties_summary">アプリのプロパティをインポートするファイルを選択します</string>
<string name="import_app_properties_title">アプリのプロパティをインポートする</string>
<string name="properties">プロパティ</string>
<string name="error_export_app_properties">アプリのプロパティのエクスポート時にエラーが発生しました</string>
<string name="success_export_app_properties">アプリのプロパティをエクスポートしました</string>
<string name="error_import_app_properties">アプリのプロパティのインポート時にエラーが発生しました</string>
<string name="success_import_app_properties">アプリのプロパティをインポートしました</string>
<string name="description_app_properties">アプリの設定を管理する KeePassDX のプロパティ</string>
<string name="export_app_properties_summary">アプリのプロパティをエクスポートするファイルを作成します</string>
<string name="export_app_properties_title">アプリのプロパティをエクスポートする</string>
<string name="error_start_database_action">データベースに対するアクションの実行中にエラーが発生しました。</string>
</resources> </resources>

View File

@@ -78,7 +78,6 @@
<string name="error_string_key">각 항목은 필드 이름을 가져야 합니다.</string> <string name="error_string_key">각 항목은 필드 이름을 가져야 합니다.</string>
<string name="error_wrong_length">\"길이\" 필드에는 양수를 입력하십시오.</string> <string name="error_wrong_length">\"길이\" 필드에는 양수를 입력하십시오.</string>
<string name="error_autofill_enable_service">자동 채우기 서비스를 활성화할 수 없습니다.</string> <string name="error_autofill_enable_service">자동 채우기 서비스를 활성화할 수 없습니다.</string>
<string name="error_move_folder_in_itself">그룹을 자신에게 옮길 수 없습니다.</string>
<string name="field_name">필드 이름</string> <string name="field_name">필드 이름</string>
<string name="field_value">필드 값</string> <string name="field_value">필드 값</string>
<string name="file_not_found_content">파일을 찾을 수 없습니다. 파일 탐색기에서 열리는지 확인해 주세요.</string> <string name="file_not_found_content">파일을 찾을 수 없습니다. 파일 탐색기에서 열리는지 확인해 주세요.</string>

View File

@@ -129,17 +129,6 @@
<string name="uppercase">Lielie burti</string> <string name="uppercase">Lielie burti</string>
<string name="version_label">Versija %1$s</string> <string name="version_label">Versija %1$s</string>
<string name="education_unlock_summary">Ievadiet paroli/atslēgas failu, lai atbloķētu savu datu bāzi.</string> <string name="education_unlock_summary">Ievadiet paroli/atslēgas failu, lai atbloķētu savu datu bāzi.</string>
<string-array name="timeout_options">
<item>5 sekundes</item>
<item>10 sekundes</item>
<item>20 sekundes</item>
<item>30 sekundes</item>
<item>1 minūte</item>
<item>5 minūtes</item>
<item>15 minūtes</item>
<item>30 minūtes</item>
<item>Nekad</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Mazs</item> <item>Mazs</item>
<item>Vidējs</item> <item>Vidējs</item>

View File

@@ -128,7 +128,6 @@
<string name="error_create_database_file">ഈ പാസ്‌വേഡും കീഫയലും ഉപയോഗിച്ച് ഡാറ്റാബേസ് സൃഷ്ടിക്കാൻ കഴിയില്ല.</string> <string name="error_create_database_file">ഈ പാസ്‌വേഡും കീഫയലും ഉപയോഗിച്ച് ഡാറ്റാബേസ് സൃഷ്ടിക്കാൻ കഴിയില്ല.</string>
<string name="error_copy_group_here">നിങ്ങൾക്ക് ഇവിടെ ഒരു ഗ്രൂപ്പ് പകർത്താൻ കഴിയില്ല.</string> <string name="error_copy_group_here">നിങ്ങൾക്ക് ഇവിടെ ഒരു ഗ്രൂപ്പ് പകർത്താൻ കഴിയില്ല.</string>
<string name="error_copy_entry_here">നിങ്ങൾക്ക് ഇവിടെ ഒരു എൻ‌ട്രി പകർ‌ത്താൻ‌ കഴിയില്ല.</string> <string name="error_copy_entry_here">നിങ്ങൾക്ക് ഇവിടെ ഒരു എൻ‌ട്രി പകർ‌ത്താൻ‌ കഴിയില്ല.</string>
<string name="error_move_folder_in_itself">നിങ്ങൾക്ക് ഒരു ഗ്രൂപ്പിനെ അതിലേക്ക് നീക്കാൻ കഴിയില്ല.</string>
<string name="error_label_exists">ഈ ലേബൽ ഇതിനകം നിലവിലുണ്ട്.</string> <string name="error_label_exists">ഈ ലേബൽ ഇതിനകം നിലവിലുണ്ട്.</string>
<string name="error_string_key">ഓരോ സ്ട്രിംഗിനും ഒരു ഫീൽഡ് നാമം ഉണ്ടായിരിക്കണം.</string> <string name="error_string_key">ഓരോ സ്ട്രിംഗിനും ഒരു ഫീൽഡ് നാമം ഉണ്ടായിരിക്കണം.</string>
<string name="error_rounds_too_large">\"Transformation rounds\" too high. Setting to 2147483648.</string> <string name="error_rounds_too_large">\"Transformation rounds\" too high. Setting to 2147483648.</string>

View File

@@ -78,7 +78,6 @@
<string name="error_string_key">Hver streng må ha et feltnavn.</string> <string name="error_string_key">Hver streng må ha et feltnavn.</string>
<string name="error_wrong_length">Skriv inn et positivt heltall i \"Lengde\" feltet.</string> <string name="error_wrong_length">Skriv inn et positivt heltall i \"Lengde\" feltet.</string>
<string name="error_autofill_enable_service">Autofyll-tjenesten kan ikke skrus på.</string> <string name="error_autofill_enable_service">Autofyll-tjenesten kan ikke skrus på.</string>
<string name="error_move_folder_in_itself">Kan ikke flytte gruppe inn i seg selv.</string>
<string name="field_name">Feltnavn</string> <string name="field_name">Feltnavn</string>
<string name="field_value">Feltverdi</string> <string name="field_value">Feltverdi</string>
<string name="file_not_found_content">Fant ikke filen. Prøv å åpne den fra din innholdsleverandør.</string> <string name="file_not_found_content">Fant ikke filen. Prøv å åpne den fra din innholdsleverandør.</string>
@@ -453,4 +452,19 @@
<string name="error_string_type">Denne teksten samsvarer ikke med det valgte elementet.</string> <string name="error_string_type">Denne teksten samsvarer ikke med det valgte elementet.</string>
<string name="html_about_contribution">For å <strong> beholde vår frihet </strong>, <strong> fikse feil </strong>, <strong> legge til funksjoner </strong> og <strong> være alltid aktiv </strong>, stoler vi på ditt <strong> bidrag </strong>.</string> <string name="html_about_contribution">For å <strong> beholde vår frihet </strong>, <strong> fikse feil </strong>, <strong> legge til funksjoner </strong> og <strong> være alltid aktiv </strong>, stoler vi på ditt <strong> bidrag </strong>.</string>
<string name="content_description_repeat_toggle_password_visibility">Gjenta for å skifte passordsynlighet</string> <string name="content_description_repeat_toggle_password_visibility">Gjenta for å skifte passordsynlighet</string>
<string name="icon_section_custom">Egendefinert</string>
<string name="icon_section_standard">Forvalg</string>
<string name="style_brightness_summary">Velg lys eller mørk drakt</string>
<string name="style_brightness_title">Draktlysstyrke</string>
<string name="backspace">Rettetast</string>
<string name="keyboard_save_search_info_title">Lagre delt info</string>
<string name="keyboard_search_share_title">Søk i delt info</string>
<string name="device_keyboard_setting_title">Innstillinger for enhetens tastatur</string>
<string name="notification">Merknad</string>
<string name="data">Data</string>
<string name="advanced_unlock_tap_delete">Trykk for å slette avanserte opplåsningsnøkler</string>
<string name="clipboard_explanation_summary">Kopier inntastingsfelter ved bruk av utklippstavlen på din enhet</string>
<string name="autofill_preference_title">Autofyllings-innstillinger</string>
<string name="warning_sure_remove_data">Fjern denne dataen uansett\?</string>
<string name="warning_permanently_delete_nodes">Slett valgte noder for godt\?</string>
</resources> </resources>

View File

@@ -26,7 +26,7 @@
<string name="add_entry">Item toevoegen</string> <string name="add_entry">Item toevoegen</string>
<string name="add_group">Groep toevoegen</string> <string name="add_group">Groep toevoegen</string>
<string name="encryption_algorithm">Algoritme</string> <string name="encryption_algorithm">Algoritme</string>
<string name="app_timeout">App-time-out</string> <string name="app_timeout">Time-out</string>
<string name="app_timeout_summary">Tijd tot vergrendeling bij inactiviteit</string> <string name="app_timeout_summary">Tijd tot vergrendeling bij inactiviteit</string>
<string name="application">App</string> <string name="application">App</string>
<string name="menu_app_settings">App-instellingen</string> <string name="menu_app_settings">App-instellingen</string>
@@ -125,17 +125,6 @@
<string name="education_unlock_summary">Voer het wachtwoord en/of sleutelbestand in om je database te ontgrendelen. <string name="education_unlock_summary">Voer het wachtwoord en/of sleutelbestand in om je database te ontgrendelen.
\n \n
\nMaak na elke aanpassing een kopie van je .kdbx-bestand op een veilige locatie.</string> \nMaak na elke aanpassing een kopie van je .kdbx-bestand op een veilige locatie.</string>
<string-array name="timeout_options">
<item>5 seconden</item>
<item>10 seconden</item>
<item>20 seconden</item>
<item>30 seconden</item>
<item>1 minuut</item>
<item>5 minuten</item>
<item>15 minuten</item>
<item>30 minuten</item>
<item>Nooit</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Klein</item> <item>Klein</item>
<item>Medium</item> <item>Medium</item>
@@ -154,7 +143,6 @@
<string name="error_load_database_KDF_memory">De sleutel kan niet worden geladen. Probeer om het \"geheugengebruik\" van KDF te verminderen.</string> <string name="error_load_database_KDF_memory">De sleutel kan niet worden geladen. Probeer om het \"geheugengebruik\" van KDF te verminderen.</string>
<string name="error_string_key">Elke zin moet een veldnaam bevatten.</string> <string name="error_string_key">Elke zin moet een veldnaam bevatten.</string>
<string name="error_autofill_enable_service">De dienst automatisch aanvullen kan niet worden ingeschakeld.</string> <string name="error_autofill_enable_service">De dienst automatisch aanvullen kan niet worden ingeschakeld.</string>
<string name="error_move_folder_in_itself">Een groep kan niet naar zichzelf worden verplaatst.</string>
<string name="field_name">Veldnaam</string> <string name="field_name">Veldnaam</string>
<string name="field_value">Veldwaarde</string> <string name="field_value">Veldwaarde</string>
<string name="file_not_found_content">Bestand niet gevonden. Probeer opnieuw te openen via bestandsbeheer.</string> <string name="file_not_found_content">Bestand niet gevonden. Probeer opnieuw te openen via bestandsbeheer.</string>
@@ -526,7 +514,7 @@
<string name="advanced_unlock_tap_delete">Tik om geavanceerde ontgrendelingstoetsen te verwijderen</string> <string name="advanced_unlock_tap_delete">Tik om geavanceerde ontgrendelingstoetsen te verwijderen</string>
<string name="content">Inhoud</string> <string name="content">Inhoud</string>
<string name="device_credential">Apparaatreferentie</string> <string name="device_credential">Apparaatreferentie</string>
<string name="credential_before_click_advanced_unlock_button">Typ het wachtwoord en klik vervolgens op de knop \"Geavanceerd ontgrendelen\".</string> <string name="credential_before_click_advanced_unlock_button">Typ het wachtwoord en klik vervolgens op deze knop.</string>
<string name="advanced_unlock_prompt_not_initialized">Kan geavanceerde ontgrendelingsprompt niet initialiseren.</string> <string name="advanced_unlock_prompt_not_initialized">Kan geavanceerde ontgrendelingsprompt niet initialiseren.</string>
<string name="advanced_unlock_scanning_error">Geavanceerde ontgrendelingsfout: %1$s</string> <string name="advanced_unlock_scanning_error">Geavanceerde ontgrendelingsfout: %1$s</string>
<string name="advanced_unlock_not_recognized">Kan geavanceerde ontgrendelingsafdruk niet herkennen</string> <string name="advanced_unlock_not_recognized">Kan geavanceerde ontgrendelingsafdruk niet herkennen</string>
@@ -554,4 +542,24 @@
<string name="error_rebuild_list">Kan de lijst niet correct opnieuw opbouwen.</string> <string name="error_rebuild_list">Kan de lijst niet correct opnieuw opbouwen.</string>
<string name="error_database_uri_null">Database-URI kan niet worden opgehaald.</string> <string name="error_database_uri_null">Database-URI kan niet worden opgehaald.</string>
<string name="error_otp_type">Het bestaande OTP-type wordt niet herkend door dit formulier, de validatie ervan genereert het token mogelijk niet langer.</string> <string name="error_otp_type">Het bestaande OTP-type wordt niet herkend door dit formulier, de validatie ervan genereert het token mogelijk niet langer.</string>
<string name="icon_section_custom">Aangepast</string>
<string name="icon_section_standard">Standaard</string>
<string name="style_brightness_summary">Selecteer lichte of donkere thema\'s</string>
<string name="style_brightness_title">Helderheid van het thema</string>
<string name="error_remove_file">Er is een fout opgetreden bij het verwijderen van de bestandsgegevens.</string>
<string name="error_duplicate_file">De bestandsgegevens bestaan al.</string>
<string name="error_upload_file">Er is een fout opgetreden bij het uploaden van de bestandsgegevens.</string>
<string name="error_file_to_big">Het bestand dat je probeert te uploaden, is te groot.</string>
<string name="content_description_otp_information">Eenmalig wachtwoord-informatie</string>
<string name="properties">Eigenschappen</string>
<string name="error_export_app_properties">Fout tijdens het exporteren van app-eigenschappen</string>
<string name="success_export_app_properties">App-eigenschappen geëxporteerd</string>
<string name="error_import_app_properties">Fout tijdens het importeren van app-eigenschappen</string>
<string name="success_import_app_properties">App-eigenschappen geïmporteerd</string>
<string name="description_app_properties">KeePassDX-eigenschappen om app-instellingen te beheren</string>
<string name="export_app_properties_summary">Maak een bestand om app-eigenschappen te exporteren</string>
<string name="export_app_properties_title">App-eigenschappen exporteren</string>
<string name="import_app_properties_summary">Selecteer een bestand om app-eigenschappen te importeren</string>
<string name="import_app_properties_title">App-eigenschappen importeren</string>
<string name="error_start_database_action">Er is een fout opgetreden bij het uitvoeren van een actie op de database.</string>
</resources> </resources>

View File

@@ -120,17 +120,6 @@
<string name="uppercase">Store bokstavar</string> <string name="uppercase">Store bokstavar</string>
<string name="version_label">Utgåve %1$s</string> <string name="version_label">Utgåve %1$s</string>
<string name="education_unlock_summary">Skriv inn passordet og/eller nøkkelfil for å låsa opp databasen.</string> <string name="education_unlock_summary">Skriv inn passordet og/eller nøkkelfil for å låsa opp databasen.</string>
<string-array name="timeout_options">
<item>5 sekund</item>
<item>10 sekund</item>
<item>20 sekund</item>
<item>30 sekund</item>
<item>1 minutt</item>
<item>5 minutt</item>
<item>15 minutt</item>
<item>30 minutt</item>
<item>Aldri</item>
</string-array>
<string-array name="list_size_options"> <string-array name="list_size_options">
<item>Liten</item> <item>Liten</item>
<item>Middels</item> <item>Middels</item>

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