Compare commits

...

688 Commits
3.4.0 ... 4.0.2

Author SHA1 Message Date
J-Jamet
798c95d8a8 Merge branch 'release/4.0.2' 2023-09-11 21:55:48 +02:00
J-Jamet
ef77c2acfb fix: Add buggy method comment #1638 2023-09-11 21:38:12 +02:00
J-Jamet
11a98267a2 fix: Upgrade to 4.0.2 2023-09-11 21:17:18 +02:00
J-Jamet
b2aa1155d0 fix: Autofill authentication 2023-09-11 21:13:26 +02:00
J-Jamet
5d6aac2d1b Merge tag '4.0.1' into develop
4.0.1
2023-09-10 11:07:13 +02:00
J-Jamet
c6723ecd4e Merge branch 'release/4.0.1' 2023-09-10 11:07:06 +02:00
J-Jamet
838c8f48d3 fix: Translation for themes #1631 2023-09-10 10:53:43 +02:00
J-Jamet
65229fae1f fix: Lock button in Autofill settings and Magikeyboard settings #1630 2023-09-10 10:26:01 +02:00
J-Jamet
f25ea89160 fix: Add CHANGELOG link 2023-09-10 09:57:56 +02:00
J-Jamet
18401d5d1e fix: Upgrade to 4.0.1 2023-09-10 09:50:26 +02:00
J-Jamet
1f2cf08108 fix: back lock 2023-09-10 09:43:08 +02:00
J-Jamet
74e86badba Merge tag '4.0.0' into develop
4.0.0
2023-09-09 21:34:37 +02:00
J-Jamet
31444a823e Merge branch 'release/4.0.0' 2023-09-09 21:34:28 +02:00
J-Jamet
068933f0fb fix: fastlane changelog version 2023-09-09 21:30:51 +02:00
J-Jamet
f496711280 Replace art screen 2023-09-09 21:24:40 +02:00
J-Jamet
657d2420d6 Revert "fix: Small bug"
This reverts commit 2467721265.
2023-09-09 21:21:04 +02:00
J-Jamet
2467721265 fix: Small bug 2023-09-09 21:11:39 +02:00
J-Jamet
d37fbb9992 fix: Change screenshot 2023-09-09 21:06:33 +02:00
J-Jamet
19b8b54dae fix: Strong tag 2023-09-08 21:22:55 +02:00
J-Jamet
84328caf3c fix: Add browsers to compatibility package 2023-09-08 21:22:36 +02:00
J-Jamet
a0bdfc973a Merge branch 'translations' into develop 2023-09-08 21:16:04 +02:00
J-Jamet
1f91854490 Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2023-09-08 21:15:42 +02:00
J-Jamet
1380325b66 Upgrade to 4.0.0 2023-09-08 21:14:21 +02:00
J-Jamet
d244eef62e feat: Add recursive number of entries #874 #1327 2023-09-08 20:32:19 +02:00
Hosted Weblate
8fb8d9ed37 Merge branch 'origin/develop' into Weblate. 2023-09-08 19:35:10 +02:00
J-Jamet
8ce63cb5c5 Merge branch 'JohnVeness-master' into develop 2023-09-08 19:31:49 +02:00
J-Jamet
9ecf2ae942 Merge branch 'master' of github.com:JohnVeness/KeePassDX into JohnVeness-master 2023-09-08 19:31:07 +02:00
J-Jamet
544f7003f6 fix: Remove Lock in Autofill 2023-09-04 20:45:32 +02:00
J-Jamet
6d633c9986 fix: Autofill implementation 2023-09-04 19:16:16 +02:00
J-Jamet
1e77a42c93 fix: Remove Autofill deprecation 2023-09-03 18:41:32 +02:00
J-Jamet
d1f2641e40 fix: On back pressed deprecation 2023-09-03 12:29:45 +02:00
jonnysemon
4b8ae154cc Translated using Weblate (Arabic)
Currently translated at 97.6% (632 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2023-08-29 18:57:00 +02:00
J-Jamet
0c1aacdf83 fix: Navigation bar margin 2023-08-28 21:52:51 +02:00
J-Jamet
5f34df3549 fix: Ellipsis 2023-08-28 17:20:09 +02:00
Alexandru
f2e6aa1abb Translated using Weblate (Romanian)
Currently translated at 72.9% (472 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2023-08-28 10:53:32 +02:00
John Veness
866731df81 Correct "im/exportation" wording 2023-08-20 19:39:09 +01:00
John Veness
5d931e09d5 Changed "app properties" wording to "app settings" 2023-08-20 19:08:51 +01:00
Kunzisoft
fe17c21c01 Translated using Weblate (French)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2023-08-20 11:52:19 +02:00
J-Jamet
085941019e fix: Small bugs 2023-08-19 19:58:26 +02:00
J-Jamet
24b3758545 feat: Entry Edit Activity Fit Window 2023-08-19 19:16:55 +02:00
J-Jamet
9083f99325 feat: Entry Activity Fit Window 2023-08-19 18:15:21 +02:00
J-Jamet
2189be9267 fix: Screenshot mode 2023-08-19 17:47:49 +02:00
J-Jamet
43218eede1 feat: Group screen as fit window 2023-08-19 17:00:26 +02:00
J-Jamet
d1a176d27d fix: Small UI 2023-08-19 12:25:38 +02:00
J-Jamet
cf51af91bf feat: Logo color 2023-08-19 12:20:41 +02:00
Darin Avdeyeva
02ff1188b2 Translated using Weblate (Russian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-08-18 15:54:40 +02:00
jonnysemon
0fac9b6864 Translated using Weblate (Arabic)
Currently translated at 97.5% (631 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2023-08-17 01:55:03 +02:00
VfBFan
b550830c30 Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-15 11:47:13 +02:00
gallegonovato
6f485dd298 Translated using Weblate (Spanish)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2023-08-12 10:53:07 +02:00
solokot
b0dfde62c7 Translated using Weblate (Russian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-08-09 17:50:55 +02:00
109247019824
686dae0af6 Translated using Weblate (Bulgarian)
Currently translated at 2.9% (19 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bg/
2023-08-08 17:21:18 +02:00
Darin Avdeyeva
ee3eabe8c8 Translated using Weblate (Russian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-08-08 17:21:18 +02:00
C. Rüdinger
521c8aa6a9 Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-08 17:21:17 +02:00
solokot
66207d599f Translated using Weblate (Russian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-08-08 08:51:37 +02:00
Masowick
e0029e0c3f Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-08 08:51:37 +02:00
C. Rüdinger
3683b64721 Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-08 08:51:37 +02:00
J-Jamet
8d4a0971f9 Merge tag '4.0.0beta02' into develop
4.0.0beta02
2023-08-07 16:00:38 +02:00
J-Jamet
e4c3baa344 Merge branch 'release/4.0.0beta02' 2023-08-07 16:00:15 +02:00
J-Jamet
1e60d7e637 fix: Crash 2023-08-07 15:47:20 +02:00
J-Jamet
262b0227c1 fix: Selection switch 2023-08-07 15:27:38 +02:00
J-Jamet
226e461324 fix: Item entry alignment 2023-08-07 13:57:47 +02:00
J-Jamet
151eb26d56 fix: Tab in entry 2023-08-07 13:52:48 +02:00
J-Jamet
335e767426 fix: Small bugs 2023-08-07 13:36:19 +02:00
Hosted Weblate
d212fa180b Merge branch 'origin/develop' into Weblate. 2023-08-06 23:00:27 +02:00
Salif Mehmed
bc4ea2ec2a Translated using Weblate (Bulgarian)
Currently translated at 2.7% (18 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bg/
2023-08-06 23:00:27 +02:00
Linerly
5d8c80fc1e Translated using Weblate (Indonesian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2023-08-06 23:00:27 +02:00
Alexandru
a02714ff6e Translated using Weblate (Romanian)
Currently translated at 63.5% (411 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2023-08-06 23:00:26 +02:00
Fjuro
4bd952e223 Translated using Weblate (Czech)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2023-08-06 23:00:25 +02:00
J-Jamet
91bbc6d84e fix: tags 2023-08-06 22:59:48 +02:00
J-Jamet
6dbd16c5f6 Merge branch 'translations' into develop 2023-08-06 22:57:18 +02:00
J-Jamet
76e040c585 Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2023-08-06 22:55:11 +02:00
J-Jamet
8de6382a64 fix: update CHANGELOG 2023-08-06 22:54:06 +02:00
J-Jamet
53532ead9f fix: Dialog buttons color 2023-08-06 22:47:33 +02:00
J-Jamet
f4e6baeac2 fix: Entry path style 2023-08-06 21:46:36 +02:00
J-Jamet
5c46fdf41a fix: Replace boolean parcelable 2023-08-06 20:57:39 +02:00
J-Jamet
22073e4bbd fix: Deactivated color 2023-08-06 20:29:01 +02:00
J-Jamet
41e7376b7b feat: Cut and Copy from search CHANGELOG 2023-08-05 17:37:20 +02:00
J-Jamet
3fc26c8c4e feat: Cut and Copy from search CHANGELOG 2023-08-05 17:37:15 +02:00
J-Jamet
14f070a942 feat: Cut and Copy from search CHANGELOG 2023-08-05 17:36:13 +02:00
J-Jamet
c078bd05e2 feat: Cut and Copy from search 2023-08-05 17:29:32 +02:00
J-Jamet
8ce9757b7c fix: Error with coordinator 2023-08-05 17:06:07 +02:00
bowornsin
e028738dc2 Translated using Weblate (Thai)
Currently translated at 83.9% (543 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/th/
2023-08-03 11:08:42 +02:00
Masowick
9f4a302b72 Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-03 11:08:42 +02:00
C. Rüdinger
2ef17e0c7a Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-03 11:08:42 +02:00
VfBFan
b86a8c8633 Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-03 11:08:41 +02:00
Masowick
5a3be0853e Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-01 21:36:43 +02:00
Reza Almanda
99568db10c Translated using Weblate (Indonesian)
Currently translated at 96.1% (622 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2023-08-01 07:36:19 +02:00
Milo Ivir
bf892f5b6a Translated using Weblate (Croatian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2023-08-01 07:36:19 +02:00
Eric
8e2c7ba1f0 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2023-08-01 07:36:18 +02:00
solokot
fd3bb4b243 Translated using Weblate (Russian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-08-01 07:36:18 +02:00
André Marcelo Alvarenga
7f4a1d6896 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2023-08-01 07:36:17 +02:00
Retrial
d62734e8ac Translated using Weblate (Greek)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2023-08-01 07:36:17 +02:00
VfBFan
fbebc12a38 Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-01 07:36:16 +02:00
Masowick
3c65be2a72 Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-08-01 07:36:16 +02:00
Fjuro
a29a9f28ef Translated using Weblate (Czech)
Currently translated at 93.9% (608 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2023-08-01 07:36:15 +02:00
J-Jamet
eb14dadb3c fix: Scroll and color flickering 2023-07-31 22:28:53 +02:00
J-Jamet
8d926a306b fix: Shadow on logo 2023-07-31 22:19:55 +02:00
J-Jamet
5699359099 fix: Toolbar flickering 2023-07-31 22:07:41 +02:00
J-Jamet
e3176033dc fix: Margin bug 2023-07-31 22:03:29 +02:00
J-Jamet
9df6215c02 Merge branch 'feature/delete_search_entry_1308' into develop 2023-07-31 21:49:34 +02:00
J-Jamet
93a0e4c0a6 fix: Upgrade CHANGELOG 2023-07-31 21:49:20 +02:00
J-Jamet
f55a824cdc fix: Search in special mode 2023-07-31 21:45:35 +02:00
J-Jamet
766026d3be Merge branch 'develop' into feature/delete_search_entry_1308 2023-07-31 21:24:41 +02:00
J-Jamet
c64fc56496 Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2023-07-31 21:19:46 +02:00
J-Jamet
6e2fb21431 fix: Better theme colors 2023-07-31 21:19:16 +02:00
Jérémy JAMET
2bb70abc39 Merge pull request #1582 from MarijnS95/icon-pack-JavaVersion
Add `JavaVersion` compatible to `icon-pack`
2023-07-31 16:08:35 +02:00
Darin Avdeyeva
a6cb1dbe5c Translated using Weblate (Russian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-07-30 15:18:59 +02:00
Alexthegib
5222a72cc6 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2023-07-30 15:18:59 +02:00
Stephan Paternotte
5d3aa44545 Translated using Weblate (Dutch)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2023-07-30 15:18:58 +02:00
random r
61cfda93a5 Translated using Weblate (Italian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2023-07-30 15:18:58 +02:00
Htet Oo Hlaing
b490295b90 Translated using Weblate (Burmese)
Currently translated at 4.6% (30 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/my/
2023-07-30 11:56:12 +02:00
Alexthegib
61035ca47b Translated using Weblate (Portuguese)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2023-07-30 11:56:11 +02:00
Eric
1dc08bbfef Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2023-07-30 11:56:10 +02:00
Ihor Hordiichuk
9ea7c86da7 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2023-07-30 11:56:09 +02:00
solokot
4fa3fb86cb Translated using Weblate (Russian)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-07-30 11:56:08 +02:00
marfS2
df089f4415 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2023-07-30 11:56:07 +02:00
Matthaiks
6be12eb440 Translated using Weblate (Polish)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2023-07-30 11:56:06 +02:00
Stephan Paternotte
84efd1c497 Translated using Weblate (Dutch)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2023-07-30 11:56:05 +02:00
Kunzisoft
4817654d58 Translated using Weblate (French)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2023-07-30 11:56:04 +02:00
Deleted User
70d45e0bba Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-07-30 11:56:02 +02:00
C. Rüdinger
a4c7e3860b Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-07-30 11:56:01 +02:00
VfBFan
2a890091d7 Translated using Weblate (German)
Currently translated at 100.0% (647 of 647 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-07-30 11:56:01 +02:00
Htet Oo Hlaing
cc593e6e1f Added translation using Weblate (Burmese) 2023-07-30 04:40:47 +02:00
J-Jamet
552684fd90 feat: Delete entry from search 2023-07-29 21:40:00 +02:00
J-Jamet
a260e1d4e3 fix: Encapsulate Keyboard code 2023-07-29 18:59:16 +02:00
J-Jamet
07bbf232b6 Merge tag '4.0.0beta01' into develop
4.0.0beta01
2023-07-29 12:45:50 +02:00
J-Jamet
64b4a2f317 Merge branch 'release/4.0.0beta01' 2023-07-29 12:45:31 +02:00
J-Jamet
f0455fbca6 fix: Upgrade CHANGELOG 2023-07-29 12:35:07 +02:00
J-Jamet
2fa973f34a fix: Upgrade Gemfile and version to 4.0.0 2023-07-29 12:02:50 +02:00
J-Jamet
470497b8ef fix: Small size bug 2023-07-28 21:02:05 +02:00
J-Jamet
215cc641c3 feat: Switch keyboard only if focus #1351 2023-07-28 20:52:35 +02:00
J-Jamet
a98fa0d7bb feat: add simple theme in free version 2023-07-28 18:48:35 +02:00
J-Jamet
c70841d3e5 feat: add simple theme in free version 2023-07-28 18:47:55 +02:00
J-Jamet
3fdcd3c43d feat: Build title from webDomain 2023-07-28 18:44:13 +02:00
J-Jamet
b32bb609de fix: Notification for missing notification permission 2023-07-28 17:30:32 +02:00
J-Jamet
4db03fe034 fix: Remove URL in menu 2023-07-28 17:26:00 +02:00
J-Jamet
68430ef62c fix: Null when unbind service 2023-07-28 17:02:57 +02:00
J-Jamet
d46f48f317 fix: Special toolbar style 2023-07-28 16:43:08 +02:00
J-Jamet
e68e4dac46 fix: Unchecked auto key action by default 2023-07-28 15:35:56 +02:00
J-Jamet
3a79f94698 fix: Add validate entry education 2023-07-28 13:06:48 +02:00
J-Jamet
b913913bf1 fix: Add validate entry education 2023-07-28 12:59:20 +02:00
J-Jamet
16f92fc895 fix: Change wording for screen on option 2023-07-28 12:27:44 +02:00
J-Jamet
189e98b3fe fix: Small biometric changes 2023-07-28 12:27:23 +02:00
J-Jamet
64fea078e8 fix: Update device unlock wording 2023-07-28 11:06:07 +02:00
J-Jamet
4ad5d35be7 fix: Theme in settings 2023-07-27 01:40:11 +02:00
J-Jamet
82015953b9 Merge branch 'feature/Material_You' into develop 2023-07-27 01:12:06 +02:00
J-Jamet
630318dbef fix: Remove context themed 2023-07-27 01:10:30 +02:00
J-Jamet
d289e8acaa feat: Add Material You with dynamic color 2023-07-27 00:07:41 +02:00
J-Jamet
4632a202a9 fix: Small color change 2023-07-25 13:04:41 +02:00
J-Jamet
4a9282ee53 fix: Titles 2023-07-25 01:07:56 +02:00
J-Jamet
45ec38ecab fix: Parcelable for action 2023-07-24 23:32:03 +02:00
J-Jamet
b82fc4659a Merge branch 'feature/Material_You_1469' into develop 2023-07-24 21:12:29 +02:00
J-Jamet
aade780f92 fix: Image database modified 2023-07-24 21:03:32 +02:00
J-Jamet
6001f68cce fix: Breadcrumb and colors 2023-07-24 20:32:40 +02:00
J-Jamet
d0a072554e fix: Refactoring text styles 2023-07-24 19:14:33 +02:00
J-Jamet
53ccac0e9c fix: Preference flickering 2023-07-24 14:39:07 +02:00
J-Jamet
14e2a3f79e fix: Titles 2023-07-24 14:17:26 +02:00
J-Jamet
9afe5aaaf4 fix: Title and small bugs 2023-07-24 13:51:29 +02:00
J-Jamet
b992c9eb65 fix: Better preference implementation and fix colors 2023-07-24 13:21:24 +02:00
J-Jamet
fa2a79bc6c fix: Breadcrumb and buttons 2023-07-24 09:47:17 +02:00
J-Jamet
b1d4310b93 fix: OTP clipboard as sensitive 2023-07-23 22:57:22 +02:00
J-Jamet
1811b02fc8 fix: colors Kitkat selection and progressCircular 2023-07-23 22:54:09 +02:00
J-Jamet
5bbf436e4e fix: color selection 2023-07-23 12:40:39 +02:00
J-Jamet
d47a12c3d9 fix: Collapsing title 2023-07-23 12:00:35 +02:00
J-Jamet
dd7f6b389c fix: Custom entry color 2023-07-23 11:49:17 +02:00
J-Jamet
e829e3eedf fix: Dialog implementation 2023-07-22 21:02:58 +02:00
J-Jamet
178de4dd99 fix: Colors in styles 2023-07-22 20:45:05 +02:00
J-Jamet
a4cfc8ff49 fix: Chips 2023-07-22 20:08:54 +02:00
J-Jamet
b607cce691 fix: Kunzite style 2023-07-22 19:49:11 +02:00
J-Jamet
5986f39041 fix: Date and Time Picker as Material 2023-07-22 19:09:12 +02:00
J-Jamet
8ab39aaea6 fix: Style Reply 2023-07-22 17:46:58 +02:00
J-Jamet
4114d5aeb1 fix: Styles moon and sun 2023-07-21 22:11:39 +02:00
J-Jamet
2a1a5d9bd4 fix: Styles 2023-07-21 18:17:00 +02:00
J-Jamet
72b347bc85 fix: Search view 2023-07-20 23:23:47 +02:00
J-Jamet
44628f6fc4 fix: Lock button position 2023-07-20 23:09:00 +02:00
J-Jamet
9191fd4a21 fix: colors 2023-07-20 22:54:32 +02:00
J-Jamet
02c4eba676 fix: Many style issues 2023-07-19 21:42:31 +02:00
J-Jamet
a45b5605a1 fix: group icon 2023-07-11 22:54:57 +02:00
J-Jamet
8570f47372 fix: Large pass to add Material 3 2023-07-10 20:11:39 +02:00
J-Jamet
71fd17c0ac fix: Add color for tabs 2023-07-09 13:28:22 +02:00
J-Jamet
eb4e45cb69 fix: Add tertiary color, change color accent to color secondary 2023-07-09 11:40:58 +02:00
J-Jamet
3891bb1c8f fix: Add configuration cache 2023-07-09 10:14:07 +02:00
J-Jamet
a3c5237c2b fix: Null 2023-07-08 17:40:08 +02:00
J-Jamet
e05b904e78 fix: Styles 2023-07-08 16:35:25 +02:00
J-Jamet
0423dd7295 fix: Settings and biometric button 2023-07-08 15:59:51 +02:00
J-Jamet
8fa8043ff4 feat: Fingerprint button as real button 2023-07-08 00:38:58 +02:00
J-Jamet
244174e494 fix: Fingerprint button 2023-07-07 23:53:01 +02:00
J-Jamet
db8920ae69 fix: move advanced unlock button to the bottom 2023-07-07 22:32:47 +02:00
J-Jamet
670e949e9f fix: Buttons and wording 2023-06-27 23:20:51 +02:00
J-Jamet
c4daafea6d fix: rollback AlertDialog 2023-06-26 20:20:15 +02:00
J-Jamet
c8f07ffa7c fix: red and blue styles 2023-06-26 20:05:55 +02:00
J-Jamet
7f721427b6 fix: switch and color 2023-06-26 20:01:46 +02:00
J-Jamet
ad670151c8 fix: radius and color 2023-06-26 19:26:01 +02:00
J-Jamet
800335e416 fix: DateTime dialog color and switch to the right 2023-06-26 18:44:38 +02:00
J-Jamet
2da58f5899 fix: Lot of styles bugs 2023-06-26 18:03:36 +02:00
J-Jamet
0850b19aec fix: Crash on kitkat 2023-06-26 13:38:59 +02:00
Marijn Suijten
b6d32999b9 Add JavaVersion compatible to icon-pack
This now also contains Kotlin code.
2023-06-22 01:11:06 +02:00
J-Jamet
4f96a300c2 fix: CardView Radius 2023-06-19 19:03:31 +02:00
J-Jamet
c630b9d0c0 fix: Button color 2023-06-19 18:00:42 +02:00
J-Jamet
7324f630ba fix: File selection screen 2023-06-19 17:08:37 +02:00
J-Jamet
519da9c4ef fix: First pass to Material3 2023-06-19 16:10:36 +02:00
J-Jamet
6fc26ef6e6 fix: update strings 2023-06-18 23:03:29 +02:00
J-Jamet
dfb7e6f3bd fix: check permission 2023-06-18 23:02:09 +02:00
J-Jamet
5a883b83a5 fix: bn string 2023-06-18 21:20:53 +02:00
J-Jamet
dfef51fd27 fix: replace strong tag 2023-06-18 21:11:31 +02:00
J-Jamet
8bb7c7c8af Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2023-06-18 21:09:20 +02:00
J-Jamet
98370a03b5 fix: better intent to ask fingerprint enrollment 2023-06-18 20:46:48 +02:00
J-Jamet
82875ff832 fix: ask for notification permission 2023-06-18 18:12:17 +02:00
J-Jamet
9b4b5914d6 fix: allowCreateDocumentByStorageAccessFramework method 2023-06-18 12:23:37 +02:00
J-Jamet
7b9576841d fix: Menu deprecation 2023-06-18 12:05:08 +02:00
J-Jamet
59794557b3 fix: PackageInfo compat 2023-06-18 11:00:22 +02:00
J-Jamet
c3e4504a1a fix: TokenAutoComplete as lib 2023-06-18 10:43:07 +02:00
J-Jamet
65d7fdbf7e fix: Resolve dependencies and small changes 2023-06-18 10:33:58 +02:00
J-Jamet
d5310e5c58 fix: Deprecated parcelable 2023-06-18 10:08:58 +02:00
J-Jamet
f02b7c16d2 fix: Upgrade to SDK 33 2023-06-17 19:01:35 +02:00
J-Jamet
7cd456ecbc fix: Manifest tag 2023-06-17 18:39:30 +02:00
J-Jamet
1aeff9684e fix: Upgrade gradle to 7.4.2 2023-06-17 18:20:05 +02:00
Jeanluc Henrot
4b0ad75e7b Translated using Weblate (French)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2023-06-06 14:50:16 +02:00
Masowick
055b3c6002 Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-05-19 10:48:28 +02:00
abidin toumi
9ad4858224 Translated using Weblate (Arabic)
Currently translated at 75.4% (477 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2023-05-18 09:47:41 +02:00
ctntt
33422f90ce Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-05-18 09:47:41 +02:00
ctntt
b9f4f9380b Translated using Weblate (German)
Currently translated at 99.2% (627 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-05-15 16:52:04 +02:00
J-Jamet
56a1601d79 Merge branch 'feature/Module_Refactoring' into develop 2023-05-15 16:43:46 +02:00
J-Jamet
a7d04e573a fix: Better database action implementation 2023-05-15 16:21:49 +02:00
J-Jamet
0c4d827469 fix: Remove unused assign password runnable 2023-05-15 00:13:02 +02:00
J-Jamet
5656f6afb5 fix: Small changes 2023-05-14 23:43:45 +02:00
J-Jamet
4bd12b8a95 fix: Refactoring database actions 2023-05-14 23:38:08 +02:00
J-Jamet
939b319563 fix: Change ContextualDatabase 2023-05-14 22:21:34 +02:00
J-Jamet
05865fe8c3 fix: Encapsulate BASE64_FLAG 2023-05-14 22:05:08 +02:00
J-Jamet
09cf8aabf6 Revert "fix: Base64 encoder as external library"
This reverts commit 13f2003fed.
2023-05-14 21:56:11 +02:00
J-Jamet
111a77d984 Revert "fix: Base64 encoder as external library"
This reverts commit 1b96747187.
2023-05-14 21:56:11 +02:00
J-Jamet
c2cc0a77ab fix: NDKVersion in the right package 2023-05-14 21:54:52 +02:00
J-Jamet
5b052b02a2 fix: crypto module at the upper level 2023-05-14 21:33:37 +02:00
J-Jamet
3b3908dd7c fix: Better icon pack implementation 2023-05-14 21:25:24 +02:00
J-Jamet
1b96747187 fix: Base64 encoder as external library 2023-05-14 20:19:22 +02:00
J-Jamet
13f2003fed fix: Base64 encoder as external library 2023-05-14 20:06:51 +02:00
J-Jamet
4d6dbc9e30 fix: Create unit tests in right package 2023-05-14 19:40:43 +02:00
J-Jamet
a235b21fbf feat: Move modules 2023-05-14 18:40:45 +02:00
J-Jamet
158ed26f6f fix: Remove unused libs 2023-05-14 18:03:05 +02:00
J-Jamet
81930bf3a1 fix: Remove AndroidX dependency and format code 2023-05-14 17:52:40 +02:00
J-Jamet
e1ecebb4a0 fix: Remove unnecessary coroutine lib 2023-05-14 15:55:04 +02:00
J-Jamet
52fd576f55 fix: Delete color lib dependency 2023-05-14 15:51:32 +02:00
J-Jamet
6742a8893c fix: Separate Main credential, remove ContentProvider from Database 2023-05-14 15:33:39 +02:00
J-Jamet
7db0c4f7d3 fix: Locale warning 2023-05-14 13:26:23 +02:00
J-Jamet
372b2aebe0 fix: Smaller isSearchable implementation 2023-05-14 13:22:51 +02:00
J-Jamet
b713237ec3 fix: Small warning 2023-05-14 13:14:27 +02:00
J-Jamet
ee1a0a53a6 fix: Better icon builder implementation, add ContextualDatabase 2023-05-14 13:04:49 +02:00
VfBFan
45ece887a2 Translated using Weblate (German)
Currently translated at 99.2% (627 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-05-14 12:49:11 +02:00
ctntt
0f59aaf4f7 Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-05-13 02:50:29 +02:00
J-Jamet
0d2c814b3d fix: Small changes 2023-05-08 21:17:00 +02:00
J-Jamet
64bdcc2f32 fix: Remove unused interface 2023-05-08 20:50:59 +02:00
J-Jamet
dfb418c12a fix: better static utils implementation 2023-05-08 20:30:16 +02:00
J-Jamet
c8868f31e6 Refactoring code, better encapsulation 2023-05-08 18:49:42 +02:00
J-Jamet
48cc8b3f75 Merge branch 'extract-database' of github.com:GianpaMX/KeePassDX into GianpaMX-extract-database 2023-05-08 15:19:32 +02:00
Aitor Elorza
62777373a4 Translated using Weblate (Basque)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-05-05 16:48:19 +02:00
Aitor Elorza
ce987237ae Translated using Weblate (Basque)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-05-03 18:48:25 +02:00
Aitor Elorza
77b99f9ef4 Translated using Weblate (Bengali)
Currently translated at 57.1% (361 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn/
2023-04-29 14:51:26 +02:00
Aitor Elorza
049ac4cb21 Translated using Weblate (Basque)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-04-29 14:51:25 +02:00
Éfrit
41e3c60632 Translated using Weblate (French)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2023-04-28 11:27:27 +02:00
Aitor Elorza
5b2c846f7d Translated using Weblate (Basque)
Currently translated at 94.3% (596 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-04-28 11:27:26 +02:00
C. Rüdinger
8a0192bcac Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-04-28 11:27:25 +02:00
Subham Jena
3d2fce78c6 Translated using Weblate (Odia)
Currently translated at 0.4% (3 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/or/
2023-04-24 00:51:47 +02:00
Ihor Hordiichuk
120bfd47e2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2023-04-24 00:51:47 +02:00
Aitor Elorza
408ac08059 Translated using Weblate (Basque)
Currently translated at 69.7% (441 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-04-24 00:51:47 +02:00
Subham Jena
9ae2377bd9 Added translation using Weblate (Odia) 2023-04-23 00:13:42 +02:00
J-Jamet
f126a9ab09 Merge branch 'jkrasuk-jkrasuk-patch-1' into develop 2023-04-22 22:41:38 +02:00
J-Jamet
c2d4bf67d0 Merge branch 'jkrasuk-patch-1' of github.com:jkrasuk/KeePassDX into jkrasuk-jkrasuk-patch-1 2023-04-22 22:39:25 +02:00
J-Jamet
d0367ea7f8 fix: Replace strong tags 2023-04-22 22:34:50 +02:00
J-Jamet
73c6c97637 Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2023-04-22 22:31:42 +02:00
J-Jamet
3f63fa9c30 fix: Upgrade to 3.5.2 and add new default configuration for protected memory 2023-04-22 22:15:49 +02:00
J-Jamet
2884d12b4b fix: Upgrade GemFile 2023-04-22 22:10:58 +02:00
Aitor Elorza
24dd966eab Translated using Weblate (Basque)
Currently translated at 35.4% (224 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-04-18 07:14:03 +02:00
ctntt
47b2464297 Translated using Weblate (German)
Currently translated at 97.9% (619 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-04-18 07:14:02 +02:00
Masowick
08bb11a4d7 Translated using Weblate (German)
Currently translated at 97.9% (619 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-04-18 07:14:02 +02:00
Martin
e0c2c14b98 Translated using Weblate (German)
Currently translated at 97.9% (619 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-04-16 19:27:55 +02:00
Alexandru
481b818619 Translated using Weblate (Romanian)
Currently translated at 65.5% (414 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ro/
2023-04-12 10:32:45 +02:00
Aitor Elorza
138efebd8c Translated using Weblate (Basque)
Currently translated at 28.0% (177 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-04-05 18:48:12 +02:00
Aitor Elorza
3251f02ab2 Translated using Weblate (Basque)
Currently translated at 22.7% (144 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-04-01 15:37:39 +02:00
Aitor Elorza
8f74cdfac0 Translated using Weblate (Basque)
Currently translated at 20.5% (130 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-03-29 18:39:08 +02:00
VfBFan
75f795b6ab Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-29 18:39:07 +02:00
C. Rüdinger
dd901f3b16 Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-29 18:39:07 +02:00
Masowick
cf2d641303 Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-29 18:39:07 +02:00
Ettore Atalan
489c820ab9 Translated using Weblate (German)
Currently translated at 99.0% (626 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-28 09:37:14 +02:00
Masowick
dd21382b29 Translated using Weblate (German)
Currently translated at 99.0% (626 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-28 09:37:14 +02:00
VfBFan
05b2ccf0ed Translated using Weblate (German)
Currently translated at 99.0% (626 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-28 09:37:14 +02:00
J-Jamet
428efd2bb6 fix: CHANGELOG 2023-03-26 14:53:27 +02:00
J-Jamet
512b4cd16e Merge tag '3.5.1' into develop
3.5.1
2023-03-26 14:48:43 +02:00
J-Jamet
4d648b77b9 Merge branch 'release/3.5.1' 2023-03-26 14:48:32 +02:00
J-Jamet
02f6caf4dd fix: Loading dialog with Yubikey #1506 2023-03-26 14:46:11 +02:00
J-Jamet
2b23649674 feat: upgrade to 3.5.1 2023-03-26 14:44:20 +02:00
WB
1693be7776 Translated using Weblate (Galician)
Currently translated at 97.3% (615 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2023-03-25 16:40:20 +01:00
Aitor Elorza
c97be80a3a Translated using Weblate (Basque)
Currently translated at 13.2% (84 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-03-25 16:40:19 +01:00
Aitor Elorza
29d4d5232d Translated using Weblate (Basque)
Currently translated at 11.3% (72 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-03-22 16:38:00 +01:00
Tom Sawyer
90ee9d8c40 Translated using Weblate (Swedish)
Currently translated at 61.2% (387 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2023-03-20 08:40:54 +01:00
C. Rüdinger
5c0a82ba38 Translated using Weblate (German)
Currently translated at 98.8% (625 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-20 08:40:53 +01:00
Aitor Elorza
13aee1072f Translated using Weblate (Basque)
Currently translated at 11.3% (72 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-03-17 17:37:53 +01:00
VfBFan
b158cf1323 Translated using Weblate (German)
Currently translated at 98.7% (624 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-17 17:37:53 +01:00
C. Rüdinger
22be5ecbca Translated using Weblate (German)
Currently translated at 98.7% (624 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-16 08:07:05 +01:00
Aitor Elorza
4b16d9394a Translated using Weblate (Basque)
Currently translated at 10.7% (68 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/eu/
2023-03-15 19:37:20 +01:00
Masowick
c0ad54219f Translated using Weblate (German)
Currently translated at 99.8% (631 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-15 19:37:20 +01:00
76c72cfe
4af6f776b0 Translated using Weblate (Swedish)
Currently translated at 60.9% (385 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2023-03-12 17:37:26 +01:00
P.O
76373658d8 Translated using Weblate (Swedish)
Currently translated at 60.9% (385 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2023-03-12 17:37:26 +01:00
Anonimas
f9f6e69a95 Translated using Weblate (Lithuanian)
Currently translated at 51.7% (327 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2023-03-08 20:37:37 +01:00
C. Rüdinger
61354df795 Translated using Weblate (German)
Currently translated at 99.8% (631 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-08 20:37:37 +01:00
Masowick
3a5c42b138 Translated using Weblate (German)
Currently translated at 99.8% (631 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-07 07:41:09 +01:00
jc
55e7544739 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2023-03-05 22:40:37 +01:00
Masowick
ad0dcdd54a Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-03-05 22:40:37 +01:00
Masowick
caa208c847 Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-25 19:38:55 +01:00
VfBFan
067d00376d Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-22 10:35:42 +01:00
Masowick
07f0521ef9 Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-22 10:35:42 +01:00
Masowick
3be3593b1b Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-21 07:53:12 +01:00
VfBFan
71db66d174 Translated using Weblate (German)
Currently translated at 99.5% (629 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-21 07:53:11 +01:00
Masowick
cd58a8e1b5 Translated using Weblate (German)
Currently translated at 99.6% (630 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-20 22:05:26 +01:00
VfBFan
256a579b1c Translated using Weblate (German)
Currently translated at 99.6% (630 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-20 22:05:25 +01:00
Masowick
97a427ea24 Translated using Weblate (German)
Currently translated at 99.6% (630 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-20 19:37:59 +01:00
VfBFan
b848587901 Translated using Weblate (German)
Currently translated at 99.6% (630 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-20 19:37:58 +01:00
VfBFan
fd90af3757 Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-20 18:42:57 +01:00
Ivan
cb1c9e1103 Translated using Weblate (Slovak)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sk/
2023-02-20 13:13:00 +01:00
Masowick
c14092d52b Translated using Weblate (German)
Currently translated at 99.8% (631 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-20 13:12:59 +01:00
Deleted User
27c19bd708 Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-14 18:39:31 +01:00
VfBFan
13d5273314 Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-13 17:59:14 +01:00
HudobniVolk
a0f93b5441 Translated using Weblate (Slovenian)
Currently translated at 8.5% (54 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sl/
2023-02-13 07:12:36 +01:00
gallegonovato
4c4c92527e Translated using Weblate (Galician)
Currently translated at 97.3% (615 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2023-02-13 07:12:35 +01:00
Deleted User
ff9999f39c Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-13 07:12:35 +01:00
HudobniVolk
45d06f269c Added translation using Weblate (Slovenian) 2023-02-12 13:45:47 +01:00
Ivan
acf92c0f8e Translated using Weblate (Slovak)
Currently translated at 99.0% (626 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sk/
2023-02-11 19:32:18 +01:00
Joaquín Krasuk
4b7de3b85f Update es translation 2023-02-11 12:16:34 -03:00
Milo Ivir
0502bfb986 Translated using Weblate (Croatian)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2023-02-05 20:38:52 +01:00
bowornsin
eacecd9b0d Translated using Weblate (Thai)
Currently translated at 87.6% (554 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/th/
2023-02-02 11:38:08 +01:00
SC
38245b4807 Translated using Weblate (Portuguese)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2023-02-02 11:38:07 +01:00
Oğuz Ersen
95228e67c2 Translated using Weblate (Turkish)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2023-02-02 11:38:07 +01:00
Eric
33cb4a55f5 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2023-02-02 11:38:07 +01:00
Ihor Hordiichuk
7beb13d859 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2023-02-02 11:38:07 +01:00
solokot
f0e0d26923 Translated using Weblate (Russian)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-02-02 11:38:06 +01:00
SC
5326322356 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2023-02-02 11:38:06 +01:00
Sengeku Ferreira Lima
ebe1c0bcdd Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2023-02-02 11:38:06 +01:00
Matthaiks
42100d4cb9 Translated using Weblate (Polish)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2023-02-02 11:38:06 +01:00
Stephan Paternotte
369f3d240e Translated using Weblate (Dutch)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2023-02-02 11:38:05 +01:00
random r
d40e83fb52 Translated using Weblate (Italian)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2023-02-02 11:38:05 +01:00
gallegonovato
6776b0867f Translated using Weblate (Spanish)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2023-02-02 11:38:05 +01:00
Retrial
5b1e945883 Translated using Weblate (Greek)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2023-02-02 11:38:05 +01:00
VfBFan
2f2f5e6f43 Translated using Weblate (German)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2023-02-02 11:38:05 +01:00
Fjuro
dfc7654842 Translated using Weblate (Czech)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2023-02-02 11:38:04 +01:00
Linerly
183015707f Translated using Weblate (Indonesian)
Currently translated at 100.0% (632 of 632 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2023-01-29 15:10:28 +01:00
Hosted Weblate
2bc4908452 Merge branch 'origin/develop' into Weblate. 2023-01-29 14:36:53 +01:00
bowornsin
213e5c40d7 Translated using Weblate (Thai)
Currently translated at 84.7% (535 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/th/
2023-01-29 14:36:53 +01:00
J-Jamet
e7cce21c51 Merge tag '3.5.0' into develop
3.5.0
2023-01-26 23:25:50 +01:00
J-Jamet
128ce5657e Merge branch 'release/3.5.0' 2023-01-26 23:25:40 +01:00
J-Jamet
861911ad63 fix: update fastlane changelogs 2023-01-26 23:21:10 +01:00
J-Jamet
9093c65235 feat: upgrade to 3.5.0 2023-01-26 23:15:02 +01:00
J-Jamet
e6ab8f82ff fix: url to download driver 2023-01-26 23:01:54 +01:00
J-Jamet
77ff1850f3 fix: url to download driver 2023-01-26 23:01:26 +01:00
J-Jamet
e985bd2a20 fix: dialog to download driver 2023-01-26 22:29:01 +01:00
bowornsin
a1e91bb26f Translated using Weblate (Thai)
Currently translated at 8.8% (56 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/th/
2023-01-24 18:58:28 +01:00
J-Jamet
a6cd02d146 feat: new year 2023-01-22 21:30:37 +01:00
J-Jamet
eb51f6712b fix: changelog length 2023-01-22 21:12:43 +01:00
J-Jamet
0a19ecb715 fi: translation strong tags 2023-01-22 20:59:11 +01:00
J-Jamet
d7e7020244 Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2023-01-22 20:54:48 +01:00
J-Jamet
ea3349eea4 fix: autofill with hardware key 2023-01-22 20:33:29 +01:00
J-Jamet
9cec64ded4 fix: better challenge channel implementation 2023-01-22 20:21:17 +01:00
J-Jamet
f9dc456032 fix: remove ChallengeResponseViewModel and add HardwareKeyActivity 2023-01-22 15:10:36 +01:00
Linerly
6416aad823 Translated using Weblate (Indonesian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2023-01-12 22:48:40 +01:00
Kornelijus Tvarijanavičius
10b5c9c261 Translated using Weblate (Lithuanian)
Currently translated at 40.5% (256 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2023-01-12 22:48:40 +01:00
ERYpTION
bc0e364164 Translated using Weblate (Danish)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2023-01-12 22:48:40 +01:00
bowornsin
83ab5223a8 Translated using Weblate (Thai)
Currently translated at 8.2% (52 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/th/
2023-01-08 06:48:05 +01:00
Ahmad0a
94c6710f22 Translated using Weblate (Arabic)
Currently translated at 75.2% (475 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2023-01-06 02:49:44 +01:00
solokot
a1e5266161 Translated using Weblate (Russian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2023-01-04 18:47:52 +01:00
J-Jamet
adbdb9a642 first code to manage merge with yubikey 2023-01-02 19:30:19 +01:00
bowornsin
527f734fcf Translated using Weblate (Thai)
Currently translated at 5.5% (35 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/th/
2023-01-02 08:50:00 +01:00
Stephan Paternotte
f1a29af0c6 Translated using Weblate (Dutch)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-12-29 16:51:03 +01:00
WB
b69a277769 Translated using Weblate (Galician)
Currently translated at 97.3% (614 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-12-26 22:49:03 +01:00
J-Jamet
d049ed39e8 fix: upgrade to 3.5.0Beta03 2022-12-18 23:58:10 +01:00
J-Jamet
ecfe767068 fix: replace error method 2022-12-18 23:46:33 +01:00
J-Jamet
8d827eb562 Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2022-12-18 12:24:19 +01:00
Jérémy JAMET
9048f618c5 Merge pull request #1447 from flawedworld/vanadium-rebrand
Add support for Vanadium in GrapheneOS
2022-12-18 12:21:47 +01:00
Salih Ail
66377ded62 Translated using Weblate (Arabic)
Currently translated at 75.1% (474 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-12-18 04:50:51 +01:00
VfBFan
04c4c82953 Translated using Weblate (German)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-12-10 09:49:31 +01:00
Besnik Bleta
ccd8467cba Translated using Weblate (Albanian)
Currently translated at 57.3% (362 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sq/
2022-12-08 18:48:14 +01:00
Besnik Bleta
4eee9e95c4 Translated using Weblate (Albanian)
Currently translated at 54.5% (344 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sq/
2022-12-07 17:48:59 +01:00
Besnik Bleta
c64b4b8d62 Translated using Weblate (Albanian)
Currently translated at 5.8% (37 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sq/
2022-12-07 12:44:47 +01:00
Besnik Bleta
72ef0f2f3f Added translation using Weblate (Albanian) 2022-12-07 12:34:56 +01:00
Michael
9b5a95a10f Translated using Weblate (German)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-12-06 07:43:25 +01:00
Yudong
1a3baa6523 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-12-04 16:48:49 +01:00
Matthaiks
a745ed1c28 Translated using Weblate (Polish)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-11-26 20:48:46 +01:00
WaldiS
39ff112b42 Translated using Weblate (Polish)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-11-25 20:13:07 +01:00
WB
b3aab27c9b Translated using Weblate (Galician)
Currently translated at 88.7% (560 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-11-24 20:38:26 +01:00
WB
9b3c751a49 Translated using Weblate (Galician)
Currently translated at 80.1% (506 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-11-19 21:46:56 +01:00
flawedworld
d29f4e0097 Add support for Vanadium in GrapheneOS 2022-11-19 02:11:56 +00:00
Abhi
0645fbe938 Translated using Weblate (Malayalam)
Currently translated at 60.6% (383 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ml/
2022-11-17 14:47:11 +01:00
Jacek
ee6c8fc041 Translated using Weblate (Polish)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-11-16 02:49:13 +01:00
bowornsin
d93e3a1c2d Translated using Weblate (Thai)
Currently translated at 4.1% (26 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/th/
2022-11-09 11:50:24 +01:00
bowornsin
d4e0c008b8 Added translation using Weblate (Thai) 2022-11-08 07:37:59 +01:00
ssantos
0ca392f312 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2022-11-07 09:05:08 +01:00
solokot
c6c14c2354 Translated using Weblate (Russian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-11-06 09:03:25 +01:00
Darin Avdeyeva
652616226b Translated using Weblate (Russian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-11-05 09:54:16 +01:00
Retrial
d2101fd3e5 Translated using Weblate (Greek)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-11-02 06:36:25 +01:00
GianpaMX
3b967dd4e3 clean up 2022-10-27 17:31:48 +01:00
GianpaMX
e5d58721dd icon size 2022-10-26 17:19:40 +01:00
GianpaMX
1495c442ac default template group name 2022-10-26 17:16:38 +01:00
GianpaMX
2206184bcb recycler bin title 2022-10-26 16:37:08 +01:00
GianpaMX
9e0b6fa800 named compression algorithm 2022-10-26 15:25:38 +01:00
GianpaMX
b11533f9fe exceptions refactor 2022-10-26 12:20:19 +01:00
GianpaMX
50343193e1 template field 2022-10-26 11:33:51 +01:00
GianpaMX
7d7e3f4ad6 extract database module 2022-10-25 18:00:31 +01:00
WB
e22d9f6bdf Translated using Weblate (Galician)
Currently translated at 66.4% (419 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-10-24 23:37:12 +02:00
magnum Choi
affcc28f13 Translated using Weblate (Korean)
Currently translated at 50.2% (317 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2022-10-22 15:02:26 +02:00
Jacek
7a151bc2fe Translated using Weblate (Polish)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-10-22 15:02:24 +02:00
Anonimas
5349c4783e Translated using Weblate (Lithuanian)
Currently translated at 39.7% (251 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2022-10-22 15:02:24 +02:00
Óscar Fernández Díaz
96a72d9842 Translated using Weblate (Spanish)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-10-22 15:02:23 +02:00
GianpaMX
04eae1ae3d new module 2022-10-22 07:57:40 +01:00
Gabriel Cardoso
67afa55f1d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-10-17 02:53:24 +02:00
J-Jamet
efa8fd9f17 fix: replace strong tags 2022-10-14 21:21:27 +02:00
J-Jamet
64128991a6 Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2022-10-14 21:16:09 +02:00
J-Jamet
3fa38c29f6 fix: fix channel closing 2022-10-14 21:06:52 +02:00
J-Jamet
86a335768c fix: update CHANGELOG 2022-10-14 20:49:26 +02:00
J-Jamet
2d29092c52 fix: update CHANGELOG 2022-10-14 20:43:49 +02:00
J-Jamet
2d7e76a279 Merge branch 'issue-1412' of github.com:ryg-git/KeePassDX into ryg-git-issue-1412 2022-10-14 20:39:26 +02:00
J-Jamet
1514dbb1de fix: comment FIDO2 code #304 2022-10-14 13:59:20 +02:00
J-Jamet
1707d3c3ba Revert "fix: Smaller advanced unlock UI"
This reverts commit 2882bb30d7.
2022-10-14 13:53:11 +02:00
J-Jamet
3fee162c4d fix: rollback mime-types */* #1211 #1408, add monochrome icon #1403 #1404 2022-10-14 13:40:01 +02:00
J-Jamet
78fd9d616b Merge branch 'Sandelinos-themed-icons' into release/3.5.0 2022-10-14 13:30:02 +02:00
J-Jamet
46828c3317 Revert "fix: remove application/octet-stream file recognition #1211"
This reverts commit e367051b80.
Mimetype */* #1408
2022-10-14 13:25:39 +02:00
ryg-git
5126bd4fb6 remove onRegularBackPressed method call as lockAndExit handles exiting app 2022-10-01 20:54:01 +05:30
Digger
6440e5e054 Translated using Weblate (Japanese)
Currently translated at 98.4% (621 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-10-01 04:18:30 +02:00
Fjuro
15b84739e7 Translated using Weblate (Czech)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-09-28 19:19:26 +02:00
Fjuro
4bfe296e1a Translated using Weblate (Czech)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-09-27 08:57:42 +02:00
Stephan Paternotte
3fcc0db4f8 Translated using Weblate (Dutch)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-09-25 13:53:18 +02:00
Suman Garai
a8238565f3 Translated using Weblate (Bengali)
Currently translated at 57.2% (361 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn/
2022-09-24 15:19:38 +02:00
Digger
53114462b3 Translated using Weblate (Japanese)
Currently translated at 98.2% (620 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-09-24 15:19:34 +02:00
Roee Hershberg
094da79cea Translated using Weblate (Hebrew)
Currently translated at 45.1% (285 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/he/
2022-09-24 15:19:33 +02:00
SC
f093206a1c Translated using Weblate (Portuguese)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-09-20 20:19:37 +02:00
Digger
5b4940d017 Translated using Weblate (Japanese)
Currently translated at 97.4% (615 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-09-20 20:19:36 +02:00
VfBFan
aee36eeec6 Translated using Weblate (German)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-09-20 20:19:36 +02:00
Digger
cc783f8be1 Translated using Weblate (Japanese)
Currently translated at 95.5% (603 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-09-18 06:24:10 +02:00
WB
a20f491cf1 Translated using Weblate (Galician)
Currently translated at 62.2% (393 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-09-17 18:16:24 +02:00
Anonimas
f5ff9bf263 Translated using Weblate (Lithuanian)
Currently translated at 30.4% (192 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2022-09-17 18:16:23 +02:00
random r
dcba54b499 Translated using Weblate (Italian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-09-17 18:16:22 +02:00
zeritti
d1e103c1d7 Translated using Weblate (Czech)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-09-17 18:16:21 +02:00
Sandelinos
542daba206 Added monochrome icon 2022-09-17 00:42:47 +03:00
J-Jamet
94b61b1bbd Change KeePassX by KeeWeb 2022-09-16 09:19:43 +02:00
WB
718e590bfd Translated using Weblate (Galician)
Currently translated at 52.9% (334 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-09-15 21:22:54 +02:00
J-Jamet
9ce5e184c0 fix: try to not call BiometricManager methods if disabled #1400 2022-09-15 18:47:19 +02:00
J-Jamet
dbc477765f fix: upgrade to 3.5.0beta02 2022-09-15 15:05:38 +02:00
J-Jamet
92fcadf3f3 fix: attachment download button #1401 2022-09-15 15:04:24 +02:00
WB
c437fd96a8 Translated using Weblate (Galician)
Currently translated at 41.0% (259 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-09-14 05:15:57 +02:00
Vitor Henrique
10d33ecb82 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.6% (629 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-09-14 05:15:54 +02:00
Vitor Henrique
d506c0cb27 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.0% (606 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-09-11 22:18:00 +02:00
Milo Ivir
acac7f7540 Translated using Weblate (Croatian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-09-10 19:49:28 +02:00
Roee Hershberg
c853bd282a Translated using Weblate (Hebrew)
Currently translated at 37.7% (238 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/he/
2022-09-10 19:49:28 +02:00
J-Jamet
ecc4550261 Add Deutsh description 2022-09-07 22:42:18 +02:00
Linerly
c26ece7166 Translated using Weblate (Indonesian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-09-07 02:15:21 +02:00
nautilusx
88b50c7902 Translated using Weblate (German)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-09-07 02:15:20 +02:00
J-Jamet
7ba2cdd6ff Revert "fix: Change key driver url"
This reverts commit 941f9bcd48.
2022-09-06 09:29:41 +02:00
J-Jamet
589d9a2f1d Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2022-09-04 18:24:06 +02:00
Oğuz Ersen
a92411b95b Translated using Weblate (Turkish)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-09-04 17:54:45 +02:00
Eric
472051bd24 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-09-04 17:54:44 +02:00
Ihor Hordiichuk
e5109a1f43 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-09-04 17:54:44 +02:00
solokot
f7ae9e3574 Translated using Weblate (Russian)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-09-04 17:54:43 +02:00
Matthaiks
170ec3c636 Translated using Weblate (Polish)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-09-04 17:54:43 +02:00
Retrial
1ab3fa8b3b Translated using Weblate (Greek)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-09-04 17:54:43 +02:00
J-Jamet
8b046512e3 fix: upgrade Gemfile.lock 2022-09-04 14:20:36 +02:00
J-Jamet
228a10c8e0 Merge branch 'translations' into develop 2022-09-04 12:24:32 +02:00
J-Jamet
9c53bea190 fix: replace <strong> tags 2022-09-04 12:23:49 +02:00
J-Jamet
11cf991498 Merge branch 'develop' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2022-09-04 12:15:55 +02:00
J-Jamet
a88c3721b2 Merge branch 'translations' into develop 2022-09-04 12:14:33 +02:00
J-Jamet
0b4b6d4d91 feat: upgrade to 3.5.0 2022-09-04 12:13:19 +02:00
J-Jamet
941f9bcd48 fix: Change key driver url 2022-09-03 23:03:11 +02:00
Jérémy JAMET
09988a858d Add badge in README.md 2022-09-03 22:12:17 +02:00
solokot
f1bf9fb25c Translated using Weblate (Russian)
Currently translated at 99.5% (628 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-09-03 19:24:05 +02:00
Matthaiks
1751fa49c0 Translated using Weblate (Polish)
Currently translated at 99.6% (629 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-09-03 19:24:05 +02:00
Kunzisoft
6b4fc9a4fa Translated using Weblate (French)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-09-03 19:24:04 +02:00
Retrial
7c8d85e428 Translated using Weblate (Greek)
Currently translated at 99.5% (628 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-09-03 19:24:04 +02:00
Allan Nordhøy
e335140f23 Translated using Weblate (English)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-09-03 19:24:03 +02:00
Kunzisoft
d85f398b5f Translated using Weblate (English)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-09-03 19:24:03 +02:00
Wilker Santana da Silva
a16082a59d Translated using Weblate (English)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-09-03 19:11:24 +02:00
Kunzisoft
456269a343 Translated using Weblate (English)
Currently translated at 100.0% (631 of 631 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2022-09-03 19:11:23 +02:00
J-Jamet
eb8e1e20eb fix: Remove cancel button for development dialog 2022-09-03 17:30:31 +02:00
Hosted Weblate
ed3c84fec0 Merge branch 'origin/develop' into Weblate. 2022-09-03 17:27:29 +02:00
J-Jamet
be40416a2d feat: Add privacy text in About section 2022-09-03 17:25:20 +02:00
PiQuark6046
5b5476a513 Translated using Weblate (Korean)
Currently translated at 35.0% (221 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2022-09-01 19:19:15 +02:00
random r
dc64dd6400 Translated using Weblate (Italian)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-09-01 19:19:14 +02:00
atilluF
eca02d3bde Translated using Weblate (Italian)
Currently translated at 97.4% (614 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-08-31 15:15:20 +02:00
SC
176b6c2936 Translated using Weblate (Portuguese)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-08-26 20:21:24 +02:00
J-Jamet
5b22350bdf fix: Hide clipboard text when copy entry field #1386 2022-08-23 11:56:29 +02:00
J-Jamet
6e1e011234 fix: exec gradlew version 7.5.1 to update scripts 2022-08-23 11:32:10 +02:00
J-Jamet
ac65ef6a5c Merge branch 'develop' of github.com:Kunzisoft/KeePassDX into develop 2022-08-23 11:26:35 +02:00
Jérémy JAMET
fc198dde74 Merge pull request #1387 from lberrymage/update-gradle
Upgrade Gradle to 7.5.1
2022-08-23 11:25:59 +02:00
lberrymage
15ac51b2fc Upgrade Gradle to 7.5.1
Generated by `./gradlew wrapper --gradle-version 7.5.1`
2022-08-22 18:09:13 -08:00
J-Jamet
34214432e1 fix: upgrade libs 2022-08-17 23:01:45 +02:00
J-Jamet
361ca92493 fix: upgrade gradle plugin to 7.2.2 2022-08-17 22:56:50 +02:00
J-Jamet
e367051b80 fix: remove application/octet-stream file recognition #1211 2022-08-17 22:25:47 +02:00
Linerly
a2a4a50c5e Translated using Weblate (Indonesian)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-08-14 14:20:41 +02:00
Milo Ivir
afc74b2f2a Translated using Weblate (Croatian)
Currently translated at 99.2% (625 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-08-14 14:20:40 +02:00
devchung
fc756d1eaf Translated using Weblate (Chinese (Traditional))
Currently translated at 97.3% (613 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2022-08-14 14:20:39 +02:00
Eric
eb8a4b1e49 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-08-14 14:20:39 +02:00
Ihor Hordiichuk
8d258b3538 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-08-14 14:20:38 +02:00
solokot
a59cfa3477 Translated using Weblate (Russian)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-08-14 14:20:38 +02:00
Matthaiks
f1e513006e Translated using Weblate (Polish)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-08-14 14:20:37 +02:00
Retrial
9df5c8f439 Translated using Weblate (Greek)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-08-14 14:20:36 +02:00
Deleted User
3ae099accf Translated using Weblate (German)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-08-14 14:20:36 +02:00
VfBFan
bb3e9396f2 Translated using Weblate (German)
Currently translated at 100.0% (630 of 630 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-08-14 14:20:35 +02:00
Hosted Weblate
1628749bde Merge branch 'origin/develop' into Weblate. 2022-08-11 12:47:58 +02:00
J-Jamet
f2006b5e42 fix: show lock button hidden by screenshot mode banner #1377 2022-08-11 12:37:28 +02:00
GianpaMX
80d387d9e7 Add screenshot mode
* Add new screenshot mode entry under Settings -> App -> General
* Disable Screenshot mode  by default
* Add a screenshot mode indication at the bottom of the screen
* Set or clear window FLAG_SECURE accordingly
* Translate strings into Spanish
2022-08-10 15:19:09 +01:00
J-Jamet
4452b4d599 Merge branch 'feature/Hardware_Key' into develop 2022-08-08 14:00:21 +02:00
J-Jamet
dfeaeb9888 feature: todo open external app in f-droid 2022-08-08 13:57:51 +02:00
J-Jamet
7e45a20ee7 fix: Refactoring key driver app id 2022-08-07 23:27:59 +02:00
J-Jamet
f3fe92e4de Merge branch 'develop' into feature/Hardware_Key 2022-08-02 22:25:44 +02:00
J-Jamet
b606909c65 fix: Update libs and SDK to 32 2022-08-02 22:25:19 +02:00
J-Jamet
2882bb30d7 fix: Smaller advanced unlock UI 2022-08-02 21:47:33 +02:00
eamz8jpajok
5b62227e3f Translated using Weblate (German)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-07-28 23:21:09 +02:00
J-Jamet
8b6af6fd8a feat: Derive master key exception 2022-07-05 18:09:35 +02:00
J-Jamet
99e9a92953 fix: KDB opening 2022-07-05 17:55:12 +02:00
Noël Krähenbühl
9f626309c3 Translated using Weblate (English (United Kingdom))
Currently translated at 8.3% (51 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en_GB/
2022-06-25 17:16:13 +02:00
Anonimas
3fe7cf2bfd Translated using Weblate (Lithuanian)
Currently translated at 23.4% (143 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2022-06-19 16:16:36 +02:00
Matthaiks
9b5c274b49 Translated using Weblate (Polish)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-06-18 00:19:02 +02:00
WB
46b350e7ac Translated using Weblate (Galician)
Currently translated at 23.4% (143 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2022-06-16 00:19:34 +02:00
Óscar Fernández Díaz
22a4aeb108 Translated using Weblate (Spanish)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-06-14 00:19:14 +02:00
random r
332e116ba7 Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-06-04 11:15:25 +02:00
Anonimas
8b594a1a1f Translated using Weblate (Lithuanian)
Currently translated at 22.9% (140 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/lt/
2022-06-02 18:17:01 +02:00
J-Jamet
ab23ec6d4d Merge tag '3.4.5' into develop
3.4.5
2022-06-02 11:29:08 +02:00
J-Jamet
0ef574d675 Merge branch 'release/3.4.5' 2022-06-02 11:29:00 +02:00
J-Jamet
6d15a2462d Merge branch 'translations' into develop 2022-06-02 11:02:57 +02:00
J-Jamet
24fcdeb7aa Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-06-02 11:01:11 +02:00
J-Jamet
13905db732 fix: searchable selection 2022-06-01 14:15:41 +02:00
J-Jamet
6e1c8e5bec fix: custom data in group (fix KeeShare) #1335 2022-06-01 12:41:01 +02:00
J-Jamet
9aa1d11b94 Change the order of the search filters 2022-05-31 18:46:46 +02:00
J-Jamet
6c9f359fae New clipboard manager #1343 2022-05-31 18:22:01 +02:00
J-Jamet
531ebcae85 Fix device credential unlocking #1344 2022-05-31 11:44:42 +02:00
J-Jamet
fe9601b510 Change to 3.4.5 2022-05-31 10:25:34 +02:00
Oymate
bdf7cc6ea0 Translated using Weblate (Bengali)
Currently translated at 15.7% (96 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn/
2022-05-30 21:18:06 +02:00
Douglas Han
1cfe02af6f Translated using Weblate (Korean)
Currently translated at 28.0% (171 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2022-05-30 21:18:05 +02:00
J-Jamet
647e3f9383 Change intent challenge recognition 2022-05-30 18:19:01 +02:00
J-Jamet
597f52799d Cache to capture exception during database save 2022-05-30 16:59:33 +02:00
J-Jamet
a59e052ed8 Merge branch 'develop' into feature/Hardware_Key 2022-05-30 10:31:46 +02:00
J-Jamet
11da0a4500 Keep screen on by default when viewing an entry
Upgrade to 3.5.0
2022-05-30 10:31:18 +02:00
J-Jamet
fd736bd1c2 Keep screen on entry by default 2022-05-30 10:24:47 +02:00
Oymate
ca6a4bfeef Translated using Weblate (Bengali)
Currently translated at 7.2% (44 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn/
2022-05-27 16:16:54 +02:00
Oymate
02e9debc42 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 15.2% (93 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn_BD/
2022-05-27 16:16:53 +02:00
Oymate
9bc9bd8b95 Added translation using Weblate (Bengali) 2022-05-26 15:10:24 +02:00
Park JM
d810f79b7a Translated using Weblate (Korean)
Currently translated at 20.5% (125 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ko/
2022-05-24 11:17:53 +02:00
Milo Ivir
f3468951f1 Translated using Weblate (Croatian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-05-22 19:32:57 +02:00
Kunzisoft
7ec5badabb Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-05-20 22:17:36 +02:00
J-Jamet
1ff2f501ca Fix capture database exception 2022-05-19 21:22:13 +02:00
J-Jamet
cfcb49e233 Better management of exceptions 2022-05-19 21:16:29 +02:00
J-Jamet
467df2020e Fix merge 2022-05-19 19:48:43 +02:00
J-Jamet
a961b41de0 Fix file save outside of the app 2022-05-19 19:20:21 +02:00
J-Jamet
40e8d5225e Fix notification and save state 2022-05-19 15:54:34 +02:00
J-Jamet
bc755ae1df Fix progress message 2022-05-19 15:00:12 +02:00
J-Jamet
b1cb0c3786 Fix infinite loop 2022-05-19 13:41:38 +02:00
J-Jamet
090d0fa2db Encapsulate channels 2022-05-19 12:53:12 +02:00
J-Jamet
27918a12b0 Fix small bugs 2022-05-19 11:47:53 +02:00
J-Jamet
ba1498b0b2 Fix error message and better implementation 2022-05-19 11:15:28 +02:00
J-Jamet
cbde96dd82 Add waiting task message and cancellable 2022-05-18 19:49:18 +02:00
J-Jamet
344118a755 Better error management 2022-05-18 18:35:24 +02:00
J-Jamet
259c8a4bd9 Setting to remember hardware key 2022-05-18 16:39:35 +02:00
SHINJI.K
fe92e41e91 Translated using Weblate (Japanese)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-05-15 15:18:25 +02:00
zeritti
e58c2f2a99 Translated using Weblate (Czech)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2022-05-15 15:18:24 +02:00
J-Jamet
f4d5bd1bea Fix save and better write implementation 2022-05-11 15:26:49 +02:00
J-Jamet
20b352cabe Better code encapsulation 2022-05-11 14:19:32 +02:00
J-Jamet
20e35f1a69 Encapsulate database operations 2022-05-11 13:42:48 +02:00
J-Jamet
d963f56d0f Merge branch 'develop' into feature/Hardware_Key 2022-05-11 11:36:20 +02:00
J-Jamet
aecfbc7728 Upgrade gradle 2022-05-11 10:59:14 +02:00
J-Jamet
5734df89f0 Merge branch 'develop' into feature/Hardware_Key 2022-05-11 10:10:52 +02:00
J-Jamet
bdf9b864d4 Merge tag '3.4.4' into develop
3.4.4
2022-05-11 10:00:50 +02:00
J-Jamet
1c0f1a036b Merge branch 'release/3.4.4' 2022-05-11 10:00:42 +02:00
J-Jamet
327c9de464 Change main credential validation 2022-05-10 19:59:56 +02:00
J-Jamet
8b2f994769 Save database with challenge response 2022-05-10 15:02:22 +02:00
J-Jamet
a5e53d872b Open database with challenge response in service 2022-05-09 15:56:53 +02:00
Claudio
1868d90693 Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-05-08 16:00:54 +02:00
bondlxv
da0c19e068 Translated using Weblate (Italian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2022-05-08 16:00:54 +02:00
jazzyjabroni
d1103d8db4 Translated using Weblate (Danish)
Currently translated at 84.8% (517 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2022-05-07 21:13:46 +02:00
solokot
b2e92646a1 Translated using Weblate (Russian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-05-06 18:36:13 +02:00
J-Jamet
19bc2444bc Merge branch 'develop' into feature/Hardware_Key 2022-05-05 16:15:03 +02:00
J-Jamet
831b649cbb Merge branch 'translations' into develop 2022-05-05 15:59:37 +02:00
Kunzisoft
ded3c204b9 Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-05-05 15:58:56 +02:00
Hosted Weblate
23eec5f066 Merge branch 'origin/develop' into Weblate. 2022-05-05 15:54:24 +02:00
J-Jamet
6c167090e1 Small changes 2022-05-05 15:50:51 +02:00
J-Jamet
7d9eca0d46 Small changes 2022-05-05 15:29:56 +02:00
J-Jamet
c551aff474 Upgrade libs 2022-05-05 15:10:30 +02:00
J-Jamet
e627745358 Prevent Tapjacking #1318 2022-05-05 14:49:36 +02:00
J-Jamet
5a30d9d2b5 * Fix crash in New Android 13 #1321
* Better backstack management for selection mode
2022-05-05 12:45:07 +02:00
Santosh Anantwal
0a46817bbc Translated using Weblate (Marathi)
Currently translated at 1.9% (12 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/mr/
2022-05-04 20:13:11 +02:00
Hosted Weblate
a4134fa8c8 Merge branch 'origin/develop' into Weblate. 2022-05-03 19:23:33 +02:00
Santosh Anantwal
683535a5a6 Translated using Weblate (Marathi)
Currently translated at 1.4% (9 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/mr/
2022-05-03 19:23:33 +02:00
Santosh Anantwal
edb53112c2 Added translation using Weblate (Marathi) 2022-05-03 19:01:31 +02:00
SHINJI.K
83a77af520 Translated using Weblate (Japanese)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-05-03 15:13:15 +02:00
SC
df3ae17c7b Translated using Weblate (Portuguese)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-05-01 10:10:24 +02:00
abidin toumi
4a1624a443 Translated using Weblate (Arabic)
Currently translated at 78.3% (477 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-05-01 10:10:24 +02:00
wqk317
a8de9f9f9f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-05-01 10:10:23 +02:00
hokonch
3aa5b40acd Translated using Weblate (Japanese)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-05-01 10:10:23 +02:00
wqk317
8400f3e874 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.6% (607 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 09:35:42 +02:00
wqk317
b40bca1913 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 09:18:00 +02:00
wqk317
7100257f31 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.1% (604 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 09:17:06 +02:00
SHINJI.K
17df1a4d8a Translated using Weblate (Japanese)
Currently translated at 98.0% (597 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:53:45 +02:00
hokonch
d7a5209c68 Translated using Weblate (Japanese)
Currently translated at 98.0% (597 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:53:45 +02:00
wqk317
076220eacd Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:43:03 +02:00
SHINJI.K
99a50f271a Translated using Weblate (Japanese)
Currently translated at 96.8% (590 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:43:03 +02:00
hokonch
63d265da06 Translated using Weblate (Japanese)
Currently translated at 96.8% (590 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:43:03 +02:00
wqk317
30e3624eb1 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:25:13 +02:00
Eric
88f3713e28 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:25:13 +02:00
wqk317
90f0c22545 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:23:43 +02:00
wqk317
8deed8468d Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (608 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-30 08:21:23 +02:00
hokonch
923ad26b1b Translated using Weblate (Japanese)
Currently translated at 96.0% (585 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:17:37 +02:00
SHINJI.K
3bc858e4c2 Translated using Weblate (Japanese)
Currently translated at 96.0% (585 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:17:37 +02:00
SHINJI.K
f5a7fa41a7 Translated using Weblate (Japanese)
Currently translated at 95.5% (582 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:10:18 +02:00
hokonch
bf71d5508b Translated using Weblate (Japanese)
Currently translated at 95.5% (582 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-30 08:10:17 +02:00
J-Jamet
b44c9cfc51 Opening refactoring 2022-04-28 20:39:26 +02:00
J-Jamet
5b4338abae Better implementation for challenge response intent 2022-04-27 14:39:08 +02:00
John Veness
aa5adc28cb Translated using Weblate (English (United Kingdom))
Currently translated at 3.6% (22 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en_GB/
2022-04-26 20:07:59 +02:00
abidin toumi
2dad013cc0 Translated using Weblate (Arabic)
Currently translated at 75.8% (462 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2022-04-26 20:07:59 +02:00
solokot
7ade66f3ac Translated using Weblate (Russian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-26 20:07:58 +02:00
Stephan Paternotte
ed75a64b46 Translated using Weblate (Dutch)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-04-26 20:07:58 +02:00
Kunzisoft
e156b80d91 Translated using Weblate (French)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-04-26 20:07:58 +02:00
J-Jamet
e8f79ae467 Fix to open challenge-response dynamically / refactoring methods #8 2022-04-25 21:47:43 +02:00
Kunzisoft
90e4862280 Added translation using Weblate (English (United Kingdom)) 2022-04-25 18:22:17 +02:00
André Marcelo Alvarenga
438080d3d6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-04-23 13:08:01 +02:00
SHINJI.K
3c17605764 Translated using Weblate (Japanese)
Currently translated at 94.2% (574 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2022-04-23 13:08:00 +02:00
VfBFan
3f68bc0eda Translated using Weblate (German)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-23 13:08:00 +02:00
J-Jamet
ecbee73eae Add view and first implementation of hardware key #8 2022-04-21 18:03:32 +02:00
J-Jamet
3e4452da00 Fix inherited view after orientation change 2022-04-21 17:43:24 +02:00
Linerly
549c690b56 Translated using Weblate (Indonesian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-04-21 01:11:05 +02:00
Oğuz Ersen
aabe06f29b Translated using Weblate (Turkish)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-04-21 01:11:05 +02:00
Eric
82693c5cd3 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-21 01:11:04 +02:00
Ihor Hordiichuk
37a4f26d2f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-04-21 01:11:04 +02:00
solokot
ca94063c7b Translated using Weblate (Russian)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-21 01:11:04 +02:00
Matthaiks
eadc4bf6c2 Translated using Weblate (Polish)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-04-21 01:11:03 +02:00
Retrial
b1c307c86b Translated using Weblate (Greek)
Currently translated at 100.0% (609 of 609 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-04-21 01:11:03 +02:00
J-Jamet
1874a0056d Merge branch 'develop' into feature/Hardware_Key 2022-04-20 14:24:10 +02:00
J-Jamet
48331f9552 Merge tag '3.4.3' into develop
3.4.3
2022-04-19 21:17:37 +02:00
J-Jamet
f907aa578a Merge branch 'release/3.4.3' 2022-04-19 21:17:31 +02:00
J-Jamet
41e2620cc1 Generate auto description for github repo 2022-04-19 21:16:55 +02:00
J-Jamet
e7a82b167a fix Fastfile 2022-04-19 18:18:15 +02:00
J-Jamet
088c556b00 Update fastlane to copy the release file with name 2022-04-19 18:04:12 +02:00
J-Jamet
c80343b6d4 Remove unused import 2022-04-19 16:41:13 +02:00
J-Jamet
4e52a8cf60 Show visual title when entry is available in Magikeyboard 2022-04-19 16:10:40 +02:00
J-Jamet
1ed1d4233f Allow to add entry with no info in Magikeyboard 2022-04-19 16:06:16 +02:00
J-Jamet
6e4626bc02 Fix ask lock when database can be saved 2022-04-19 15:47:21 +02:00
J-Jamet
2608ae247f Fix quick search and better loadGroup implementation #1302 2022-04-19 14:35:28 +02:00
J-Jamet
785586bfe9 Remove "Select share info" setting for Magikeyboard #1304 2022-04-19 12:24:51 +02:00
J-Jamet
bdcbb177ae Upgrade to 3.4.3 2022-04-19 11:36:04 +02:00
J-Jamet
15ac365d79 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-04-15 19:29:56 +02:00
Braja Yudhistira
debbcb753b Translated using Weblate (Indonesian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-04-15 18:27:39 +02:00
Linerly
69d73aeaa4 Translated using Weblate (Indonesian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2022-04-15 18:27:39 +02:00
solokot
dffe53370f Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-15 18:27:37 +02:00
Darin Avdeyeva
4334e6dcdf Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-15 18:27:37 +02:00
Stephan Paternotte
c2c6c093d5 Translated using Weblate (Dutch)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-04-15 18:27:36 +02:00
Cow
77e539eec2 Translated using Weblate (Spanish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2022-04-15 18:27:36 +02:00
J-Jamet
a57970210e Merge tag '3.4.2' into develop
3.4.2
2022-04-15 13:42:49 +02:00
J-Jamet
1b31a46fb7 Merge branch 'release/3.4.2' 2022-04-15 13:42:43 +02:00
J-Jamet
87f19c74fc Add clean in fastlane 2022-04-15 13:23:25 +02:00
J-Jamet
bd157a9724 Fix small UI color 2022-04-15 13:18:11 +02:00
J-Jamet
5a327eb0db Catch reset app timeout for unexpected exceptions 2022-04-15 13:00:41 +02:00
J-Jamet
4b9c0b0109 Fix navigation bar color in dark mode 2022-04-15 12:54:10 +02:00
J-Jamet
df6b75cdbb Fix color 2022-04-15 12:47:50 +02:00
J-Jamet
0b4f8c122b Fix color 2022-04-15 12:46:37 +02:00
J-Jamet
2a87eaf3e5 Upgrade to 3.4.2 and fix service parameter and workflow 2022-04-15 12:21:14 +02:00
J-Jamet
c52266f5cf Merge branch 'master' of github.com:Kunzisoft/KeePassDX 2022-04-14 19:28:49 +02:00
J-Jamet
3b21f8add2 Merge tag '3.4.1' into develop
3.4.1
2022-04-14 19:28:12 +02:00
J-Jamet
6574bd10a0 Merge branch 'release/3.4.1' 2022-04-14 19:28:05 +02:00
J-Jamet
23f3335988 Update version code for deployment 2022-04-14 19:17:49 +02:00
J-Jamet
a5d7f33c82 Fix unexpected lock of the app #1294 2022-04-14 19:11:33 +02:00
J-Jamet
3782c4dac0 Fix styles 2022-04-14 17:17:38 +02:00
J-Jamet
1fc02fd2fe Small UI changes 2022-04-14 15:16:32 +02:00
J-Jamet
cc347c1dbe Update strings 2022-04-14 15:07:32 +02:00
J-Jamet
79ff20eb18 Remove irrelevant Autofill autosearch setting 2022-04-14 14:55:00 +02:00
J-Jamet
e6e8a447da Clear focus in autosearch and update CHANGELOG 2022-04-14 14:32:34 +02:00
J-Jamet
233f0c5bdb Fix another entry in selection mode with Magikeyboard #1293 2022-04-14 14:12:35 +02:00
J-Jamet
9ed4271a14 Fix search mode with Magikeyboard #1292 2022-04-14 12:50:33 +02:00
J-Jamet
470c0b6b43 Update README description 2022-04-14 11:54:16 +02:00
J-Jamet
afa8ae42b9 Upgrade gradle to 3.4.1 2022-04-14 11:45:55 +02:00
Jérémy JAMET
63d426503f Update FUNDING.yml
Fix issuehunt
2022-04-12 20:43:48 +02:00
Jérémy JAMET
ffb7f80b26 Create FUNDING.yml 2022-04-12 20:42:35 +02:00
J-Jamet
63f8826fd8 Merge branch 'master' into develop 2022-04-12 20:08:21 +02:00
J-Jamet
ef836e8b84 Change screenshots 2022-04-12 20:08:10 +02:00
J-Jamet
abc1c43a51 Merge tag '3.4.0' into develop
3.4.0
2022-04-12 19:45:17 +02:00
Hosted Weblate
a4fe92562f Merge branch 'origin/develop' into Weblate. 2022-04-12 12:39:39 +02:00
SC
b9bd1d9d4b Translated using Weblate (Portuguese)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-04-12 12:39:39 +02:00
Milo Ivir
3b6c28488a Translated using Weblate (Croatian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-04-12 12:39:38 +02:00
Eric
875eb3500d Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-12 12:39:38 +02:00
Ihor Hordiichuk
3a88a2451c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-04-12 12:39:37 +02:00
solokot
6800b73a4f Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-12 12:39:37 +02:00
Matthaiks
983404e6d8 Translated using Weblate (Polish)
Currently translated at 100.0% (613 of 613 strings)

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

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-04-12 12:39:36 +02:00
VfBFan
36b317cad8 Translated using Weblate (German)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-12 12:39:36 +02:00
Sebastian
35d74888fb Translated using Weblate (Danish)
Currently translated at 84.3% (517 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2022-04-12 12:39:36 +02:00
J-Jamet
279bd16b74 Best autofill recognition #1250 2022-02-26 13:13:07 +01:00
J-Jamet
2e0081b66c Prepare hardware key in main credential 2022-02-26 12:51:00 +01:00
899 changed files with 18514 additions and 13135 deletions

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

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

3
.gitignore vendored
View File

@@ -80,6 +80,9 @@ art/screen*.png
art/logo_512.png art/logo_512.png
art/store_screens/ art/store_screens/
# Release
releases/*
# Dir linux # Dir linux
.directory .directory
*/.directory */.directory

View File

@@ -1,3 +1,62 @@
KeePassDX(4.0.2)
* Fix Autofill with API 33
KeePassDX(4.0.1)
* Fix back lock #1635 #1629 #1634
* Fix lock button in settings #1630
* Improve theme translation #1631
KeePassDX(4.0.0)
* New UX/UI with Material 3 #1183 #1529 #1428 #1441 #1607
* Material You theme (follow system colors) #1469
* Refactoring inner code #1371
* Migration to API 33
* Cut, copy and delete from search #891 #1308 #1263
* Fix behaviors #1351 #874 #1327
* Fix bugs #1589 #1584 #1545 #1563 #1371 #1609
KeePassDX(3.5.1)
* Fix action dialog with YubiKey challenge-response #1506
KeePassDX(3.5.0)
* Support YubiKey challenge-response #8 #137
* Better exception management during database save #1346
* Add "Screenshot mode" setting #459 #1377 #1354 (Thx @GianpaMX)
* Hide clipboard sensitive text when copy entry field #1386
* Fix attachment download button #1401
* Add monochrome icon #1403 #1404 (Thx @Sandelinos)
* Fix lock with back button #1412 #1414 (Thx @ryg-git)
* Vanadium compatibility #1447 (Thx @flawedworld)
KeePassDX(3.4.5)
* Fix custom data in group (fix KeeShare) #1335
* Fix device credential unlocking #1344
* New clipboard manager #1343
* Keep screen on by default when viewing an entry
* Change the order of the search filters
* Fix searchable selection
KeePassDX(3.4.4)
* Fix crash in New Android 13 #1321
* Better backstack management for selection mode
* Prevent Tapjacking #1318
* Small changes #1298
KeePassDX(3.4.3)
* Remove "Select share info" setting for Magikeyboard #1304
* Fix quick search and better loadGroup implementation #1302
* Fix small bugs
KeePassDX(3.4.2)
* Fix service parameter and workflow to remove notification when service is killed
* Fix color
KeePassDX(3.4.1)
* Fix search mode with Magikeyboard #1292
* Fix select another entry with Magikeyboard #1293
* Fix unexpected lock with Magikeyboard #1294
* Small UI changes
KeePassDX(3.4.0) KeePassDX(3.4.0)
* Passphrase implementation #218 * Passphrase implementation #218
* Show visual password strength indicator with entropy #631 #869 #454 #1270 * Show visual password strength indicator with entropy #631 #869 #454 #1270

10
Gemfile Normal file
View File

@@ -0,0 +1,10 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
source "https://rubygems.org"
gem 'fastlane'
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

220
Gemfile.lock Normal file
View File

@@ -0,0 +1,220 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
rexml
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.794.0)
aws-sdk-core (3.180.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.132.0)
aws-sdk-core (~> 3, >= 3.179.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.100.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.7)
fastlane (2.214.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-versioning_android (0.1.1)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.46.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.1)
google-cloud-storage (1.44.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.7.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.1)
memoist (0.16.2)
mini_magick (4.12.0)
mini_mime (1.1.2)
multi_json (1.15.0)
multipart-post (2.3.0)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.1.1)
os (1.1.4)
plist (3.7.0)
public_suffix (5.0.3)
rake (13.0.6)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.6)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.8.1)
word_wrap (1.0.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane
fastlane-plugin-versioning_android
BUNDLED WITH
2.1.4

View File

@@ -1,6 +1,6 @@
# Android KeePassDX # Android KeePassDX
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeePassDX is a **multi-format KeePass manager for Android devices**. The app allows creating keys and passwords in a secure way by integrating with the Android design standards. <img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> **Lightweight password manager for Android**, KeePassDX allows editing encrypted data in a single file in KeePass format and fill in the forms in a secure way.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220"> <img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
@@ -8,7 +8,7 @@
- Create database files / entries and groups. - Create database files / entries and groups.
- Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm. - Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
- **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC, …). - **Compatible** with the majority of alternative programs (KeePass, KeePassXC, KeeWeb, …).
- Allows opening and **copying URI / URL fields quickly**. - Allows opening and **copying URI / URL fields quickly**.
- **Biometric recognition** for fast unlocking *(fingerprint / face unlock / …)*. - **Biometric recognition** for fast unlocking *(fingerprint / face unlock / …)*.
- **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA). - **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA).
@@ -53,10 +53,12 @@ Optional visual styles are accessible after a contribution (and a congratulatory
[<img src="https://f-droid.org/badge/get-it-on.png" [<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid" alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/) height="80">](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" [<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play" alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free) height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
[<img src="https://raw.githubusercontent.com/Kunzisoft/Github-badge/main/get-it-on-github.png"
alt="Get it on Github"
height="80">](https://github.com/Kunzisoft/KeePassDX/releases)
## Frequently Asked Questions ## Frequently Asked Questions
@@ -72,7 +74,7 @@ Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/w
## License ## License
Copyright © 2022 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com). Copyright © 2023 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of KeePassDX. This file is part of KeePassDX.

View File

@@ -4,16 +4,16 @@ apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion 31 namespace 'com.kunzisoft.keepass'
buildToolsVersion "31.0.0" compileSdkVersion 33
ndkVersion "21.4.7075529" buildToolsVersion "33.0.2"
defaultConfig { defaultConfig {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 31 targetSdkVersion 33
versionCode = 108 versionCode = 125
versionName = "3.4.0" versionName = "4.0.2"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"
@@ -33,7 +33,6 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled = false minifyEnabled = false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
@@ -50,7 +49,9 @@ android {
"\"KeepassDXStyle_Reply\"," + "\"KeepassDXStyle_Reply\"," +
"\"KeepassDXStyle_Reply_Night\"," + "\"KeepassDXStyle_Reply_Night\"," +
"\"KeepassDXStyle_Purple\"," + "\"KeepassDXStyle_Purple\"," +
"\"KeepassDXStyle_Purple_Dark\"}" "\"KeepassDXStyle_Purple_Dark\"," +
"\"KeepassDXStyle_Dynamic_Light\"," +
"\"KeepassDXStyle_Dynamic_Night\"}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}" buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
} }
free { free {
@@ -59,16 +60,16 @@ android {
buildConfigField "String", "BUILD_VERSION", "\"free\"" buildConfigField "String", "BUILD_VERSION", "\"free\""
buildConfigField "boolean", "CLOSED_STORE", "true" buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED", buildConfigField "String[]", "STYLES_DISABLED",
"{\"KeepassDXStyle_Simple\"," + "{\"KeepassDXStyle_Blue\"," +
"\"KeepassDXStyle_Simple_Night\"," +
"\"KeepassDXStyle_Blue\"," +
"\"KeepassDXStyle_Blue_Night\"," + "\"KeepassDXStyle_Blue_Night\"," +
"\"KeepassDXStyle_Red\"," + "\"KeepassDXStyle_Red\"," +
"\"KeepassDXStyle_Red_Night\"," + "\"KeepassDXStyle_Red_Night\"," +
"\"KeepassDXStyle_Reply\"," + "\"KeepassDXStyle_Reply\"," +
"\"KeepassDXStyle_Reply_Night\"," + "\"KeepassDXStyle_Reply_Night\"," +
"\"KeepassDXStyle_Purple\"," + "\"KeepassDXStyle_Purple\"," +
"\"KeepassDXStyle_Purple_Dark\"}" "\"KeepassDXStyle_Purple_Dark\"," +
"\"KeepassDXStyle_Dynamic_Light\"," +
"\"KeepassDXStyle_Dynamic_Night\"}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}" buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIbRfbV8fHLItXo8OcHwrO0sSNblqhPwkc0DPTqg" ] manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIbRfbV8fHLItXo8OcHwrO0sSNblqhPwkc0DPTqg" ]
} }
@@ -93,7 +94,7 @@ android {
} }
} }
def room_version = "2.4.2" def room_version = "2.5.1"
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@@ -101,18 +102,18 @@ dependencies {
implementation "androidx.appcompat:appcompat:$android_appcompat_version" implementation "androidx.appcompat:appcompat:$android_appcompat_version"
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.1.0' implementation 'androidx.biometric:biometric:1.1.0'
implementation 'androidx.media:media:1.5.0' implementation 'androidx.media:media:1.6.0'
// Lifecycle - LiveData - ViewModel - Coroutines // Lifecycle - LiveData - ViewModel - Coroutines
implementation "androidx.core:core-ktx:$android_core_version" implementation "androidx.core:core-ktx:$android_core_version"
implementation 'androidx.fragment:fragment-ktx:1.4.1' implementation 'androidx.fragment:fragment-ktx:1.6.0'
implementation "com.google.android.material:material:$android_material_version" implementation "com.google.android.material:material:$android_material_version"
// Token auto complete // Token auto complete
// From sources until https://github.com/splitwise/TokenAutoComplete/pull/422 fixed // From sources until https://github.com/splitwise/TokenAutoComplete/pull/422 fixed
// implementation "com.splitwise:tokenautocomplete:4.0.0-beta04" implementation "com.splitwise:tokenautocomplete:4.0.0-beta05"
// Database // Database
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
@@ -129,11 +130,10 @@ dependencies {
implementation 'commons-codec:commons-codec:1.15' implementation 'commons-codec:commons-codec:1.15'
// Password generator // Password generator
implementation 'me.gosimple:nbvcxz:1.5.0' implementation 'me.gosimple:nbvcxz:1.5.0'
// Encrypt lib
implementation project(path: ':crypto') // Modules import
// Icon pack implementation project(path: ':database')
implementation project(path: ':icon-pack-classic') implementation project(path: ':icon-pack')
implementation project(path: ':icon-pack-material')
// Tests // Tests
androidTestImplementation "androidx.test:runner:$android_test_version" androidTestImplementation "androidx.test:runner:$android_test_version"

View File

@@ -0,0 +1,90 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "f8fb4aed546de19ae7ca0797f49b26a4",
"entities": [
{
"tableName": "file_database_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `database_alias` TEXT NOT NULL, `keyfile_uri` TEXT, `hardware_key` TEXT, `updated` INTEGER NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "databaseAlias",
"columnName": "database_alias",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "keyFileUri",
"columnName": "keyfile_uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "hardwareKey",
"columnName": "hardware_key",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "updated",
"columnName": "updated",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cipher_database",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `encrypted_value` TEXT NOT NULL, `specs_parameters` TEXT NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptedValue",
"columnName": "encrypted_value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "specParameters",
"columnName": "specs_parameters",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f8fb4aed546de19ae7ca0797f49b26a4')"
]
}
}

View File

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

View File

@@ -7,10 +7,6 @@
<group <group
android:translateX="-12" android:translateX="-12"
android:translateY="-12"> android:translateY="-12">
<path
android:fillColor="#ffa726"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path <path
android:fillColor="#ffffff" android:fillColor="#ffffff"
android:strokeWidth="1.99999297" android:strokeWidth="1.99999297"

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="84"
android:viewportHeight="84">
<group
android:translateX="-12"
android:translateY="-12">
<path
android:fillColor="#ffffff"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
</group>
</vector>

View File

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

View File

@@ -7,10 +7,6 @@
<group <group
android:translateX="-12" android:translateX="-12"
android:translateY="-12"> android:translateY="-12">
<path
android:fillColor="#ffa726"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
<path <path
android:fillColor="#ffffff" android:fillColor="#ffffff"
android:strokeWidth="1.99999297" android:strokeWidth="1.99999297"

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="84"
android:viewportHeight="84">
<group
android:translateX="-12"
android:translateY="-12">
<path
android:fillColor="#ffffff"
android:strokeWidth="1.99999297"
android:pathData="M36,36 L36,40.2422 L67.7578,72 L72,72 L72,67.7578 L40.2422,36 Z" />
</group>
</vector>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.kunzisoft.keepass"
android:installLocation="auto"> android:installLocation="auto">
<supports-screens <supports-screens
android:smallScreens="true" android:smallScreens="true"
@@ -10,6 +9,8 @@
android:anyDensity="true" /> android:anyDensity="true" />
<uses-permission <uses-permission
android:name="android.permission.FOREGROUND_SERVICE" /> android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission <uses-permission
android:name="android.permission.SCHEDULE_EXACT_ALARM" /> android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission <uses-permission
@@ -20,6 +21,12 @@
<uses-permission <uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES" android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" /> tools:ignore="QueryAllPackagesPermission" />
<queries>
<intent>
<action android:name="android.intent.action.CREATE_DOCUMENT" />
<data android:mimeType="application/octet-stream" />
</intent>
</queries>
<application <application
android:label="@string/app_name" android:label="@string/app_name"
@@ -39,7 +46,6 @@
android:value="${googleAndroidBackupAPIKey}" /> android:value="${googleAndroidBackupAPIKey}" />
<activity <activity
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity" android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
android:theme="@style/KeepassDXStyle.SplashScreen"
android:launchMode="singleTop" android:launchMode="singleTop"
android:exported="true" android:exported="true"
android:configChanges="keyboardHidden" android:configChanges="keyboardHidden"
@@ -139,7 +145,7 @@
android:configChanges="keyboardHidden" /> android:configChanges="keyboardHidden" />
<activity <activity
android:name="com.kunzisoft.keepass.activities.EntryEditActivity" android:name="com.kunzisoft.keepass.activities.EntryEditActivity"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustPan" />
<!-- About and Settings --> <!-- About and Settings -->
<activity <activity
android:name="com.kunzisoft.keepass.activities.AboutActivity" android:name="com.kunzisoft.keepass.activities.AboutActivity"
@@ -150,15 +156,21 @@
<activity <activity
android:name="com.kunzisoft.keepass.activities.AutofillLauncherActivity" android:name="com.kunzisoft.keepass.activities.AutofillLauncherActivity"
android:theme="@style/Theme.Transparent" android:theme="@style/Theme.Transparent"
android:configChanges="keyboardHidden" /> android:configChanges="keyboardHidden"
android:excludeFromRecents="true"/>
<activity <activity
android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" /> android:name="com.kunzisoft.keepass.settings.AdvancedUnlockSettingsActivity" />
<activity <activity
android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" /> android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
<activity
android:name="com.kunzisoft.keepass.settings.AppearanceSettingsActivity" />
<activity
android:name="com.kunzisoft.keepass.hardware.HardwareKeyActivity"
android:theme="@style/Theme.Transparent" />
<activity <activity
android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity" android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity"
android:theme="@style/Theme.Transparent" android:theme="@style/Theme.Transparent"
android:excludeFromRecents="true" android:launchMode="singleInstance"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@@ -169,8 +181,9 @@
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="otpauth" android:host="totp" /> <data android:scheme="otpauth"/>
<data android:scheme="otpauth" android:host="hotp" /> <data android:host="totp"/>
<data android:host="hotp"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity

View File

@@ -172,16 +172,16 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
private val onScaleGestureListener: ScaleGestureDetector.OnScaleGestureListener = private val onScaleGestureListener: ScaleGestureDetector.OnScaleGestureListener =
object : ScaleGestureDetector.OnScaleGestureListener { object : ScaleGestureDetector.OnScaleGestureListener {
override fun onScale(detector: ScaleGestureDetector?): Boolean { override fun onScale(detector: ScaleGestureDetector): Boolean {
if (isDragging() || isBitmapTranslateAnimationRunning || isBitmapScaleAnimationRunninng) { if (isDragging() || isBitmapTranslateAnimationRunning || isBitmapScaleAnimationRunninng) {
return false return false
} }
val scaleFactor = detector?.scaleFactor ?: 1.0f val scaleFactor = detector.scaleFactor
val focalX = detector?.focusX ?: bitmapBounds.centerX() val focalX = detector.focusX
val focalY = detector?.focusY ?: bitmapBounds.centerY() val focalY = detector.focusY
if (detector?.scaleFactor == 1.0f) { if (detector.scaleFactor == 1.0f) {
// scale is not changing // scale is not changing
return true return true
} }
@@ -191,22 +191,23 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
return true return true
} }
override fun onScaleBegin(p0: ScaleGestureDetector?): Boolean = true override fun onScaleBegin(p0: ScaleGestureDetector): Boolean = true
override fun onScaleEnd(p0: ScaleGestureDetector) {}
override fun onScaleEnd(p0: ScaleGestureDetector?) {}
} }
private val onGestureListener: GestureDetector.OnGestureListener = private val onGestureListener: GestureDetector.OnGestureListener =
object : GestureDetector.SimpleOnGestureListener() { object : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean = true override fun onDown(e: MotionEvent): Boolean = true
override fun onScroll( override fun onScroll(
e1: MotionEvent?, e1: MotionEvent,
e2: MotionEvent?, e2: MotionEvent,
distanceX: Float, distanceX: Float,
distanceY: Float distanceY: Float
): Boolean { ): Boolean {
if (e2?.pointerCount != 1) { if (e2.pointerCount != 1) {
return true return true
} }
@@ -219,13 +220,11 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
} }
override fun onFling( override fun onFling(
e1: MotionEvent?, e1: MotionEvent,
e2: MotionEvent?, e2: MotionEvent,
velocityX: Float, velocityX: Float,
velocityY: Float velocityY: Float
): Boolean { ): Boolean {
e1 ?: return true
if (scale > minScale) { if (scale > minScale) {
processFlingBitmap(velocityX, velocityY) processFlingBitmap(velocityX, velocityY)
} else { } else {
@@ -234,9 +233,7 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
return true return true
} }
override fun onDoubleTap(e: MotionEvent?): Boolean { override fun onDoubleTap(e: MotionEvent): Boolean {
e ?: return false
if (isBitmapScaleAnimationRunninng) { if (isBitmapScaleAnimationRunninng) {
return true return true
} }
@@ -376,21 +373,21 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
onViewTranslateListener?.onViewTranslate(imageView, amount) onViewTranslateListener?.onViewTranslate(imageView, amount)
} }
.setListener(object : Animator.AnimatorListener { .setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView) onViewTranslateListener?.onDismiss(imageView)
cleanup() cleanup()
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
@@ -409,21 +406,21 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
onViewTranslateListener?.onViewTranslate(imageView, amount) onViewTranslateListener?.onViewTranslate(imageView, amount)
} }
addListener(object : Animator.AnimatorListener { addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
// no op // no op
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView) onViewTranslateListener?.onDismiss(imageView)
cleanup() cleanup()
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
@@ -480,20 +477,20 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
setTransform() setTransform()
} }
addListener(object : Animator.AnimatorListener { addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
isBitmapTranslateAnimationRunning = true isBitmapTranslateAnimationRunning = true
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
isBitmapTranslateAnimationRunning = false isBitmapTranslateAnimationRunning = false
constrainBitmapBounds() constrainBitmapBounds()
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
isBitmapTranslateAnimationRunning = false isBitmapTranslateAnimationRunning = false
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
@@ -531,11 +528,11 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
setTransform() setTransform()
} }
addListener(object : Animator.AnimatorListener { addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
isBitmapScaleAnimationRunninng = true isBitmapScaleAnimationRunninng = true
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
isBitmapScaleAnimationRunninng = false isBitmapScaleAnimationRunninng = false
if (endScale == minScale) { if (endScale == minScale) {
zoomToTargetScale(minScale, focalX, focalY) zoomToTargetScale(minScale, focalX, focalY)
@@ -543,11 +540,11 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
} }
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
isBitmapScaleAnimationRunninng = false isBitmapScaleAnimationRunninng = false
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
@@ -585,11 +582,11 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
setTransform() setTransform()
} }
addListener(object : Animator.AnimatorListener { addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
isBitmapScaleAnimationRunninng = true isBitmapScaleAnimationRunninng = true
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
isBitmapScaleAnimationRunninng = false isBitmapScaleAnimationRunninng = false
if (endScale == minScale) { if (endScale == minScale) {
scale = minScale scale = minScale
@@ -599,11 +596,11 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
} }
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
isBitmapScaleAnimationRunninng = false isBitmapScaleAnimationRunninng = false
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
@@ -669,19 +666,19 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
onViewTranslateListener?.onViewTranslate(this, amount) onViewTranslateListener?.onViewTranslate(this, amount)
} }
.setListener(object : Animator.AnimatorListener { .setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
// no op // no op
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
onViewTranslateListener?.onRestore(imageView) onViewTranslateListener?.onRestore(imageView)
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
// no op // no op
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
@@ -696,19 +693,19 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
onViewTranslateListener?.onViewTranslate(imageView, amount) onViewTranslateListener?.onViewTranslate(imageView, amount)
} }
addListener(object : Animator.AnimatorListener { addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
// no op // no op
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
onViewTranslateListener?.onRestore(imageView) onViewTranslateListener?.onRestore(imageView)
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
// no op // no op
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
@@ -737,27 +734,27 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
onViewTranslateListener?.onViewTranslate(this, amount) onViewTranslateListener?.onViewTranslate(this, amount)
} }
.setListener(object : Animator.AnimatorListener { .setListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
isViewTranslateAnimationRunning = true isViewTranslateAnimationRunning = true
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView) onViewTranslateListener?.onDismiss(imageView)
cleanup() cleanup()
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })
} }
} else { } else {
ObjectAnimator.ofFloat(imageView, View.TRANSLATION_Y, imageView.translationY.toFloat()).apply { ObjectAnimator.ofFloat(imageView, View.TRANSLATION_Y, imageView.translationY).apply {
duration = dismissAnimationDuration duration = dismissAnimationDuration
interpolator = AccelerateDecelerateInterpolator() interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { addUpdateListener {
@@ -766,21 +763,21 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
onViewTranslateListener?.onViewTranslate(imageView, amount) onViewTranslateListener?.onViewTranslate(imageView, amount)
} }
addListener(object : Animator.AnimatorListener { addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator?) { override fun onAnimationStart(p0: Animator) {
isViewTranslateAnimationRunning = true isViewTranslateAnimationRunning = true
} }
override fun onAnimationEnd(p0: Animator?) { override fun onAnimationEnd(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
onViewTranslateListener?.onDismiss(imageView) onViewTranslateListener?.onDismiss(imageView)
cleanup() cleanup()
} }
override fun onAnimationCancel(p0: Animator?) { override fun onAnimationCancel(p0: Animator) {
isViewTranslateAnimationRunning = false isViewTranslateAnimationRunning = false
} }
override fun onAnimationRepeat(p0: Animator?) { override fun onAnimationRepeat(p0: Animator) {
// no op // no op
} }
}) })

View File

@@ -30,6 +30,8 @@ import androidx.core.text.HtmlCompat
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.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import com.kunzisoft.keepass.utils.getPackageInfoCompat
import org.joda.time.DateTime import org.joda.time.DateTime
class AboutActivity : StylishActivity() { class AboutActivity : StylishActivity() {
@@ -45,10 +47,16 @@ class AboutActivity : StylishActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
val appName = if (this.isContributingUser())
getString(R.string.app_name) + " " + getString(R.string.app_name_part3)
else
getString(R.string.app_name)
findViewById<TextView>(R.id.activity_about_app_name).text = appName
var version: String var version: String
var build: String var build: String
try { try {
version = packageManager.getPackageInfo(packageName, 0).versionName version = packageManager.getPackageInfoCompat(packageName).versionName
build = BuildConfig.BUILD_VERSION build = BuildConfig.BUILD_VERSION
} catch (e: NameNotFoundException) { } catch (e: NameNotFoundException) {
Log.w(javaClass.simpleName, "Unable to get the app or the build version", e) Log.w(javaClass.simpleName, "Unable to get the app or the build version", e)
@@ -70,6 +78,12 @@ class AboutActivity : StylishActivity() {
HtmlCompat.FROM_HTML_MODE_LEGACY) HtmlCompat.FROM_HTML_MODE_LEGACY)
} }
findViewById<TextView>(R.id.activity_about_privacy_text).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_privacy),
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
findViewById<TextView>(R.id.activity_about_contribution_text).apply { findViewById<TextView>(R.id.activity_about_contribution_text).apply {
movementMethod = LinkMovementMethod.getInstance() movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_contribution), text = HtmlCompat.fromHtml(getString(R.string.html_about_contribution),

View File

@@ -36,11 +36,14 @@ import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.CompatInlineSuggestionsRequest import com.kunzisoft.keepass.autofill.CompatInlineSuggestionsRequest
import com.kunzisoft.keepass.autofill.KeeAutofillService import com.kunzisoft.keepass.autofill.KeeAutofillService
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.helper.SearchHelper
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.utils.WebDomain
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : DatabaseModeActivity() { class AutofillLauncherActivity : DatabaseModeActivity() {
@@ -58,7 +61,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
return true return true
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
// Retrieve selection mode // Retrieve selection mode
@@ -69,11 +72,11 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
// To pass extra inline request // To pass extra inline request
var compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null var compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
compatInlineSuggestionsRequest = bundle.getParcelable(KEY_INLINE_SUGGESTION) compatInlineSuggestionsRequest = bundle.getParcelableCompat(KEY_INLINE_SUGGESTION)
} }
// Build search param // Build search param
bundle.getParcelable<SearchInfo>(KEY_SEARCH_INFO)?.let { searchInfo -> bundle.getParcelableCompat<SearchInfo>(KEY_SEARCH_INFO)?.let { searchInfo ->
SearchInfo.getConcreteWebDomain( WebDomain.getConcreteWebDomain(
this, this,
searchInfo.webDomain searchInfo.webDomain
) { concreteWebDomain -> ) { concreteWebDomain ->
@@ -99,9 +102,9 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
} }
SpecialMode.REGISTRATION -> { SpecialMode.REGISTRATION -> {
// To register info // To register info
val registerInfo = intent.getParcelableExtra<RegisterInfo>(KEY_REGISTER_INFO) val registerInfo = intent.getParcelableExtraCompat<RegisterInfo>(KEY_REGISTER_INFO)
val searchInfo = SearchInfo(registerInfo?.searchInfo) val searchInfo = SearchInfo(registerInfo?.searchInfo)
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain -> WebDomain.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain searchInfo.webDomain = concreteWebDomain
launchRegistration(database, searchInfo, registerInfo) launchRegistration(database, searchInfo, registerInfo)
} }
@@ -115,7 +118,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
} }
} }
private fun launchSelection(database: Database?, private fun launchSelection(database: ContextualDatabase?,
autofillComponent: AutofillComponent?, autofillComponent: AutofillComponent?,
searchInfo: SearchInfo) { searchInfo: SearchInfo) {
if (autofillComponent == null) { if (autofillComponent == null) {
@@ -158,7 +161,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
} }
} }
private fun launchRegistration(database: Database?, private fun launchRegistration(database: ContextualDatabase?,
searchInfo: SearchInfo, searchInfo: SearchInfo,
registerInfo: RegisterInfo?) { registerInfo: RegisterInfo?) {
if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId, if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId,

View File

@@ -23,6 +23,7 @@ import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@@ -30,17 +31,22 @@ import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.LinearLayoutManager import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat import androidx.core.graphics.BlendModeCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
@@ -51,8 +57,8 @@ 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.legacy.DatabaseLockActivity import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.TagsAdapter import com.kunzisoft.keepass.adapters.TagsAdapter
import com.kunzisoft.keepass.database.ContextualDatabase
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.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation import com.kunzisoft.keepass.education.EntryActivityEducation
@@ -67,17 +73,21 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.UuidUtil import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.view.WindowInsetPosition
import com.kunzisoft.keepass.view.applyWindowInsets
import com.kunzisoft.keepass.view.changeControlColor import com.kunzisoft.keepass.view.changeControlColor
import com.kunzisoft.keepass.view.changeTitleColor import com.kunzisoft.keepass.view.changeTitleColor
import com.kunzisoft.keepass.view.hideByFading import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.setTransparentNavigationBar
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.EntryViewModel import com.kunzisoft.keepass.viewmodels.EntryViewModel
import java.util.* import java.util.UUID
class EntryActivity : DatabaseLockActivity() { class EntryActivity : DatabaseLockActivity() {
private var footer: ViewGroup? = null
private var coordinatorLayout: CoordinatorLayout? = null private var coordinatorLayout: CoordinatorLayout? = null
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var appBarLayout: AppBarLayout? = null private var appBarLayout: AppBarLayout? = null
@@ -98,7 +108,6 @@ class EntryActivity : DatabaseLockActivity() {
private var mMainEntryId: NodeId<UUID>? = null private var mMainEntryId: NodeId<UUID>? = null
private var mHistoryPosition: Int = -1 private var mHistoryPosition: Int = -1
private var mEntryIsHistory: Boolean = false private var mEntryIsHistory: Boolean = false
private var mUrl: String? = null
private var mEntryLoaded = false private var mEntryLoaded = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
@@ -111,9 +120,9 @@ class EntryActivity : DatabaseLockActivity() {
} }
private var mIcon: IconImage? = null private var mIcon: IconImage? = null
private var mColorAccent: Int = 0 private var mColorSecondary: Int = 0
private var mControlColor: Int = 0 private var mColorSurface: Int = 0
private var mColorPrimary: Int = 0 private var mColorOnSurface: Int = 0
private var mColorBackground: Int = 0 private var mColorBackground: Int = 0
private var mBackgroundColor: Int? = null private var mBackgroundColor: Int? = null
private var mForegroundColor: Int? = null private var mForegroundColor: Int? = null
@@ -129,6 +138,7 @@ class EntryActivity : DatabaseLockActivity() {
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
// Get views // Get views
footer = findViewById(R.id.activity_entry_footer)
coordinatorLayout = findViewById(R.id.toolbar_coordinator) coordinatorLayout = findViewById(R.id.toolbar_coordinator)
collapsingToolbarLayout = findViewById(R.id.toolbar_layout) collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
appBarLayout = findViewById(R.id.app_bar) appBarLayout = findViewById(R.id.app_bar)
@@ -140,22 +150,30 @@ class EntryActivity : DatabaseLockActivity() {
lockView = findViewById(R.id.lock_button) lockView = findViewById(R.id.lock_button)
loadingView = findViewById(R.id.loading) loadingView = findViewById(R.id.loading)
// To apply fit window with transparency
setTransparentNavigationBar {
// To fix margin with API 27
ViewCompat.setOnApplyWindowInsetsListener(collapsingToolbarLayout!!, null)
coordinatorLayout?.applyWindowInsets(WindowInsetPosition.TOP)
footer?.applyWindowInsets(WindowInsetPosition.BOTTOM)
}
// Empty title // Empty title
collapsingToolbarLayout?.title = " " collapsingToolbarLayout?.title = " "
toolbar?.title = " " toolbar?.title = " "
// Retrieve the textColor to tint the toolbar // Retrieve the textColor to tint the toolbar
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent)) val taColorSecondary = theme.obtainStyledAttributes(intArrayOf(R.attr.colorSecondary))
val taControlColor = theme.obtainStyledAttributes(intArrayOf(R.attr.toolbarColorControl)) val taColorSurface = theme.obtainStyledAttributes(intArrayOf(R.attr.colorSurface))
val taColorPrimary = theme.obtainStyledAttributes(intArrayOf(R.attr.colorPrimary)) val taColorOnSurface = theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnSurface))
val taColorBackground = theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground)) val taColorBackground = theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
mColorAccent = taColorAccent.getColor(0, Color.BLACK) mColorSecondary = taColorSecondary.getColor(0, Color.BLACK)
mControlColor = taControlColor.getColor(0, Color.BLACK) mColorSurface = taColorSurface.getColor(0, Color.BLACK)
mColorPrimary = taColorPrimary.getColor(0, Color.BLACK) mColorOnSurface = taColorOnSurface.getColor(0, Color.BLACK)
mColorBackground = taColorBackground.getColor(0, Color.BLACK) mColorBackground = taColorBackground.getColor(0, Color.BLACK)
taColorAccent.recycle() taColorSecondary.recycle()
taControlColor.recycle() taColorSurface.recycle()
taColorPrimary.recycle() taColorOnSurface.recycle()
taColorBackground.recycle() taColorBackground.recycle()
// Init Tags adapter // Init Tags adapter
@@ -180,7 +198,7 @@ class EntryActivity : DatabaseLockActivity() {
// Get Entry from UUID // Get Entry from UUID
try { try {
intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { mainEntryId -> intent.getParcelableExtraCompat<NodeId<UUID>>(KEY_ENTRY)?.let { mainEntryId ->
intent.removeExtra(KEY_ENTRY) intent.removeExtra(KEY_ENTRY)
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1) val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
intent.removeExtra(KEY_ENTRY_HISTORY_POSITION) intent.removeExtra(KEY_ENTRY_HISTORY_POSITION)
@@ -224,16 +242,16 @@ class EntryActivity : DatabaseLockActivity() {
this.mEntryIsHistory = entryIsHistory this.mEntryIsHistory = entryIsHistory
// Assign history dedicated view // Assign history dedicated view
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
// TODO History badge
/*
if (entryIsHistory) { if (entryIsHistory) {
collapsingToolbarLayout?.contentScrim = }*/
ColorDrawable(mColorAccent)
}
val entryInfo = entryInfoHistory.entryInfo val entryInfo = entryInfoHistory.entryInfo
// Manage entry copy to start notification if allowed (at the first start) // Manage entry copy to start notification if allowed (at the first start)
if (savedInstanceState == null) { if (savedInstanceState == null) {
// Manage entry to launch copying notification if allowed // Manage entry to launch copying notification if allowed
ClipboardEntryNotificationService.launchNotificationIfAllowed(this, entryInfo) ClipboardEntryNotificationService.checkAndLaunchNotification(this, entryInfo)
// Manage entry to populate Magikeyboard and launch keyboard notification if allowed // Manage entry to populate Magikeyboard and launch keyboard notification if allowed
if (PreferencesUtil.isKeyboardEntrySelectionEnable(this)) { if (PreferencesUtil.isKeyboardEntrySelectionEnable(this)) {
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(this, entryInfo) MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
@@ -246,7 +264,6 @@ class EntryActivity : DatabaseLockActivity() {
if (entryInfo.title.isNotEmpty()) entryInfo.title else UuidUtil.toHexString(entryInfo.id) if (entryInfo.title.isNotEmpty()) entryInfo.title else UuidUtil.toHexString(entryInfo.id)
collapsingToolbarLayout?.title = entryTitle collapsingToolbarLayout?.title = entryTitle
toolbar?.title = entryTitle toolbar?.title = entryTitle
mUrl = entryInfo.url
// Assign tags // Assign tags
val tags = entryInfo.tags val tags = entryInfo.tags
tagsListView?.visibility = if (tags.isEmpty()) View.GONE else View.VISIBLE tagsListView?.visibility = if (tags.isEmpty()) View.GONE else View.VISIBLE
@@ -310,14 +327,14 @@ class EntryActivity : DatabaseLockActivity() {
return coordinatorLayout return coordinatorLayout
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
mEntryViewModel.loadDatabase(database) mEntryViewModel.loadDatabase(database)
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {
@@ -365,8 +382,8 @@ class EntryActivity : DatabaseLockActivity() {
} }
private fun applyToolbarColors() { private fun applyToolbarColors() {
appBarLayout?.setBackgroundColor(mBackgroundColor ?: mColorPrimary) collapsingToolbarLayout?.setBackgroundColor(mBackgroundColor ?: mColorSurface)
collapsingToolbarLayout?.contentScrim = ColorDrawable(mBackgroundColor ?: mColorPrimary) collapsingToolbarLayout?.contentScrim = ColorDrawable(mBackgroundColor ?: mColorSurface)
val backgroundDarker = if (mBackgroundColor != null) { val backgroundDarker = if (mBackgroundColor != null) {
ColorUtils.blendARGB(mBackgroundColor!!, Color.WHITE, 0.1f) ColorUtils.blendARGB(mBackgroundColor!!, Color.WHITE, 0.1f)
} else { } else {
@@ -376,15 +393,15 @@ class EntryActivity : DatabaseLockActivity() {
.createBlendModeColorFilterCompat(backgroundDarker, BlendModeCompat.SRC_IN) .createBlendModeColorFilterCompat(backgroundDarker, BlendModeCompat.SRC_IN)
mIcon?.let { icon -> mIcon?.let { icon ->
titleIconView?.let { iconView -> titleIconView?.let { iconView ->
mIconDrawableFactory?.assignDatabaseIcon( mDatabase?.iconDrawableFactory?.assignDatabaseIcon(
iconView, iconView,
icon, icon,
mForegroundColor ?: mColorAccent mForegroundColor ?: mColorSecondary
) )
} }
} }
toolbar?.changeControlColor(mForegroundColor ?: mControlColor) toolbar?.changeControlColor(mForegroundColor ?: mColorOnSurface)
collapsingToolbarLayout?.changeTitleColor(mForegroundColor ?: mControlColor) collapsingToolbarLayout?.changeTitleColor(mForegroundColor ?: mColorOnSurface)
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -408,9 +425,6 @@ class EntryActivity : DatabaseLockActivity() {
} }
override fun onPrepareOptionsMenu(menu: Menu?): Boolean { override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
if (mUrl?.isEmpty() != false) {
menu?.findItem(R.id.menu_goto_url)?.isVisible = false
}
if (mEntryIsHistory || mDatabaseReadOnly) { if (mEntryIsHistory || mDatabaseReadOnly) {
menu?.findItem(R.id.menu_save_database)?.isVisible = false menu?.findItem(R.id.menu_save_database)?.isVisible = false
menu?.findItem(R.id.menu_merge_database)?.isVisible = false menu?.findItem(R.id.menu_merge_database)?.isVisible = false
@@ -471,12 +485,6 @@ class EntryActivity : DatabaseLockActivity() {
} }
return true return true
} }
R.id.menu_goto_url -> {
mUrl?.let { url ->
UriUtil.gotoUrl(this, url)
}
return true
}
R.id.menu_restore_entry_history -> { R.id.menu_restore_entry_history -> {
mMainEntryId?.let { mainEntryId -> mMainEntryId?.let { mainEntryId ->
restoreEntryHistory( restoreEntryHistory(
@@ -526,7 +534,7 @@ class EntryActivity : DatabaseLockActivity() {
* Open standard Entry activity * Open standard Entry activity
*/ */
fun launch(activity: Activity, fun launch(activity: Activity,
database: Database, database: ContextualDatabase,
entryId: NodeId<UUID>, entryId: NodeId<UUID>,
activityResultLauncher: ActivityResultLauncher<Intent>) { activityResultLauncher: ActivityResultLauncher<Intent>) {
if (database.loaded) { if (database.loaded) {
@@ -542,7 +550,7 @@ class EntryActivity : DatabaseLockActivity() {
* Open history Entry activity * Open history Entry activity
*/ */
fun launch(activity: Activity, fun launch(activity: Activity,
database: Database, database: ContextualDatabase,
entryId: NodeId<UUID>, entryId: NodeId<UUID>,
historyPosition: Int, historyPosition: Int,
activityResultLauncher: ActivityResultLauncher<Intent>) { activityResultLauncher: ActivityResultLauncher<Intent>) {

View File

@@ -19,8 +19,6 @@
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.app.Activity import android.app.Activity
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@@ -32,7 +30,10 @@ import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.* import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ProgressBar
import android.widget.Spinner
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
@@ -44,10 +45,16 @@ import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.timepicker.MaterialTimePicker
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.* import com.kunzisoft.keepass.activities.dialogs.ColorPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.EntryCustomFieldDialogFragment
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment
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.dialogs.ReplaceFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
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.ExternalFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
@@ -55,11 +62,16 @@ import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.TemplatesSelectorAdapter import com.kunzisoft.keepass.adapters.TemplatesSelectorAdapter
import com.kunzisoft.keepass.autofill.AutofillComponent import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.template.Template import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.education.EntryEditActivityEducation import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
@@ -75,22 +87,29 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.view.* import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.view.ToolbarAction
import com.kunzisoft.keepass.view.WindowInsetPosition
import com.kunzisoft.keepass.view.applyWindowInsets
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.setTransparentNavigationBar
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.view.updateLockPaddingLeft
import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import org.joda.time.DateTime import java.util.UUID
import java.util.*
class EntryEditActivity : DatabaseLockActivity(), class EntryEditActivity : DatabaseLockActivity(),
EntryCustomFieldDialogFragment.EntryCustomFieldListener, EntryCustomFieldDialogFragment.EntryCustomFieldListener,
SetOTPDialogFragment.CreateOtpListener, SetOTPDialogFragment.CreateOtpListener,
DatePickerDialog.OnDateSetListener,
TimePickerDialog.OnTimeSetListener,
FileTooBigDialogFragment.ActionChooseListener, FileTooBigDialogFragment.ActionChooseListener,
ReplaceFileDialogFragment.ActionChooseListener { ReplaceFileDialogFragment.ActionChooseListener {
// Views // Views
private var footer: ViewGroup? = null
private var container: ViewGroup? = null
private var coordinatorLayout: CoordinatorLayout? = null private var coordinatorLayout: CoordinatorLayout? = null
private var scrollView: NestedScrollView? = null private var scrollView: NestedScrollView? = null
private var templateSelectorSpinner: Spinner? = null private var templateSelectorSpinner: Spinner? = null
@@ -143,10 +162,8 @@ class EntryEditActivity : DatabaseLockActivity(),
// Bottom Bar // Bottom Bar
entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar) entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
setSupportActionBar(entryEditAddToolBar) footer = findViewById(R.id.activity_entry_edit_footer)
supportActionBar?.setDisplayHomeAsUpEnabled(true) container = findViewById(R.id.activity_entry_edit_container)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout) coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
scrollView = findViewById(R.id.entry_edit_scroll) scrollView = findViewById(R.id.entry_edit_scroll)
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
@@ -155,19 +172,30 @@ class EntryEditActivity : DatabaseLockActivity(),
validateButton = findViewById(R.id.entry_edit_validate) validateButton = findViewById(R.id.entry_edit_validate)
loadingView = findViewById(R.id.loading) loadingView = findViewById(R.id.loading)
setSupportActionBar(entryEditAddToolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
// To apply fit window with transparency
setTransparentNavigationBar(applyToStatusBar = true) {
container?.applyWindowInsets(WindowInsetPosition.TOP)
footer?.applyWindowInsets(WindowInsetPosition.BOTTOM)
}
stopService(Intent(this, ClipboardEntryNotificationService::class.java)) stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java)) stopService(Intent(this, KeyboardEntryNotificationService::class.java))
// Entry is retrieve, it's an entry to update // Entry is retrieve, it's an entry to update
var entryId: NodeId<UUID>? = null var entryId: NodeId<UUID>? = null
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate -> intent.getParcelableExtraCompat<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate ->
intent.removeExtra(KEY_ENTRY) intent.removeExtra(KEY_ENTRY)
entryId = entryToUpdate entryId = entryToUpdate
} }
// Parent is retrieve, it's a new entry to create // Parent is retrieve, it's a new entry to create
var parentId: NodeId<*>? = null var parentId: NodeId<*>? = null
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { parent -> intent.getParcelableExtraCompat<NodeId<*>>(KEY_PARENT)?.let { parent ->
intent.removeExtra(KEY_PARENT) intent.removeExtra(KEY_PARENT)
parentId = parent parentId = parent
} }
@@ -184,7 +212,7 @@ class EntryEditActivity : DatabaseLockActivity(),
mExternalFileHelper = ExternalFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri -> mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let { attachmentToUploadUri -> uri?.let { attachmentToUploadUri ->
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile -> attachmentToUploadUri.getDocumentFile(this)?.also { documentFile ->
documentFile.name?.let { fileName -> documentFile.name?.let { fileName ->
if (documentFile.length() > MAX_WARNING_BINARY_FILE) { if (documentFile.length() > MAX_WARNING_BINARY_FILE) {
FileTooBigDialogFragment.build(attachmentToUploadUri, fileName) FileTooBigDialogFragment.build(attachmentToUploadUri, fileName)
@@ -202,7 +230,7 @@ class EntryEditActivity : DatabaseLockActivity(),
// Lock button // Lock button
lockView?.setOnClickListener { lockAndExit() } lockView?.setOnClickListener { lockAndExit() }
// Save button // Save button
validateButton?.setOnClickListener { saveEntry() } validateButton?.setOnClickListener { validateEntry() }
mEntryEditViewModel.onTemplateChanged.observe(this) { template -> mEntryEditViewModel.onTemplateChanged.observe(this) { template ->
this.mTemplate = template this.mTemplate = template
@@ -220,7 +248,7 @@ class EntryEditActivity : DatabaseLockActivity(),
this@EntryEditActivity, this@EntryEditActivity,
templates templates
).apply { ).apply {
iconDrawableFactory = mIconDrawableFactory iconDrawableFactory = mDatabase?.iconDrawableFactory
} }
adapter = mTemplatesSelectorAdapter adapter = mTemplatesSelectorAdapter
val selectedTemplate = if (mTemplate != null) val selectedTemplate = if (mTemplate != null)
@@ -271,14 +299,20 @@ class EntryEditActivity : DatabaseLockActivity(),
mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant -> mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant ->
if (dateInstant.type == DateInstant.Type.TIME) { if (dateInstant.type == DateInstant.Type.TIME) {
// Launch the time picker // Launch the time picker
val dateTime = DateTime(dateInstant.date) MaterialTimePicker.Builder().build().apply {
TimePickerFragment.getInstance(dateTime.hourOfDay, dateTime.minuteOfHour) addOnPositiveButtonClickListener {
.show(supportFragmentManager, "TimePickerFragment") mEntryEditViewModel.selectTime(this.hour, this.minute)
}
show(supportFragmentManager, "TimePickerFragment")
}
} else { } else {
// Launch the date picker // Launch the date picker
val dateTime = DateTime(dateInstant.date) MaterialDatePicker.Builder.datePicker().build().apply {
DatePickerFragment.getInstance(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth) addOnPositiveButtonClickListener {
.show(supportFragmentManager, "DatePickerFragment") mEntryEditViewModel.selectDate(it)
}
show(supportFragmentManager, "DatePickerFragment")
}
} }
} }
@@ -367,19 +401,19 @@ class EntryEditActivity : DatabaseLockActivity(),
return true return true
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
mAllowCustomFields = database?.allowEntryCustomFields() == true mAllowCustomFields = database?.allowEntryCustomFields() == true
mAllowOTP = database?.allowOTP == true mAllowOTP = database?.allowOTP == true
mEntryEditViewModel.loadDatabase(database) mEntryEditViewModel.loadDatabase(database)
mTemplatesSelectorAdapter?.apply { mTemplatesSelectorAdapter?.apply {
iconDrawableFactory = mIconDrawableFactory iconDrawableFactory = mDatabase?.iconDrawableFactory
notifyDataSetChanged() notifyDataSetChanged()
} }
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {
@@ -432,17 +466,18 @@ class EntryEditActivity : DatabaseLockActivity(),
finishForEntryResult(entry) finishForEntryResult(entry)
} }
private fun entryValidatedForKeyboardSelection(database: Database, entry: Entry) { private fun entryValidatedForKeyboardSelection(database: ContextualDatabase, entry: Entry) {
// Populate Magikeyboard with entry // Populate Magikeyboard with entry
populateKeyboardAndMoveAppToBackground(this, MagikeyboardService.populateKeyboardAndMoveAppToBackground(
entry.getEntryInfo(database), this,
intent) entry.getEntryInfo(database)
)
onValidateSpecialMode() onValidateSpecialMode()
// Don't keep activity history for entry edition // Don't keep activity history for entry edition
finishForEntryResult(entry) finishForEntryResult(entry)
} }
private fun entryValidatedForAutofillSelection(database: Database, entry: Entry) { private fun entryValidatedForAutofillSelection(database: ContextualDatabase, entry: Entry) {
// Build Autofill response with the entry selected // Build Autofill response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity, AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity,
@@ -477,6 +512,11 @@ class EntryEditActivity : DatabaseLockActivity(),
} }
} }
} }
// Keep the screen on
if (PreferencesUtil.isKeepScreenOnEnabled(this)) {
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
} }
override fun onPause() { override fun onPause() {
@@ -539,9 +579,9 @@ class EntryEditActivity : DatabaseLockActivity(),
} }
/** /**
* Saves the new entry or update an existing entry in the database * Validate the new entry or update an existing entry in the database
*/ */
private fun saveEntry() { private fun validateEntry() {
mAttachmentFileBinderManager?.stopUploadAllAttachments() mAttachmentFileBinderManager?.stopUploadAllAttachments()
mEntryEditViewModel.requestEntryInfoUpdate(mDatabase) mEntryEditViewModel.requestEntryInfoUpdate(mDatabase)
} }
@@ -623,14 +663,29 @@ class EntryEditActivity : DatabaseLockActivity(),
) )
if (!addAttachmentEducationPerformed) { if (!addAttachmentEducationPerformed) {
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp) val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
setupOtpView != null val validateEntryEducationPerformed = setupOtpView != null
&& setupOtpView.isVisible && setupOtpView.isVisible
&& mEntryEditActivityEducation.checkAndPerformedSetUpOTPEducation( && mEntryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
setupOtpView, setupOtpView,
{ {
setupOtp() setupOtp()
},
{
performedNextEducation()
} }
) )
if (!validateEntryEducationPerformed) {
val entryValidateView = validateButton
mAllowCustomFields
&& entryValidateView != null
&& entryValidateView.isVisible
&& mEntryEditActivityEducation.checkAndPerformedValidateEntryEducation(
entryValidateView,
{
validateEntry()
}
)
}
} }
} }
} }
@@ -651,28 +706,16 @@ class EntryEditActivity : DatabaseLockActivity(),
return true return true
} }
android.R.id.home -> { android.R.id.home -> {
onBackPressed() onDatabaseBackPressed()
} }
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun onDateSet(datePicker: DatePicker?, year: Int, month: Int, day: Int) { override fun onDatabaseBackPressed() {
// To fix android 4.4 issue
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
if (datePicker?.isShown == true) {
mEntryEditViewModel.selectDate(year, month, day)
}
}
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
mEntryEditViewModel.selectTime(hours, minutes)
}
override fun onBackPressed() {
onApprovedBackPressed { onApprovedBackPressed {
super@EntryEditActivity.onBackPressed() super@EntryEditActivity.onDatabaseBackPressed()
} }
} }
@@ -727,7 +770,7 @@ class EntryEditActivity : DatabaseLockActivity(),
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
entryAddedOrUpdatedListener.invoke( entryAddedOrUpdatedListener.invoke(
result.data?.getParcelableExtra(ADD_OR_UPDATE_ENTRY_KEY) result.data?.getParcelableExtraCompat(ADD_OR_UPDATE_ENTRY_KEY)
) )
} else { } else {
entryAddedOrUpdatedListener.invoke(null) entryAddedOrUpdatedListener.invoke(null)
@@ -740,7 +783,7 @@ class EntryEditActivity : DatabaseLockActivity(),
return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
entryAddedOrUpdatedListener.invoke( entryAddedOrUpdatedListener.invoke(
result.data?.getParcelableExtra(ADD_OR_UPDATE_ENTRY_KEY) result.data?.getParcelableExtraCompat(ADD_OR_UPDATE_ENTRY_KEY)
) )
} else { } else {
entryAddedOrUpdatedListener.invoke(null) entryAddedOrUpdatedListener.invoke(null)
@@ -752,7 +795,7 @@ class EntryEditActivity : DatabaseLockActivity(),
* Launch EntryEditActivity to update an existing entry by his [entryId] * Launch EntryEditActivity to update an existing entry by his [entryId]
*/ */
fun launchToUpdate(activity: Activity, fun launchToUpdate(activity: Activity,
database: Database, database: ContextualDatabase,
entryId: NodeId<UUID>, entryId: NodeId<UUID>,
activityResultLauncher: ActivityResultLauncher<Intent>) { activityResultLauncher: ActivityResultLauncher<Intent>) {
if (database.loaded && !database.isReadOnly) { if (database.loaded && !database.isReadOnly) {
@@ -768,7 +811,7 @@ class EntryEditActivity : DatabaseLockActivity(),
* Launch EntryEditActivity to add a new entry in an existent group * Launch EntryEditActivity to add a new entry in an existent group
*/ */
fun launchToCreate(activity: Activity, fun launchToCreate(activity: Activity,
database: Database, database: ContextualDatabase,
groupId: NodeId<*>, groupId: NodeId<*>,
activityResultLauncher: ActivityResultLauncher<Intent>) { activityResultLauncher: ActivityResultLauncher<Intent>) {
if (database.loaded && !database.isReadOnly) { if (database.loaded && !database.isReadOnly) {
@@ -781,7 +824,7 @@ class EntryEditActivity : DatabaseLockActivity(),
} }
fun launchToUpdateForSave(context: Context, fun launchToUpdateForSave(context: Context,
database: Database, database: ContextualDatabase,
entryId: NodeId<UUID>, entryId: NodeId<UUID>,
searchInfo: SearchInfo) { searchInfo: SearchInfo) {
if (database.loaded && !database.isReadOnly) { if (database.loaded && !database.isReadOnly) {
@@ -798,7 +841,7 @@ class EntryEditActivity : DatabaseLockActivity(),
} }
fun launchToCreateForSave(context: Context, fun launchToCreateForSave(context: Context,
database: Database, database: ContextualDatabase,
groupId: NodeId<*>, groupId: NodeId<*>,
searchInfo: SearchInfo) { searchInfo: SearchInfo) {
if (database.loaded && !database.isReadOnly) { if (database.loaded && !database.isReadOnly) {
@@ -818,7 +861,7 @@ class EntryEditActivity : DatabaseLockActivity(),
* Launch EntryEditActivity to add a new entry in keyboard selection * Launch EntryEditActivity to add a new entry in keyboard selection
*/ */
fun launchForKeyboardSelectionResult(context: Context, fun launchForKeyboardSelectionResult(context: Context,
database: Database, database: ContextualDatabase,
groupId: NodeId<*>, groupId: NodeId<*>,
searchInfo: SearchInfo? = null) { searchInfo: SearchInfo? = null) {
if (database.loaded && !database.isReadOnly) { if (database.loaded && !database.isReadOnly) {
@@ -839,7 +882,7 @@ class EntryEditActivity : DatabaseLockActivity(),
*/ */
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: AppCompatActivity, fun launchForAutofillResult(activity: AppCompatActivity,
database: Database, database: ContextualDatabase,
activityResultLauncher: ActivityResultLauncher<Intent>?, activityResultLauncher: ActivityResultLauncher<Intent>?,
autofillComponent: AutofillComponent, autofillComponent: AutofillComponent,
groupId: NodeId<*>, groupId: NodeId<*>,
@@ -863,7 +906,7 @@ class EntryEditActivity : DatabaseLockActivity(),
* Launch EntryEditActivity to register an updated entry (from autofill) * Launch EntryEditActivity to register an updated entry (from autofill)
*/ */
fun launchToUpdateForRegistration(context: Context, fun launchToUpdateForRegistration(context: Context,
database: Database, database: ContextualDatabase,
entryId: NodeId<UUID>, entryId: NodeId<UUID>,
registerInfo: RegisterInfo? = null) { registerInfo: RegisterInfo? = null) {
if (database.loaded && !database.isReadOnly) { if (database.loaded && !database.isReadOnly) {
@@ -883,7 +926,7 @@ class EntryEditActivity : DatabaseLockActivity(),
* Launch EntryEditActivity to register a new entry (from autofill) * Launch EntryEditActivity to register a new entry (from autofill)
*/ */
fun launchToCreateForRegistration(context: Context, fun launchToCreateForRegistration(context: Context,
database: Database, database: ContextualDatabase,
groupId: NodeId<*>, groupId: NodeId<*>,
registerInfo: RegisterInfo? = null) { registerInfo: RegisterInfo? = null) {
if (database.loaded && !database.isReadOnly) { if (database.loaded && !database.isReadOnly) {

View File

@@ -19,23 +19,21 @@
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Context import android.content.Context
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.widget.Toast import android.widget.Toast
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.helper.SearchHelper
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.utils.KeyboardUtil.isKeyboardActivatedInSettings
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.utils.WebDomain
/** /**
* Activity to search or select entry in database, * Activity to search or select entry in database,
@@ -48,20 +46,20 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
} }
override fun finishActivityIfReloadRequested(): Boolean { override fun finishActivityIfReloadRequested(): Boolean {
return true return false
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
val keySelectionBundle = intent.getBundleExtra(KEY_SELECTION_BUNDLE) val keySelectionBundle = intent.getBundleExtra(KEY_SELECTION_BUNDLE)
if (keySelectionBundle != null) { if (keySelectionBundle != null) {
// To manage package name // To manage package name
var searchInfo = SearchInfo() var searchInfo = SearchInfo()
keySelectionBundle.getParcelable<SearchInfo>(KEY_SEARCH_INFO)?.let { mSearchInfo -> keySelectionBundle.getParcelableCompat<SearchInfo>(KEY_SEARCH_INFO)?.let { mSearchInfo ->
searchInfo = mSearchInfo searchInfo = mSearchInfo
} }
launch(database, searchInfo, true) launch(database, searchInfo)
} else { } else {
// To manage share // To manage share
var sharedWebDomain: String? = null var sharedWebDomain: String? = null
@@ -78,6 +76,7 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
sharedWebDomain = Uri.parse(extra).host sharedWebDomain = Uri.parse(extra).host
} }
} }
launchSelection(database, sharedWebDomain, otpString)
} }
Intent.ACTION_VIEW -> { Intent.ACTION_VIEW -> {
// Retrieve OTP // Retrieve OTP
@@ -85,29 +84,40 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
if (OtpEntryFields.isOTPUri(extra)) if (OtpEntryFields.isOTPUri(extra))
otpString = extra otpString = extra
} }
launchSelection(database, sharedWebDomain, otpString)
}
else -> {
if (database != null) {
GroupActivity.launch(this, database)
} else {
FileDatabaseSelectActivity.launch(this)
}
} }
else -> {}
} }
}
finish()
}
// Build domain search param private fun launchSelection(database: ContextualDatabase?,
val searchInfo = SearchInfo().apply { sharedWebDomain: String?,
this.webDomain = sharedWebDomain otpString: String?) {
this.otpString = otpString // Build domain search param
} val searchInfo = SearchInfo().apply {
this.webDomain = sharedWebDomain
this.otpString = otpString
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain -> WebDomain.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain searchInfo.webDomain = concreteWebDomain
launch(database, searchInfo) launch(database, searchInfo)
}
} }
} }
private fun launch(database: Database?, private fun launch(database: ContextualDatabase?,
searchInfo: SearchInfo, searchInfo: SearchInfo) {
forceSelection: Boolean = false) {
// Setting to integrate Magikeyboard // Setting to integrate Magikeyboard
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this) val searchShareForMagikeyboard = isKeyboardActivatedInSettings()
// If database is open // If database is open
val readOnly = database?.isReadOnly != false val readOnly = database?.isReadOnly != false
@@ -130,21 +140,22 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
.show() .show()
} }
} else if (searchShareForMagikeyboard) { } else if (searchShareForMagikeyboard) {
if (items.size == 1 && !forceSelection) { MagikeyboardService.performSelection(
// Automatically populate keyboard items,
val entryPopulate = items[0] { entryInfo ->
populateKeyboardAndMoveAppToBackground( // Automatically populate keyboard
this, MagikeyboardService.populateKeyboardAndMoveAppToBackground(
entryPopulate, this,
intent) entryInfo
Log.e("TEST", "One item activity") )
} else { },
// Select the one we want { autoSearch ->
GroupActivity.launchForKeyboardSelectionResult(this, GroupActivity.launchForKeyboardSelectionResult(this,
openedDatabase, openedDatabase,
searchInfo, searchInfo,
true) autoSearch)
} }
)
} else { } else {
GroupActivity.launchForSearchResult(this, GroupActivity.launchForSearchResult(this,
openedDatabase, openedDatabase,
@@ -166,13 +177,13 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
Toast.LENGTH_LONG) Toast.LENGTH_LONG)
.show() .show()
} }
} else if (readOnly || searchShareForMagikeyboard) { } else if (searchShareForMagikeyboard) {
GroupActivity.launchForKeyboardSelectionResult(this, GroupActivity.launchForKeyboardSelectionResult(this,
openedDatabase, openedDatabase,
searchInfo, searchInfo,
false) false)
} else { } else {
GroupActivity.launchForSaveResult(this, GroupActivity.launchForSearchResult(this,
openedDatabase, openedDatabase,
searchInfo, searchInfo,
false) false)
@@ -192,7 +203,6 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
} }
} }
) )
finish()
} }
companion object { companion object {
@@ -214,14 +224,3 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
} }
} }
} }
fun populateKeyboardAndMoveAppToBackground(activity: Activity,
entry: EntryInfo,
intent: Intent,
toast: Boolean = true) {
// Populate Magikeyboard with entry
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
// Consume the selection mode
EntrySelectionHelper.removeModesFromIntent(intent)
activity.moveTaskToBack(true)
}

View File

@@ -53,9 +53,10 @@ import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillComponent import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
@@ -64,8 +65,16 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.* import com.kunzisoft.keepass.utils.DexUtil
import com.kunzisoft.keepass.utils.MagikeyboardUtil
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.utils.parseUri
import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.utils.allowCreateDocumentByStorageAccessFramework
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel
import java.io.FileNotFoundException import java.io.FileNotFoundException
@@ -155,8 +164,9 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen -> mAdapterDatabaseHistory?.setOnFileDatabaseHistoryOpenListener { fileDatabaseHistoryEntityToOpen ->
fileDatabaseHistoryEntityToOpen.databaseUri?.let { databaseFileUri -> fileDatabaseHistoryEntityToOpen.databaseUri?.let { databaseFileUri ->
launchPasswordActivity( launchPasswordActivity(
databaseFileUri, databaseFileUri,
fileDatabaseHistoryEntityToOpen.keyFileUri fileDatabaseHistoryEntityToOpen.keyFileUri,
fileDatabaseHistoryEntityToOpen.hardwareKey
) )
} }
} }
@@ -176,7 +186,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) { && savedInstanceState.getBoolean(EXTRA_STAY, false))) {
val databasePath = PreferencesUtil.getDefaultDatabasePath(this) val databasePath = PreferencesUtil.getDefaultDatabasePath(this)
UriUtil.parse(databasePath)?.let { databaseFileUri -> databasePath?.parseUri()?.let { databaseFileUri ->
launchPasswordActivityWithPath(databaseFileUri) launchPasswordActivityWithPath(databaseFileUri)
} ?: run { } ?: run {
Log.i(TAG, "No default database to prepare") Log.i(TAG, "No default database to prepare")
@@ -186,7 +196,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
// Retrieve the database URI provided by file manager after an orientation change // Retrieve the database URI provided by file manager after an orientation change
if (savedInstanceState != null if (savedInstanceState != null
&& savedInstanceState.containsKey(EXTRA_DATABASE_URI)) { && savedInstanceState.containsKey(EXTRA_DATABASE_URI)) {
mDatabaseFileUri = savedInstanceState.getParcelable(EXTRA_DATABASE_URI) mDatabaseFileUri = savedInstanceState.getParcelableCompat(EXTRA_DATABASE_URI)
} }
// Observe list of databases // Observe list of databases
@@ -225,7 +235,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
} }
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
if (database != null) { if (database != null) {
launchGroupActivityIfLoaded(database) launchGroupActivityIfLoaded(database)
@@ -233,7 +243,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {
@@ -244,13 +254,14 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
when (actionTask) { when (actionTask) {
ACTION_DATABASE_CREATE_TASK, ACTION_DATABASE_CREATE_TASK,
ACTION_DATABASE_LOAD_TASK -> { ACTION_DATABASE_LOAD_TASK -> {
result.data?.getParcelable<Uri?>(DATABASE_URI_KEY)?.let { databaseUri -> result.data?.getParcelableCompat<Uri>(DATABASE_URI_KEY)?.let { databaseUri ->
val mainCredential = val mainCredential =
result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) result.data?.getParcelableCompat(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY)
?: MainCredential() ?: MainCredential()
databaseFilesViewModel.addDatabaseFile( databaseFilesViewModel.addDatabaseFile(
databaseUri, databaseUri,
mainCredential.keyFileUri mainCredential.keyFileUri,
mainCredential.hardwareKey
) )
} }
} }
@@ -268,18 +279,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
launchGroupActivityIfLoaded(database) launchGroupActivityIfLoaded(database)
} }
} }
} else {
var resultError = ""
val resultMessage = result.message
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError)
Snackbar.make(coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
} }
coordinatorLayout.showActionErrorIfNeeded(result)
} }
/** /**
@@ -297,10 +298,11 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_LONG).asError().show() Snackbar.make(coordinatorLayout, error, Snackbar.LENGTH_LONG).asError().show()
} }
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) { private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?, hardwareKey: HardwareKey?) {
MainCredentialActivity.launch(this, MainCredentialActivity.launch(this,
databaseUri, databaseUri,
keyFile, keyFile,
hardwareKey,
{ exception -> { exception ->
fileNoFoundAction(exception) fileNoFoundAction(exception)
}, },
@@ -309,7 +311,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
mAutofillActivityResultLauncher) mAutofillActivityResultLauncher)
} }
private fun launchGroupActivityIfLoaded(database: Database) { private fun launchGroupActivityIfLoaded(database: ContextualDatabase) {
if (database.loaded) { if (database.loaded) {
GroupActivity.launch(this, GroupActivity.launch(this,
database, database,
@@ -320,18 +322,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
} }
} }
override fun onValidateSpecialMode() {
super.onValidateSpecialMode()
finish()
}
override fun onCancelSpecialMode() {
super.onCancelSpecialMode()
finish()
}
private fun launchPasswordActivityWithPath(databaseUri: Uri) { private fun launchPasswordActivityWithPath(databaseUri: Uri) {
launchPasswordActivity(databaseUri, null) launchPasswordActivity(databaseUri, null, null)
// Delete flickering for kitkat <= // Delete flickering for kitkat <=
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
overridePendingTransition(0, 0) overridePendingTransition(0, 0)
@@ -341,12 +333,12 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
super.onResume() super.onResume()
// Define special title // Define special title
specialTitle?.isVisible = UriUtil.contributingUser(this) specialTitle?.isVisible = this.isContributingUser()
// 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 (ExternalFileHelper.allowCreateDocumentByStorageAccessFramework(packageManager)) { if (packageManager.allowCreateDocumentByStorageAccessFramework()) {
// 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{
@@ -441,7 +433,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url) android.R.id.home -> this.openUrl(R.string.file_manager_explanation_url)
} }
MenuUtil.onDefaultMenuOptionsItemSelected(this, item) MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)

View File

@@ -41,16 +41,24 @@ import com.kunzisoft.keepass.activities.fragments.IconPickerFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.updateLockPaddingLeft import com.kunzisoft.keepass.view.updateLockPaddingLeft
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class IconPickerActivity : DatabaseLockActivity() { class IconPickerActivity : DatabaseLockActivity() {
@@ -96,7 +104,7 @@ class IconPickerActivity : DatabaseLockActivity() {
lockAndExit() lockAndExit()
} }
intent?.getParcelableExtra<IconImage>(EXTRA_ICON)?.let { intent?.getParcelableExtraCompat<IconImage>(EXTRA_ICON)?.let {
mIconImage = it mIconImage = it
} }
@@ -112,7 +120,7 @@ class IconPickerActivity : DatabaseLockActivity() {
), ICON_PICKER_FRAGMENT_TAG) ), ICON_PICKER_FRAGMENT_TAG)
} }
} else { } else {
mIconImage = savedInstanceState.getParcelable(EXTRA_ICON) ?: mIconImage mIconImage = savedInstanceState.getParcelableCompat(EXTRA_ICON) ?: mIconImage
} }
iconPickerViewModel.standardIconPicked.observe(this) { iconStandard -> iconPickerViewModel.standardIconPicked.observe(this) { iconStandard ->
@@ -166,7 +174,7 @@ class IconPickerActivity : DatabaseLockActivity() {
return true return true
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
if (database?.allowCustomIcons == true) { if (database?.allowCustomIcons == true) {
@@ -231,7 +239,7 @@ class IconPickerActivity : DatabaseLockActivity() {
if (mCustomIconsSelectionMode) { if (mCustomIconsSelectionMode) {
iconPickerViewModel.deselectAllCustomIcons() iconPickerViewModel.deselectAllCustomIcons()
} else { } else {
onBackPressed() onDatabaseBackPressed()
} }
} }
R.id.menu_edit -> { R.id.menu_edit -> {
@@ -243,7 +251,7 @@ class IconPickerActivity : DatabaseLockActivity() {
} }
} }
R.id.menu_external_icon -> { R.id.menu_external_icon -> {
UriUtil.gotoUrl(this, R.string.external_icon_url) this.openUrl(R.string.external_icon_url)
} }
} }
@@ -257,7 +265,7 @@ class IconPickerActivity : DatabaseLockActivity() {
// on Progress with thread // on Progress with thread
val asyncResult: Deferred<IconPickerViewModel.IconCustomState?> = async { val asyncResult: Deferred<IconPickerViewModel.IconCustomState?> = async {
val iconCustomState = IconPickerViewModel.IconCustomState(null, true, R.string.error_upload_file) val iconCustomState = IconPickerViewModel.IconCustomState(null, true, R.string.error_upload_file)
UriUtil.getFileData(this@IconPickerActivity, iconToUploadUri)?.also { documentFile -> iconToUploadUri?.getDocumentFile(this@IconPickerActivity)?.also { documentFile ->
if (documentFile.length() > MAX_ICON_SIZE) { if (documentFile.length() > MAX_ICON_SIZE) {
iconCustomState.errorStringId = R.string.error_file_to_big iconCustomState.errorStringId = R.string.error_file_to_big
} else { } else {
@@ -321,9 +329,9 @@ class IconPickerActivity : DatabaseLockActivity() {
}) })
} }
override fun onBackPressed() { override fun onDatabaseBackPressed() {
setResult() setResult()
super.onBackPressed() super.onDatabaseBackPressed()
} }
companion object { companion object {
@@ -336,7 +344,7 @@ class IconPickerActivity : DatabaseLockActivity() {
listener: (icon: IconImage) -> Unit): ActivityResultLauncher<Intent> { listener: (icon: IconImage) -> Unit): ActivityResultLauncher<Intent> {
return context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> return context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
listener.invoke(result.data?.getParcelableExtra(EXTRA_ICON) ?: IconImage()) listener.invoke(result.data?.getParcelableExtraCompat(EXTRA_ICON) ?: IconImage())
} }
} }
} }

View File

@@ -33,9 +33,10 @@ import androidx.appcompat.widget.Toolbar
import com.igreenwood.loupe.Loupe import com.igreenwood.loupe.Loupe
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.database.ContextualDatabase
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.tasks.BinaryDatabaseManager import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import kotlin.math.max import kotlin.math.max
class ImageViewerActivity : DatabaseLockActivity() { class ImageViewerActivity : DatabaseLockActivity() {
@@ -100,12 +101,12 @@ class ImageViewerActivity : DatabaseLockActivity() {
return true return true
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
try { try {
progressView.visibility = View.VISIBLE progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment -> intent.getParcelableExtraCompat<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
supportActionBar?.title = attachment.name supportActionBar?.title = attachment.name

View File

@@ -96,7 +96,7 @@ class KeyGeneratorActivity : DatabaseLockActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
onBackPressed() onDatabaseBackPressed()
} }
R.id.menu_generate -> { R.id.menu_generate -> {
keyGeneratorViewModel.requireKeyGeneration() keyGeneratorViewModel.requireKeyGeneration()
@@ -106,9 +106,9 @@ class KeyGeneratorActivity : DatabaseLockActivity() {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun onBackPressed() { override fun onDatabaseBackPressed() {
setResult(Activity.RESULT_CANCELED, Intent()) setResult(Activity.RESULT_CANCELED, Intent())
super.onBackPressed() super.onDatabaseBackPressed()
} }
companion object { companion object {

View File

@@ -1,6 +1,6 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
* KeePassDX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
@@ -55,24 +55,30 @@ import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.* import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_DATABASE_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_DATABASE_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
import com.kunzisoft.keepass.settings.AdvancedUnlockSettingsActivity
import com.kunzisoft.keepass.settings.AppearanceSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getUri
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.view.MainCredentialView import com.kunzisoft.keepass.view.MainCredentialView
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import java.io.FileNotFoundException import java.io.FileNotFoundException
@@ -83,6 +89,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
// Views // Views
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
private var filenameView: TextView? = null private var filenameView: TextView? = null
private var logotypeButton: View? = null
private var advancedUnlockButton: View? = null private var advancedUnlockButton: View? = null
private var mainCredentialView: MainCredentialView? = null private var mainCredentialView: MainCredentialView? = null
private var confirmButtonView: Button? = null private var confirmButtonView: Button? = null
@@ -101,6 +108,8 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
private var mRememberKeyFile: Boolean = false private var mRememberKeyFile: Boolean = false
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mRememberHardwareKey: Boolean = false
private var mReadOnly: Boolean = false private var mReadOnly: Boolean = false
private var mForceReadOnly: Boolean = false private var mForceReadOnly: Boolean = false
@@ -121,7 +130,8 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
filenameView = findViewById(R.id.filename) filenameView = findViewById(R.id.filename)
advancedUnlockButton = findViewById(R.id.activity_password_advanced_unlock_button) logotypeButton = findViewById(R.id.activity_password_logotype)
advancedUnlockButton = findViewById(R.id.fragment_advanced_unlock_container_view)
mainCredentialView = findViewById(R.id.activity_password_credentials) mainCredentialView = findViewById(R.id.activity_password_credentials)
confirmButtonView = findViewById(R.id.activity_password_open_button) confirmButtonView = findViewById(R.id.activity_password_open_button)
infoContainerView = findViewById(R.id.activity_password_info_container) infoContainerView = findViewById(R.id.activity_password_info_container)
@@ -133,11 +143,13 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
PreferencesUtil.enableReadOnlyDatabase(this) PreferencesUtil.enableReadOnlyDatabase(this)
} }
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this) mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mRememberHardwareKey = PreferencesUtil.rememberHardwareKey(this)
mExternalFileHelper = ExternalFileHelper(this@MainCredentialActivity) // Build elements to manage keyfile selection
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri -> mExternalFileHelper?.buildOpenDocument { uri ->
if (uri != null) { if (uri != null) {
mainCredentialView?.populateKeyFileTextView(uri) mainCredentialView?.populateKeyFileView(uri)
} }
} }
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper) mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
@@ -148,12 +160,12 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
// If is a view intent // If is a view intent
getUriFromIntent(intent) getUriFromIntent(intent)
// Init Biometric elements // Show appearance
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { logotypeButton?.setOnClickListener {
advancedUnlockButton?.setOnClickListener { startActivity(Intent(this, AppearanceSettingsActivity::class.java))
startActivity(Intent(this, SettingsAdvancedUnlockActivity::class.java))
}
} }
// Init Biometric elements
advancedUnlockFragment = supportFragmentManager advancedUnlockFragment = supportFragmentManager
.findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment? .findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment?
if (advancedUnlockFragment == null) { if (advancedUnlockFragment == null) {
@@ -171,6 +183,16 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
mAdvancedUnlockViewModel.checkUnlockAvailability() mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton() enableConfirmationButton()
} }
mainCredentialView?.onKeyFileChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
mainCredentialView?.onHardwareKeyChecked =
CompoundButton.OnCheckedChangeListener { _, _ ->
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
enableConfirmationButton()
}
// Observe if default database // Observe if default database
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase -> mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
@@ -204,10 +226,19 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
databaseKeyFileUri databaseKeyFileUri
} }
val databaseHardwareKey = mainCredentialView?.getMainCredential()?.hardwareKey
val hardwareKey =
if (mRememberHardwareKey
&& databaseHardwareKey == null) {
databaseFile?.hardwareKey
} else {
databaseHardwareKey
}
// Define title // Define title
filenameView?.text = databaseFile?.databaseAlias ?: "" filenameView?.text = databaseFile?.databaseAlias ?: ""
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri) onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri, hardwareKey)
} }
} }
@@ -215,6 +246,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
super.onResume() super.onResume()
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@MainCredentialActivity) mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@MainCredentialActivity)
mRememberHardwareKey = PreferencesUtil.rememberHardwareKey(this@MainCredentialActivity)
// Back to previous keyboard is setting activated // Back to previous keyboard is setting activated
if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@MainCredentialActivity)) { if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@MainCredentialActivity)) {
@@ -235,7 +267,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
} }
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
if (database != null) { if (database != null) {
// Trying to load another database // Trying to load another database
@@ -252,7 +284,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {
@@ -266,99 +298,93 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
launchGroupActivityIfLoaded(database) launchGroupActivityIfLoaded(database)
} else { } else {
mainCredentialView?.requestPasswordFocus() mainCredentialView?.requestPasswordFocus()
// Manage special exceptions
when (result.exception) {
is DuplicateUuidDatabaseException -> {
// Relaunch loading if we need to fix UUID
showLoadDatabaseDuplicateUuidMessage {
var resultError = "" var databaseUri: Uri? = null
val resultException = result.exception var mainCredential = MainCredential()
val resultMessage = result.message var readOnly = true
var cipherEncryptDatabase: CipherEncryptDatabase? = null
if (resultException != null) { result.data?.let { resultData ->
resultError = resultException.getLocalizedMessage(resources) databaseUri = resultData.getParcelableCompat(DATABASE_URI_KEY)
mainCredential =
when (resultException) { resultData.getParcelableCompat(MAIN_CREDENTIAL_KEY)
is DuplicateUuidDatabaseException -> { ?: mainCredential
// Relaunch loading if we need to fix UUID readOnly = resultData.getBoolean(READ_ONLY_KEY)
showLoadDatabaseDuplicateUuidMessage { cipherEncryptDatabase =
resultData.getParcelableCompat(CIPHER_DATABASE_KEY)
var databaseUri: Uri? = null
var mainCredential = MainCredential()
var readOnly = true
var cipherEncryptDatabase: CipherEncryptDatabase? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
mainCredential =
resultData.getParcelable(MAIN_CREDENTIAL_KEY)
?: mainCredential
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEncryptDatabase =
resultData.getParcelable(CIPHER_DATABASE_KEY)
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
mainCredential,
readOnly,
cipherEncryptDatabase,
true
)
}
} }
}
is FileNotFoundDatabaseException -> { databaseUri?.let { databaseFileUri ->
// Remove this default database inaccessible showProgressDialogAndLoadDatabase(
if (mDefaultDatabase) { databaseFileUri,
mDatabaseFileViewModel.removeDefaultDatabase() mainCredential,
readOnly,
cipherEncryptDatabase,
true
)
} }
} }
} }
is FileNotFoundDatabaseException -> {
// Remove this default database inaccessible
if (mDefaultDatabase) {
mDatabaseFileViewModel.removeDefaultDatabase()
}
}
} }
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError)
Snackbar.make(
coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG
).asError().show()
} }
} }
} }
coordinatorLayout.showActionErrorIfNeeded(result)
} }
private fun getUriFromIntent(intent: Intent?) { private fun getUriFromIntent(intent: Intent?) {
// If is a view intent // If is a view intent
val action = intent?.action val action = intent?.action
if (action != null if (action == VIEW_INTENT) {
&& action == VIEW_INTENT) { fillCredentials(
mDatabaseFileUri = intent.data intent.data,
mainCredentialView?.populateKeyFileTextView(UriUtil.getUriFromIntent(intent, KEY_KEYFILE)) intent.getUri(KEY_KEYFILE),
HardwareKey.getHardwareKeyFromString(intent.getStringExtra(KEY_HARDWARE_KEY))
)
} else { } else {
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME) fillCredentials(
intent?.getParcelableExtra<Uri?>(KEY_KEYFILE)?.let { intent?.getParcelableExtraCompat(KEY_FILENAME),
mainCredentialView?.populateKeyFileTextView(it) intent?.getParcelableExtraCompat(KEY_KEYFILE),
} HardwareKey.getHardwareKeyFromString(intent?.getStringExtra(KEY_HARDWARE_KEY))
)
} }
try { try {
intent?.removeExtra(KEY_KEYFILE) intent?.removeExtra(KEY_KEYFILE)
} catch (e: Exception) {} intent?.removeExtra(KEY_HARDWARE_KEY)
} catch (_: Exception) {}
mDatabaseFileUri?.let { mDatabaseFileUri?.let {
mDatabaseFileViewModel.checkIfIsDefaultDatabase(it) mDatabaseFileViewModel.checkIfIsDefaultDatabase(it)
} }
} }
private fun fillCredentials(databaseUri: Uri?,
keyFileUri: Uri?,
hardwareKey: HardwareKey?) {
mDatabaseFileUri = databaseUri
mainCredentialView?.populateKeyFileView(keyFileUri)
mainCredentialView?.populateHardwareKeyView(hardwareKey)
}
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
getUriFromIntent(intent) getUriFromIntent(intent)
} }
private fun launchGroupActivityIfLoaded(database: Database) { private fun launchGroupActivityIfLoaded(database: ContextualDatabase) {
// Check if database really loaded // Check if database really loaded
if (database.loaded) { if (database.loaded) {
clearCredentialsViews(true) clearCredentialsViews(clearKeyFile = true, clearHardwareKey = true)
GroupActivity.launch(this, GroupActivity.launch(this,
database, database,
{ onValidateSpecialMode() }, { onValidateSpecialMode() },
@@ -369,16 +395,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
} }
} }
override fun onValidateSpecialMode() {
super.onValidateSpecialMode()
finish()
}
override fun onCancelSpecialMode() {
super.onCancelSpecialMode()
finish()
}
override fun retrieveCredentialForEncryption(): ByteArray { override fun retrieveCredentialForEncryption(): ByteArray {
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener) return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
?: byteArrayOf() ?: byteArrayOf()
@@ -418,7 +434,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential() val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()
when (cipherDecryptDatabase.credentialStorage) { when (cipherDecryptDatabase.credentialStorage) {
CredentialStorage.PASSWORD -> { CredentialStorage.PASSWORD -> {
mainCredential.masterPassword = String(cipherDecryptDatabase.decryptedValue) mainCredential.password = String(cipherDecryptDatabase.decryptedValue)
} }
CredentialStorage.KEY_FILE -> { CredentialStorage.KEY_FILE -> {
// TODO advanced unlock key file // TODO advanced unlock key file
@@ -433,14 +449,23 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
) )
} }
private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) { private fun onDatabaseFileLoaded(databaseFileUri: Uri?,
keyFileUri: Uri?,
hardwareKey: HardwareKey?) {
// Define Key File text // Define Key File text
if (mRememberKeyFile) { if (mRememberKeyFile) {
mainCredentialView?.populateKeyFileTextView(keyFileUri) mainCredentialView?.populateKeyFileView(keyFileUri)
}
// Define hardware key
if (mRememberHardwareKey) {
mainCredentialView?.populateHardwareKeyView(hardwareKey)
} }
// Define listener for validate button // Define listener for validate button
confirmButtonView?.setOnClickListener { loadDatabase() } confirmButtonView?.setOnClickListener {
mainCredentialView?.validateCredential()
}
// If Activity is launch with a password and want to open directly // If Activity is launch with a password and want to open directly
val intent = intent val intent = intent
@@ -472,10 +497,14 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
} }
} }
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) { private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile,
clearHardwareKey: Boolean = !mRememberHardwareKey) {
mainCredentialView?.populatePasswordTextView(null) mainCredentialView?.populatePasswordTextView(null)
if (clearKeyFile) { if (clearKeyFile) {
mainCredentialView?.populateKeyFileTextView(null) mainCredentialView?.populateKeyFileView(null)
}
if (clearHardwareKey) {
mainCredentialView?.populateHardwareKeyView(null)
} }
} }
@@ -621,7 +650,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
startActivity( startActivity(
Intent( Intent(
this, this,
SettingsAdvancedUnlockActivity::class.java AdvancedUnlockSettingsActivity::class.java
) )
) )
}, },
@@ -666,18 +695,24 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
private const val KEY_FILENAME = "fileName" private const val KEY_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile" private const val KEY_KEYFILE = "keyFile"
private const val KEY_HARDWARE_KEY = "hardwareKey"
private const val VIEW_INTENT = "android.intent.action.VIEW" private const val VIEW_INTENT = "android.intent.action.VIEW"
private const val KEY_READ_ONLY = "KEY_READ_ONLY" private const val KEY_READ_ONLY = "KEY_READ_ONLY"
private const val KEY_PASSWORD = "password" private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately" private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?, private fun buildAndLaunchIntent(activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
hardwareKey: HardwareKey?,
intentBuildLauncher: (Intent) -> Unit) { intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, MainCredentialActivity::class.java) val intent = Intent(activity, MainCredentialActivity::class.java)
intent.putExtra(KEY_FILENAME, databaseFile) intent.putExtra(KEY_FILENAME, databaseFile)
if (keyFile != null) if (keyFile != null)
intent.putExtra(KEY_KEYFILE, keyFile) intent.putExtra(KEY_KEYFILE, keyFile)
if (hardwareKey != null)
intent.putExtra(KEY_HARDWARE_KEY, hardwareKey.toString())
intentBuildLauncher.invoke(intent) intentBuildLauncher.invoke(intent)
} }
@@ -690,8 +725,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class)
fun launch(activity: Activity, fun launch(activity: Activity,
databaseFile: Uri, databaseFile: Uri,
keyFile: Uri?) { keyFile: Uri?,
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> hardwareKey: HardwareKey?) {
buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
activity.startActivity(intent) activity.startActivity(intent)
} }
} }
@@ -706,8 +742,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForSearchResult(activity: Activity, fun launchForSearchResult(activity: Activity,
databaseFile: Uri, databaseFile: Uri,
keyFile: Uri?, keyFile: Uri?,
hardwareKey: HardwareKey?,
searchInfo: SearchInfo) { searchInfo: SearchInfo) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
EntrySelectionHelper.startActivityForSearchModeResult( EntrySelectionHelper.startActivityForSearchModeResult(
activity, activity,
intent, intent,
@@ -725,8 +762,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForSaveResult(activity: Activity, fun launchForSaveResult(activity: Activity,
databaseFile: Uri, databaseFile: Uri,
keyFile: Uri?, keyFile: Uri?,
hardwareKey: HardwareKey?,
searchInfo: SearchInfo) { searchInfo: SearchInfo) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
EntrySelectionHelper.startActivityForSaveModeResult( EntrySelectionHelper.startActivityForSaveModeResult(
activity, activity,
intent, intent,
@@ -744,8 +782,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForKeyboardResult(activity: Activity, fun launchForKeyboardResult(activity: Activity,
databaseFile: Uri, databaseFile: Uri,
keyFile: Uri?, keyFile: Uri?,
hardwareKey: HardwareKey?,
searchInfo: SearchInfo?) { searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
EntrySelectionHelper.startActivityForKeyboardSelectionModeResult( EntrySelectionHelper.startActivityForKeyboardSelectionModeResult(
activity, activity,
intent, intent,
@@ -764,10 +803,11 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForAutofillResult(activity: AppCompatActivity, fun launchForAutofillResult(activity: AppCompatActivity,
databaseFile: Uri, databaseFile: Uri,
keyFile: Uri?, keyFile: Uri?,
hardwareKey: HardwareKey?,
activityResultLauncher: ActivityResultLauncher<Intent>?, activityResultLauncher: ActivityResultLauncher<Intent>?,
autofillComponent: AutofillComponent, autofillComponent: AutofillComponent,
searchInfo: SearchInfo?) { searchInfo: SearchInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
AutofillHelper.startActivityForAutofillResult( AutofillHelper.startActivityForAutofillResult(
activity, activity,
intent, intent,
@@ -785,8 +825,9 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launchForRegistration(activity: Activity, fun launchForRegistration(activity: Activity,
databaseFile: Uri, databaseFile: Uri,
keyFile: Uri?, keyFile: Uri?,
hardwareKey: HardwareKey?,
registerInfo: RegisterInfo?) { registerInfo: RegisterInfo?) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent -> buildAndLaunchIntent(activity, databaseFile, keyFile, hardwareKey) { intent ->
EntrySelectionHelper.startActivityForRegistrationModeResult( EntrySelectionHelper.startActivityForRegistrationModeResult(
activity, activity,
intent, intent,
@@ -802,6 +843,7 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
fun launch(activity: AppCompatActivity, fun launch(activity: AppCompatActivity,
databaseUri: Uri, databaseUri: Uri,
keyFile: Uri?, keyFile: Uri?,
hardwareKey: HardwareKey?,
fileNoFoundAction: (exception: FileNotFoundException) -> Unit, fileNoFoundAction: (exception: FileNotFoundException) -> Unit,
onCancelSpecialMode: () -> Unit, onCancelSpecialMode: () -> Unit,
onLaunchActivitySpecialMode: () -> Unit, onLaunchActivitySpecialMode: () -> Unit,
@@ -810,43 +852,67 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
try { try {
EntrySelectionHelper.doSpecialAction(activity.intent, EntrySelectionHelper.doSpecialAction(activity.intent,
{ {
MainCredentialActivity.launch(activity, launch(
databaseUri, keyFile) activity,
databaseUri,
keyFile,
hardwareKey
)
}, },
{ searchInfo -> // Search Action { searchInfo -> // Search Action
MainCredentialActivity.launchForSearchResult(activity, launchForSearchResult(
databaseUri, keyFile, activity,
searchInfo) databaseUri,
keyFile,
hardwareKey,
searchInfo
)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
}, },
{ searchInfo -> // Save Action { searchInfo -> // Save Action
MainCredentialActivity.launchForSaveResult(activity, launchForSaveResult(
databaseUri, keyFile, activity,
searchInfo) databaseUri,
keyFile,
hardwareKey,
searchInfo
)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
}, },
{ searchInfo -> // Keyboard Selection Action { searchInfo -> // Keyboard Selection Action
MainCredentialActivity.launchForKeyboardResult(activity, launchForKeyboardResult(
databaseUri, keyFile, activity,
searchInfo) databaseUri,
keyFile,
hardwareKey,
searchInfo
)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
}, },
{ searchInfo, autofillComponent -> // Autofill Selection Action { searchInfo, autofillComponent -> // Autofill Selection Action
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
MainCredentialActivity.launchForAutofillResult(activity, launchForAutofillResult(
databaseUri, keyFile, activity,
autofillActivityResultLauncher, databaseUri,
autofillComponent, keyFile,
searchInfo) hardwareKey,
autofillActivityResultLauncher,
autofillComponent,
searchInfo
)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
} else { } else {
onCancelSpecialMode() onCancelSpecialMode()
} }
}, },
{ registerInfo -> // Registration Action { registerInfo -> // Registration Action
MainCredentialActivity.launchForRegistration(activity, launchForRegistration(
databaseUri, keyFile, activity,
registerInfo) databaseUri,
keyFile,
hardwareKey,
registerInfo
)
onLaunchActivitySpecialMode() onLaunchActivitySpecialMode()
} }
) )

View File

@@ -25,6 +25,7 @@ import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.utils.getParcelableCompat
class DatabaseChangedDialogFragment : DatabaseDialogFragment() { class DatabaseChangedDialogFragment : DatabaseDialogFragment() {
@@ -40,8 +41,8 @@ class DatabaseChangedDialogFragment : DatabaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
val oldSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelable(OLD_FILE_DATABASE_INFO) val oldSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelableCompat(OLD_FILE_DATABASE_INFO)
val newSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelable(NEW_FILE_DATABASE_INFO) val newSnapFileDatabaseInfo: SnapFileDatabaseInfo? = arguments?.getParcelableCompat(NEW_FILE_DATABASE_INFO)
if (oldSnapFileDatabaseInfo != null && newSnapFileDatabaseInfo != null) { if (oldSnapFileDatabaseInfo != null && newSnapFileDatabaseInfo != null) {
// Use the Builder class for convenient dialog construction // Use the Builder class for convenient dialog construction
@@ -78,7 +79,8 @@ class DatabaseChangedDialogFragment : DatabaseDialogFragment() {
private const val NEW_FILE_DATABASE_INFO = "NEW_FILE_DATABASE_INFO" private const val NEW_FILE_DATABASE_INFO = "NEW_FILE_DATABASE_INFO"
fun getInstance(oldSnapFileDatabaseInfo: SnapFileDatabaseInfo, fun getInstance(oldSnapFileDatabaseInfo: SnapFileDatabaseInfo,
newSnapFileDatabaseInfo: SnapFileDatabaseInfo) newSnapFileDatabaseInfo: SnapFileDatabaseInfo
)
: DatabaseChangedDialogFragment { : DatabaseChangedDialogFragment {
val fragment = DatabaseChangedDialogFragment() val fragment = DatabaseChangedDialogFragment()
fragment.arguments = Bundle().apply { fragment.arguments = Bundle().apply {

View File

@@ -5,7 +5,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
@@ -13,7 +13,7 @@ import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval { abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels() private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
private var mDatabase: Database? = null private var mDatabase: ContextualDatabase? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -36,12 +36,12 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
resetAppTimeoutOnTouchOrFocus() resetAppTimeoutOnTouchOrFocus()
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
// Can be overridden by a subclass // Can be overridden by a subclass
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {

View File

@@ -1,67 +0,0 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.DatePickerDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.DialogFragment
// Not as DatabaseDialogFragment because crash on KitKat
class DatePickerFragment : DialogFragment() {
private var mDefaultYear: Int = 2000
private var mDefaultMonth: Int = 1
private var mDefaultDay: Int = 1
private var mListener: DatePickerDialog.OnDateSetListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as DatePickerDialog.OnDateSetListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DatePickerDialog.OnDateSetListener::class.java.name)
}
}
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Create a new instance of DatePickerDialog and return it
return context?.let {
arguments?.apply {
if (containsKey(DEFAULT_YEAR_BUNDLE_KEY))
mDefaultYear = getInt(DEFAULT_YEAR_BUNDLE_KEY)
if (containsKey(DEFAULT_MONTH_BUNDLE_KEY))
mDefaultMonth = getInt(DEFAULT_MONTH_BUNDLE_KEY)
if (containsKey(DEFAULT_DAY_BUNDLE_KEY))
mDefaultDay = getInt(DEFAULT_DAY_BUNDLE_KEY)
}
DatePickerDialog(it, mListener, mDefaultYear, mDefaultMonth, mDefaultDay)
} ?: super.onCreateDialog(savedInstanceState)
}
companion object {
private const val DEFAULT_YEAR_BUNDLE_KEY = "DEFAULT_YEAR_BUNDLE_KEY"
private const val DEFAULT_MONTH_BUNDLE_KEY = "DEFAULT_MONTH_BUNDLE_KEY"
private const val DEFAULT_DAY_BUNDLE_KEY = "DEFAULT_DAY_BUNDLE_KEY"
fun getInstance(defaultYear: Int,
defaultMonth: Int,
defaultDay: Int): DatePickerFragment {
return DatePickerFragment().apply {
arguments = Bundle().apply {
putInt(DEFAULT_YEAR_BUNDLE_KEY, defaultYear)
putInt(DEFAULT_MONTH_BUNDLE_KEY, defaultMonth)
putInt(DEFAULT_DAY_BUNDLE_KEY, defaultDay)
}
}
}
}
}

View File

@@ -35,6 +35,7 @@ import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Field import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.getParcelableCompat
class EntryCustomFieldDialogFragment: DatabaseDialogFragment() { class EntryCustomFieldDialogFragment: DatabaseDialogFragment() {
@@ -72,7 +73,7 @@ class EntryCustomFieldDialogFragment: DatabaseDialogFragment() {
customFieldDeleteButton = root?.findViewById(R.id.entry_custom_field_delete) customFieldDeleteButton = root?.findViewById(R.id.entry_custom_field_delete)
customFieldProtectionButton = root?.findViewById(R.id.entry_custom_field_protection) customFieldProtectionButton = root?.findViewById(R.id.entry_custom_field_protection)
oldField = arguments?.getParcelable(KEY_FIELD) oldField = arguments?.getParcelableCompat(KEY_FIELD)
oldField?.let { oldCustomField -> oldField?.let { oldCustomField ->
customFieldLabel?.text = oldCustomField.name customFieldLabel?.text = oldCustomField.name
customFieldProtectionButton?.isChecked = oldCustomField.protectedValue.isProtected customFieldProtectionButton?.isChecked = oldCustomField.protectedValue.isProtected

View File

@@ -21,12 +21,12 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openUrl
class FileManagerDialogFragment : DialogFragment() { class FileManagerDialogFragment : DialogFragment() {
@@ -42,7 +42,7 @@ class FileManagerDialogFragment : DialogFragment() {
textDescription.text = getString(R.string.file_manager_install_description) textDescription.text = getString(R.string.file_manager_install_description)
root.findViewById<Button>(R.id.file_manager_button).setOnClickListener { root.findViewById<Button>(R.id.file_manager_button).setOnClickListener {
UriUtil.gotoUrl(requireContext(), R.string.file_manager_explanation_url) context?.openUrl(R.string.file_manager_explanation_url)
dismiss() dismiss()
} }

View File

@@ -27,6 +27,7 @@ import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.getParcelableCompat
/** /**
* Custom Dialog to confirm big file to upload * Custom Dialog to confirm big file to upload
@@ -62,7 +63,7 @@ class FileTooBigDialogFragment : DialogFragment() {
}) })
builder.setPositiveButton(android.R.string.ok) { _, _ -> builder.setPositiveButton(android.R.string.ok) { _, _ ->
mActionChooseListener?.onValidateUploadFileTooBig( mActionChooseListener?.onValidateUploadFileTooBig(
arguments?.getParcelable(KEY_FILE_URI), arguments?.getParcelableCompat(KEY_FILE_URI),
arguments?.getString(KEY_FILE_NAME)) arguments?.getString(KEY_FILE_NAME))
} }
builder.setNegativeButton(android.R.string.cancel) { _, _ -> builder.setNegativeButton(android.R.string.cancel) { _, _ ->

View File

@@ -31,11 +31,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.TagsAdapter import com.kunzisoft.keepass.adapters.TagsAdapter
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.model.GroupInfo import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString
import com.kunzisoft.keepass.utils.UuidUtil import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.view.DateTimeFieldView import com.kunzisoft.keepass.view.DateTimeFieldView
class GroupDialogFragment : DatabaseDialogFragment() { class GroupDialogFragment : DatabaseDialogFragment() {
@@ -60,7 +62,7 @@ class GroupDialogFragment : DatabaseDialogFragment() {
private lateinit var uuidContainerView: ViewGroup private lateinit var uuidContainerView: ViewGroup
private lateinit var uuidReferenceView: TextView private lateinit var uuidReferenceView: TextView
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
mPopulateIconMethod = { imageView, icon -> mPopulateIconMethod = { imageView, icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor) database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
@@ -106,17 +108,17 @@ class GroupDialogFragment : DatabaseDialogFragment() {
uuidReferenceView = root.findViewById(R.id.group_UUID_reference) uuidReferenceView = root.findViewById(R.id.group_UUID_reference)
// Retrieve the textColor to tint the icon // Retrieve the textColor to tint the icon
val ta = activity.theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent)) val ta = activity.theme.obtainStyledAttributes(intArrayOf(R.attr.colorSecondary))
mIconColor = ta.getColor(0, Color.WHITE) mIconColor = ta.getColor(0, Color.WHITE)
ta.recycle() ta.recycle()
if (savedInstanceState != null if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_GROUP_INFO)) { && savedInstanceState.containsKey(KEY_GROUP_INFO)) {
mGroupInfo = savedInstanceState.getParcelable(KEY_GROUP_INFO) ?: mGroupInfo mGroupInfo = savedInstanceState.getParcelableCompat(KEY_GROUP_INFO) ?: mGroupInfo
} else { } else {
arguments?.apply { arguments?.apply {
if (containsKey(KEY_GROUP_INFO)) { if (containsKey(KEY_GROUP_INFO)) {
mGroupInfo = getParcelable(KEY_GROUP_INFO) ?: mGroupInfo mGroupInfo = getParcelableCompat(KEY_GROUP_INFO) ?: mGroupInfo
} }
} }
} }

View File

@@ -24,17 +24,22 @@ import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
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.dialogs.GroupEditDialogFragment.EditGroupDialogAction.* import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.NONE
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
import com.kunzisoft.keepass.adapters.TagsProposalAdapter import com.kunzisoft.keepass.adapters.TagsProposalAdapter
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.model.GroupInfo import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.view.DateTimeEditFieldView import com.kunzisoft.keepass.view.DateTimeEditFieldView
import com.kunzisoft.keepass.view.InheritedCompletionView import com.kunzisoft.keepass.view.InheritedCompletionView
import com.kunzisoft.keepass.view.TagsCompletionView import com.kunzisoft.keepass.view.TagsCompletionView
@@ -85,13 +90,11 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon) mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon)
} }
mGroupEditViewModel.onDateSelected.observe(this) { viewModelDate -> mGroupEditViewModel.onDateSelected.observe(this) { dateMilliseconds ->
// Save the date // Save the date
mGroupInfo.expiryTime = DateInstant( mGroupInfo.expiryTime = DateInstant(
DateTime(mGroupInfo.expiryTime.date) DateTime(mGroupInfo.expiryTime.date)
.withYear(viewModelDate.year) .withMillis(dateMilliseconds)
.withMonthOfYear(viewModelDate.month + 1)
.withDayOfMonth(viewModelDate.day)
.toDate()) .toDate())
expirationView.dateTime = mGroupInfo.expiryTime expirationView.dateTime = mGroupInfo.expiryTime
if (expirationView.dateTime.type == DateInstant.Type.DATE_TIME) { if (expirationView.dateTime.type == DateInstant.Type.DATE_TIME) {
@@ -116,7 +119,7 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
mPopulateIconMethod = { imageView, icon -> mPopulateIconMethod = { imageView, icon ->
@@ -170,13 +173,13 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
&& savedInstanceState.containsKey(KEY_ACTION_ID) && savedInstanceState.containsKey(KEY_ACTION_ID)
&& savedInstanceState.containsKey(KEY_GROUP_INFO)) { && savedInstanceState.containsKey(KEY_GROUP_INFO)) {
mEditGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID)) mEditGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID))
mGroupInfo = savedInstanceState.getParcelable(KEY_GROUP_INFO) ?: mGroupInfo mGroupInfo = savedInstanceState.getParcelableCompat(KEY_GROUP_INFO) ?: mGroupInfo
} else { } else {
arguments?.apply { arguments?.apply {
if (containsKey(KEY_ACTION_ID)) if (containsKey(KEY_ACTION_ID))
mEditGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(getInt(KEY_ACTION_ID)) mEditGroupDialogAction = EditGroupDialogAction.getActionFromOrdinal(getInt(KEY_ACTION_ID))
if (containsKey(KEY_GROUP_INFO)) { if (containsKey(KEY_GROUP_INFO)) {
mGroupInfo = getParcelable(KEY_GROUP_INFO) ?: mGroupInfo mGroupInfo = getParcelableCompat(KEY_GROUP_INFO) ?: mGroupInfo
} }
} }
} }

View File

@@ -27,10 +27,11 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
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.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
class IconEditDialogFragment : DatabaseDialogFragment() { class IconEditDialogFragment : DatabaseDialogFragment() {
@@ -44,7 +45,7 @@ class IconEditDialogFragment : DatabaseDialogFragment() {
private var mCustomIcon: IconImageCustom? = null private var mCustomIcon: IconImageCustom? = null
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
mPopulateIconMethod = { imageView, icon -> mPopulateIconMethod = { imageView, icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon) database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon)
@@ -63,11 +64,11 @@ class IconEditDialogFragment : DatabaseDialogFragment() {
if (savedInstanceState != null if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_CUSTOM_ICON_ID)) { && savedInstanceState.containsKey(KEY_CUSTOM_ICON_ID)) {
mCustomIcon = savedInstanceState.getParcelable(KEY_CUSTOM_ICON_ID) ?: mCustomIcon mCustomIcon = savedInstanceState.getParcelableCompat(KEY_CUSTOM_ICON_ID) ?: mCustomIcon
} else { } else {
arguments?.apply { arguments?.apply {
if (containsKey(KEY_CUSTOM_ICON_ID)) { if (containsKey(KEY_CUSTOM_ICON_ID)) {
mCustomIcon = getParcelable(KEY_CUSTOM_ICON_ID) ?: mCustomIcon mCustomIcon = getParcelableCompat(KEY_CUSTOM_ICON_ID) ?: mCustomIcon
} }
} }
} }

View File

@@ -27,8 +27,9 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.view.MainCredentialView import com.kunzisoft.keepass.view.MainCredentialView
class MainCredentialDialogFragment : DatabaseDialogFragment() { class MainCredentialDialogFragment : DatabaseDialogFragment() {
@@ -65,7 +66,7 @@ class MainCredentialDialogFragment : DatabaseDialogFragment() {
var databaseUri: Uri? = null var databaseUri: Uri? = null
arguments?.apply { arguments?.apply {
if (containsKey(KEY_ASK_CREDENTIAL_URI)) if (containsKey(KEY_ASK_CREDENTIAL_URI))
databaseUri = getParcelable(KEY_ASK_CREDENTIAL_URI) databaseUri = getParcelableCompat(KEY_ASK_CREDENTIAL_URI)
} }
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
@@ -74,7 +75,7 @@ class MainCredentialDialogFragment : DatabaseDialogFragment() {
mainCredentialView = root.findViewById(R.id.main_credential_view) mainCredentialView = root.findViewById(R.id.main_credential_view)
databaseUri?.let { databaseUri?.let {
root.findViewById<TextView>(R.id.title_database)?.text = root.findViewById<TextView>(R.id.title_database)?.text =
UriUtil.getFileData(requireContext(), it)?.name it.getDocumentFile(requireContext())?.name
} }
builder.setView(root) builder.setView(root)
// Add action buttons // Add action buttons
@@ -95,7 +96,7 @@ class MainCredentialDialogFragment : DatabaseDialogFragment() {
mExternalFileHelper = ExternalFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri -> mExternalFileHelper?.buildOpenDocument { uri ->
if (uri != null) { if (uri != null) {
mainCredentialView?.populateKeyFileTextView(uri) mainCredentialView?.populateKeyFileView(uri)
} }
} }
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper) mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)

View File

@@ -26,7 +26,8 @@ import android.os.Bundle
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.utils.getParcelableCompat
class PasswordEncodingDialogFragment : DialogFragment() { class PasswordEncodingDialogFragment : DialogFragment() {
@@ -49,8 +50,8 @@ class PasswordEncodingDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val databaseUri: Uri? = savedInstanceState?.getParcelable(DATABASE_URI_KEY) val databaseUri: Uri? = savedInstanceState?.getParcelableCompat(DATABASE_URI_KEY)
val mainCredential: MainCredential = savedInstanceState?.getParcelable(MAIN_CREDENTIAL) ?: MainCredential() val mainCredential: MainCredential = savedInstanceState?.getParcelableCompat(MAIN_CREDENTIAL) ?: MainCredential()
activity?.let { activity -> activity?.let { activity ->
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
@@ -78,8 +79,10 @@ class PasswordEncodingDialogFragment : DialogFragment() {
private const val DATABASE_URI_KEY = "DATABASE_URI_KEY" private const val DATABASE_URI_KEY = "DATABASE_URI_KEY"
private const val MAIN_CREDENTIAL = "MAIN_CREDENTIAL" private const val MAIN_CREDENTIAL = "MAIN_CREDENTIAL"
fun getInstance(databaseUri: Uri, fun getInstance(
mainCredential: MainCredential): SortDialogFragment { databaseUri: Uri,
mainCredential: MainCredential
): SortDialogFragment {
val fragment = SortDialogFragment() val fragment = SortDialogFragment()
fragment.arguments = Bundle().apply { fragment.arguments = Bundle().apply {
putParcelable(DATABASE_URI_KEY, databaseUri) putParcelable(DATABASE_URI_KEY, databaseUri)

View File

@@ -28,7 +28,7 @@ import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openUrl
/** /**
* Custom Dialog that asks the user to download the pro version or make a donation. * Custom Dialog that asks the user to download the pro version or make a donation.
@@ -45,13 +45,16 @@ class ProFeatureDialogFragment : DialogFragment() {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY)) stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.download) { _, _ -> builder.setPositiveButton(R.string.download) { _, _ ->
UriUtil.gotoUrl(requireContext(), R.string.app_pro_url) activity.openUrl(
activity.getString(R.string.play_store_url,
activity.getString(R.string.keepro_app_id))
)
} }
} else { } else {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY)) stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ -> builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(requireContext(), R.string.contribution_url) activity.openUrl(R.string.contribution_url)
} }
} }
builder.setMessage(stringBuilder) builder.setMessage(stringBuilder)

View File

@@ -27,6 +27,7 @@ import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
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.utils.getParcelableCompat
/** /**
* Custom Dialog to confirm big file to upload * Custom Dialog to confirm big file to upload
@@ -62,8 +63,8 @@ class ReplaceFileDialogFragment : DatabaseDialogFragment() {
}) })
builder.setPositiveButton(android.R.string.ok) { _, _ -> builder.setPositiveButton(android.R.string.ok) { _, _ ->
mActionChooseListener?.onValidateReplaceFile( mActionChooseListener?.onValidateReplaceFile(
arguments?.getParcelable(KEY_FILE_URI), arguments?.getParcelableCompat(KEY_FILE_URI),
arguments?.getParcelable(KEY_ENTRY_ATTACHMENT)) arguments?.getParcelableCompat(KEY_ENTRY_ATTACHMENT))
} }
builder.setNegativeButton(android.R.string.cancel) { _, _ -> builder.setNegativeButton(android.R.string.cancel) { _, _ ->
dismiss() dismiss()

View File

@@ -35,9 +35,13 @@ import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.hardware.HardwareKeyActivity
import com.kunzisoft.keepass.password.PasswordEntropy import com.kunzisoft.keepass.password.PasswordEntropy
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.view.HardwareKeySelectionView
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.PassKeyView import com.kunzisoft.keepass.view.PassKeyView
import com.kunzisoft.keepass.view.applyFontVisibility import com.kunzisoft.keepass.view.applyFontVisibility
@@ -45,18 +49,21 @@ import com.kunzisoft.keepass.view.applyFontVisibility
class SetMainCredentialDialogFragment : DatabaseDialogFragment() { class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
private var mMasterPassword: String? = null private var mMasterPassword: String? = null
private var mKeyFile: Uri? = null private var mKeyFileUri: Uri? = null
private var mHardwareKey: HardwareKey? = null
private var rootView: View? = null private lateinit var rootView: View
private var passwordCheckBox: CompoundButton? = null private lateinit var passwordCheckBox: CompoundButton
private lateinit var passwordView: PassKeyView
private lateinit var passwordRepeatTextInputLayout: TextInputLayout
private lateinit var passwordRepeatView: TextView
private var passKeyView: PassKeyView? = null private lateinit var keyFileCheckBox: CompoundButton
private var passwordRepeatTextInputLayout: TextInputLayout? = null private lateinit var keyFileSelectionView: KeyFileSelectionView
private var passwordRepeatView: TextView? = null
private var keyFileCheckBox: CompoundButton? = null private lateinit var hardwareKeyCheckBox: CompoundButton
private var keyFileSelectionView: KeyFileSelectionView? = null private lateinit var hardwareKeySelectionView: HardwareKeySelectionView
private var mListener: AssignMainCredentialDialogListener? = null private var mListener: AssignMainCredentialDialogListener? = null
@@ -67,13 +74,15 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
private var mNoKeyConfirmationDialog: AlertDialog? = null private var mNoKeyConfirmationDialog: AlertDialog? = null
private var mEmptyKeyFileConfirmationDialog: AlertDialog? = null private var mEmptyKeyFileConfirmationDialog: AlertDialog? = null
private var mAllowNoMasterKey: Boolean = false
private val passwordTextWatcher = object : TextWatcher { private val passwordTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) { override fun afterTextChanged(editable: Editable) {
passwordCheckBox?.isChecked = true passwordCheckBox.isChecked = true
} }
} }
@@ -113,10 +122,9 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
var allowNoMasterKey = false
arguments?.apply { arguments?.apply {
if (containsKey(ALLOW_NO_MASTER_KEY_ARG)) if (containsKey(ALLOW_NO_MASTER_KEY_ARG))
allowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false) mAllowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false)
} }
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
@@ -128,63 +136,63 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
.setPositiveButton(android.R.string.ok) { _, _ -> } .setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
rootView?.findViewById<View>(R.id.credentials_information)?.setOnClickListener { rootView.findViewById<View>(R.id.credentials_information)?.setOnClickListener {
UriUtil.gotoUrl(activity, R.string.credentials_explanation_url) activity.openUrl(R.string.credentials_explanation_url)
} }
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox) passwordCheckBox = rootView.findViewById(R.id.password_checkbox)
passKeyView = rootView?.findViewById(R.id.password_view) passwordView = rootView.findViewById(R.id.password_view)
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout) passwordRepeatTextInputLayout = rootView.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView?.findViewById(R.id.password_confirmation) passwordRepeatView = rootView.findViewById(R.id.password_confirmation)
passwordRepeatView?.applyFontVisibility() passwordRepeatView.applyFontVisibility()
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox) keyFileCheckBox = rootView.findViewById(R.id.keyfile_checkbox)
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection) keyFileSelectionView = rootView.findViewById(R.id.keyfile_selection)
hardwareKeyCheckBox = rootView.findViewById(R.id.hardware_key_checkbox)
hardwareKeySelectionView = rootView.findViewById(R.id.hardware_key_selection)
mExternalFileHelper = ExternalFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper?.buildOpenDocument { uri -> mExternalFileHelper?.buildOpenDocument { uri ->
uri?.let { pathUri -> uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile -> pathUri.getDocumentFile(requireContext())?.length()?.let { lengthFile ->
keyFileSelectionView?.error = null keyFileSelectionView.error = null
keyFileCheckBox?.isChecked = true keyFileCheckBox.isChecked = true
keyFileSelectionView?.uri = pathUri keyFileSelectionView.uri = pathUri
if (lengthFile <= 0L) { if (lengthFile <= 0L) {
showEmptyKeyFileConfirmationDialog() showEmptyKeyFileConfirmationDialog()
} }
} }
} }
} }
keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper) keyFileSelectionView.setOpenDocumentClickListener(mExternalFileHelper)
hardwareKeySelectionView.selectionListener = { hardwareKey ->
hardwareKeyCheckBox.isChecked = true
hardwareKeySelectionView.error =
if (!HardwareKeyActivity.isHardwareKeyAvailable(requireActivity(), hardwareKey)) {
// show hardware driver dialog if required
getString(R.string.error_driver_required, hardwareKey.toString())
} else {
null
}
}
val dialog = builder.create() val dialog = builder.create()
dialog.setOnShowListener { dialog1 ->
val positiveButton = (dialog1 as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
positiveButton.setOnClickListener {
if (passwordCheckBox != null && keyFileCheckBox!= null) { mMasterPassword = ""
dialog.setOnShowListener { dialog1 -> mKeyFileUri = null
val positiveButton = (dialog1 as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE) mHardwareKey = null
positiveButton.setOnClickListener {
mMasterPassword = "" approveMainCredential()
mKeyFile = null }
val negativeButton = dialog1.getButton(DialogInterface.BUTTON_NEGATIVE)
var error = verifyPassword() || verifyKeyFile() negativeButton.setOnClickListener {
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) { mListener?.onAssignKeyDialogNegativeClick(retrieveMainCredential())
error = true dismiss()
if (allowNoMasterKey)
showNoKeyConfirmationDialog()
else {
passwordRepeatTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
}
}
if (!error) {
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
dismiss()
}
}
val negativeButton = dialog1.getButton(DialogInterface.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
mListener?.onAssignKeyDialogNegativeClick(retrieveMainCredential())
dismiss()
}
} }
} }
@@ -194,67 +202,113 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
return super.onCreateDialog(savedInstanceState) return super.onCreateDialog(savedInstanceState)
} }
private fun approveMainCredential() {
val errorPassword = verifyPassword()
val errorKeyFile = verifyKeyFile()
val errorHardwareKey = verifyHardwareKey()
// Check all to fill error
var error = errorPassword || errorKeyFile || errorHardwareKey
val hardwareKey = hardwareKeySelectionView.hardwareKey
if (!error
&& (!passwordCheckBox.isChecked)
&& (!keyFileCheckBox.isChecked)
&& (!hardwareKeyCheckBox.isChecked)
) {
error = true
if (mAllowNoMasterKey) {
// show no key dialog if required
showNoKeyConfirmationDialog()
} else {
passwordRepeatTextInputLayout.error =
getString(R.string.error_disallow_no_credentials)
}
} else if (!error
&& mMasterPassword.isNullOrEmpty()
&& !keyFileCheckBox.isChecked
&& !hardwareKeyCheckBox.isChecked
) {
// show empty password dialog if required
error = true
showEmptyPasswordConfirmationDialog()
} else if (!error
&& hardwareKey != null
&& !HardwareKeyActivity.isHardwareKeyAvailable(
requireActivity(), hardwareKey, false)
) {
// show hardware driver dialog if required
error = true
hardwareKeySelectionView.error =
getString(R.string.error_driver_required, hardwareKey.toString())
}
if (!error) {
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
dismiss()
}
}
private fun verifyPassword(): Boolean {
var error = false
passwordRepeatTextInputLayout.error = null
if (passwordCheckBox.isChecked) {
mMasterPassword = passwordView.passwordString
val confPassword = passwordRepeatView.text.toString()
// Verify that passwords match
if (mMasterPassword != confPassword) {
error = true
// Passwords do not match
passwordRepeatTextInputLayout.error = getString(R.string.error_pass_match)
}
}
return error
}
private fun verifyKeyFile(): Boolean {
var error = false
keyFileSelectionView.error = null
if (keyFileCheckBox.isChecked) {
keyFileSelectionView.uri?.let { uri ->
mKeyFileUri = uri
} ?: run {
error = true
keyFileSelectionView.error = getString(R.string.error_nokeyfile)
}
}
return error
}
private fun verifyHardwareKey(): Boolean {
var error = false
hardwareKeySelectionView.error = null
if (hardwareKeyCheckBox.isChecked) {
hardwareKeySelectionView.hardwareKey?.let { hardwareKey ->
mHardwareKey = hardwareKey
} ?: run {
error = true
hardwareKeySelectionView.error = getString(R.string.error_no_hardware_key)
}
}
return error
}
private fun retrieveMainCredential(): MainCredential { private fun retrieveMainCredential(): MainCredential {
val masterPassword = if (passwordCheckBox!!.isChecked) mMasterPassword else null val masterPassword = if (passwordCheckBox.isChecked) mMasterPassword else null
val keyFile = if (keyFileCheckBox!!.isChecked) mKeyFile else null val keyFileUri = if (keyFileCheckBox.isChecked) mKeyFileUri else null
return MainCredential(masterPassword, keyFile) val hardwareKey = if (hardwareKeyCheckBox.isChecked) mHardwareKey else null
return MainCredential(masterPassword, keyFileUri, hardwareKey)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// To check checkboxes if a text is present // To check checkboxes if a text is present
passKeyView?.addTextChangedListener(passwordTextWatcher) passwordView.addTextChangedListener(passwordTextWatcher)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
passKeyView?.removeTextChangedListener(passwordTextWatcher) passwordView.removeTextChangedListener(passwordTextWatcher)
}
private fun verifyPassword(): Boolean {
var error = false
if (passwordCheckBox != null
&& passwordCheckBox!!.isChecked
&& passKeyView != null
&& passwordRepeatView != null) {
mMasterPassword = passKeyView!!.passwordString
val confPassword = passwordRepeatView!!.text.toString()
// Verify that passwords match
if (mMasterPassword != confPassword) {
error = true
// Passwords do not match
passwordRepeatTextInputLayout?.error = getString(R.string.error_pass_match)
}
if ((mMasterPassword == null
|| mMasterPassword!!.isEmpty())
&& (keyFileCheckBox == null
|| !keyFileCheckBox!!.isChecked
|| keyFileSelectionView?.uri == null)) {
error = true
showEmptyPasswordConfirmationDialog()
}
}
return error
}
private fun verifyKeyFile(): Boolean {
var error = false
if (keyFileCheckBox != null
&& keyFileCheckBox!!.isChecked) {
keyFileSelectionView?.uri?.let { uri ->
mKeyFile = uri
} ?: run {
error = true
keyFileSelectionView?.error = getString(R.string.error_nokeyfile)
}
}
return error
} }
private fun showEmptyPasswordConfirmationDialog() { private fun showEmptyPasswordConfirmationDialog() {
@@ -262,10 +316,8 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
val builder = AlertDialog.Builder(it) val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.warning_empty_password) builder.setMessage(R.string.warning_empty_password)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
if (!verifyKeyFile()) { mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential()) this@SetMainCredentialDialogFragment.dismiss()
this@SetMainCredentialDialogFragment.dismiss()
}
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
mEmptyPasswordConfirmationDialog = builder.create() mEmptyPasswordConfirmationDialog = builder.create()
@@ -299,8 +351,8 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
}) })
.setPositiveButton(android.R.string.ok) { _, _ -> } .setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> .setNegativeButton(android.R.string.cancel) { _, _ ->
keyFileCheckBox?.isChecked = false keyFileCheckBox.isChecked = false
keyFileSelectionView?.uri = null keyFileSelectionView.uri = null
} }
mEmptyKeyFileConfirmationDialog = builder.create() mEmptyKeyFileConfirmationDialog = builder.create()
mEmptyKeyFileConfirmationDialog?.show() mEmptyKeyFileConfirmationDialog?.show()

View File

@@ -32,7 +32,6 @@ import android.view.inputmethod.EditorInfo
import android.widget.* import android.widget.*
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.OtpModel import com.kunzisoft.keepass.model.OtpModel
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
@@ -45,7 +44,9 @@ import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_TOTP_PERIOD
import com.kunzisoft.keepass.otp.OtpTokenType import com.kunzisoft.keepass.otp.OtpTokenType
import com.kunzisoft.keepass.otp.OtpType import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.otp.TokenCalculator import com.kunzisoft.keepass.otp.TokenCalculator
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.isContributingUser
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.utils.getParcelableCompat
import java.util.* import java.util.*
class SetOTPDialogFragment : DatabaseDialogFragment() { class SetOTPDialogFragment : DatabaseDialogFragment() {
@@ -126,14 +127,14 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
// Retrieve OTP model from instance state // Retrieve OTP model from instance state
if (savedInstanceState != null) { if (savedInstanceState != null) {
if (savedInstanceState.containsKey(KEY_OTP)) { if (savedInstanceState.containsKey(KEY_OTP)) {
savedInstanceState.getParcelable<OtpModel>(KEY_OTP)?.let { otpModel -> savedInstanceState.getParcelableCompat<OtpModel>(KEY_OTP)?.let { otpModel ->
mOtpElement = OtpElement(otpModel) mOtpElement = OtpElement(otpModel)
} }
} }
} else { } else {
arguments?.apply { arguments?.apply {
if (containsKey(KEY_OTP)) { if (containsKey(KEY_OTP)) {
getParcelable<OtpModel?>(KEY_OTP)?.let { otpModel -> getParcelableCompat<OtpModel>(KEY_OTP)?.let { otpModel ->
mOtpElement = OtpElement(otpModel) mOtpElement = OtpElement(otpModel)
} }
} }
@@ -206,7 +207,7 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
} }
// Proprietary only on full version // Proprietary only on full version
mTotpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues( mTotpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
UriUtil.contributingUser(activity) activity.isContributingUser()
) )
totpTokenTypeAdapter = ArrayAdapter(activity, totpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, mTotpTokenTypeArray!!).apply { android.R.layout.simple_spinner_item, mTotpTokenTypeArray!!).apply {
@@ -242,7 +243,7 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
} }
root?.findViewById<View>(R.id.otp_information)?.setOnClickListener { root?.findViewById<View>(R.id.otp_information)?.setOnClickListener {
UriUtil.gotoUrl(activity, R.string.otp_explanation_url) activity.openUrl(R.string.otp_explanation_url)
} }
return builder.create() return builder.create()
@@ -470,4 +471,4 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
} }
} }
} }
} }

View File

@@ -1,63 +0,0 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.DatePickerDialog
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.Context
import android.os.Bundle
import android.text.format.DateFormat
import androidx.fragment.app.DialogFragment
// Not as DatabaseDialogFragment because crash on KitKat
class TimePickerFragment : DialogFragment() {
private var defaultHour: Int = 0
private var defaultMinute: Int = 0
private var mListener: TimePickerDialog.OnTimeSetListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as TimePickerDialog.OnTimeSetListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DatePickerDialog.OnDateSetListener::class.java.name)
}
}
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Create a new instance of DatePickerDialog and return it
return context?.let {
arguments?.apply {
if (containsKey(DEFAULT_HOUR_BUNDLE_KEY))
defaultHour = getInt(DEFAULT_HOUR_BUNDLE_KEY)
if (containsKey(DEFAULT_MINUTE_BUNDLE_KEY))
defaultMinute = getInt(DEFAULT_MINUTE_BUNDLE_KEY)
}
TimePickerDialog(it, mListener, defaultHour, defaultMinute, DateFormat.is24HourFormat(activity))
} ?: super.onCreateDialog(savedInstanceState)
}
companion object {
private const val DEFAULT_HOUR_BUNDLE_KEY = "DEFAULT_HOUR_BUNDLE_KEY"
private const val DEFAULT_MINUTE_BUNDLE_KEY = "DEFAULT_MINUTE_BUNDLE_KEY"
fun getInstance(defaultHour: Int,
defaultMinute: Int): TimePickerFragment {
return TimePickerFragment().apply {
arguments = Bundle().apply {
putInt(DEFAULT_HOUR_BUNDLE_KEY, defaultHour)
putInt(DEFAULT_MINUTE_BUNDLE_KEY, defaultMinute)
}
}
}
}
}

View File

@@ -22,12 +22,12 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog import android.app.Dialog
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
class UnavailableFeatureDialogFragment : DialogFragment() { class UnavailableFeatureDialogFragment : DialogFragment() {

View File

@@ -26,7 +26,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.openUrl
/** /**
* Custom Dialog that asks the user to download the pro version or make a donation. * Custom Dialog that asks the user to download the pro version or make a donation.
@@ -39,21 +39,22 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
val stringBuilder = SpannableStringBuilder() val stringBuilder = SpannableStringBuilder()
if (UriUtil.contributingUser(activity)) { /*
if (activity.isContributingUser()) {
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_thanks), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n") .append(HtmlCompat.fromHtml(getString(R.string.html_rose), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_work_hard), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_upgrade), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() } builder.setPositiveButton(android.R.string.ok) { _, _ -> dismiss() }
} else { } else {
*/
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n") stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n\n")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ") .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY)) .append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
builder.setPositiveButton(R.string.contribute) { _, _ -> builder.setPositiveButton(R.string.contribute) { _, _ ->
UriUtil.gotoUrl(requireContext(), R.string.contribution_url) context?.openUrl(R.string.contribution_url)
} }
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() } //}
}
builder.setMessage(stringBuilder) builder.setMessage(stringBuilder)
// Create the AlertDialog object and return it // Create the AlertDialog object and return it
return builder.create() return builder.create()

View File

@@ -2,19 +2,19 @@ package com.kunzisoft.keepass.activities.fragments
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
import com.kunzisoft.keepass.activities.stylish.StylishFragment import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseFragment : StylishFragment(), DatabaseRetrieval { abstract class DatabaseFragment : Fragment(), DatabaseRetrieval {
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels() private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
protected var mDatabase: Database? = null protected var mDatabase: ContextualDatabase? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -38,7 +38,7 @@ abstract class DatabaseFragment : StylishFragment(), DatabaseRetrieval {
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {

View File

@@ -35,14 +35,20 @@ import com.kunzisoft.keepass.activities.dialogs.ReplaceFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.adapters.TagsProposalAdapter import com.kunzisoft.keepass.adapters.TagsProposalAdapter
import com.kunzisoft.keepass.database.ContextualDatabase
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.template.Template import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.view.* import com.kunzisoft.keepass.utils.getParcelableList
import com.kunzisoft.keepass.utils.putParcelableList
import com.kunzisoft.keepass.view.TagsCompletionView
import com.kunzisoft.keepass.view.TemplateEditView
import com.kunzisoft.keepass.view.collapse
import com.kunzisoft.keepass.view.expand
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import com.tokenautocomplete.FilteredArrayAdapter import com.tokenautocomplete.FilteredArrayAdapter
@@ -71,12 +77,11 @@ class EntryEditFragment: DatabaseFragment() {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
// Retrieve the textColor to tint the icon // Retrieve the textColor to tint the icon
val taIconColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor)) val taIconColor = context?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
mIconColor = taIconColor?.getColor(0, Color.BLACK) ?: Color.BLACK mIconColor = taIconColor?.getColor(0, Color.BLACK) ?: Color.BLACK
taIconColor?.recycle() taIconColor?.recycle()
return inflater.cloneInContext(contextThemed) return inflater.inflate(R.layout.fragment_entry_edit, container, false)
.inflate(R.layout.fragment_entry_edit, container, false)
} }
override fun onViewCreated(view: View, override fun onViewCreated(view: View,
@@ -124,7 +129,7 @@ class EntryEditFragment: DatabaseFragment() {
if (savedInstanceState != null) { if (savedInstanceState != null) {
val attachments: List<Attachment> = val attachments: List<Attachment> =
savedInstanceState.getParcelableArrayList(ATTACHMENTS_TAG) ?: listOf() savedInstanceState.getParcelableList(ATTACHMENTS_TAG) ?: listOf()
setAttachments(attachments) setAttachments(attachments)
} }
@@ -268,7 +273,7 @@ class EntryEditFragment: DatabaseFragment() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
templateView.populateIconMethod = { imageView, icon -> templateView.populateIconMethod = { imageView, icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor) database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
@@ -379,7 +384,7 @@ class EntryEditFragment: DatabaseFragment() {
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putParcelableArrayList(ATTACHMENTS_TAG, ArrayList(getAttachments())) outState.putParcelableList(ATTACHMENTS_TAG, getAttachments())
} }
/* ------------- /* -------------

View File

@@ -14,20 +14,21 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.ContextualDatabase
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.template.TemplateField import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString
import com.kunzisoft.keepass.utils.UuidUtil import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.view.TemplateView import com.kunzisoft.keepass.view.TemplateView
import com.kunzisoft.keepass.view.hideByFading import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.showByFading import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.viewmodels.EntryViewModel import com.kunzisoft.keepass.viewmodels.EntryViewModel
import java.util.*
class EntryFragment: DatabaseFragment() { class EntryFragment: DatabaseFragment() {
@@ -58,8 +59,7 @@ class EntryFragment: DatabaseFragment() {
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
return inflater.cloneInContext(contextThemed) return inflater.inflate(R.layout.fragment_entry, container, false)
.inflate(R.layout.fragment_entry, container, false)
} }
override fun onViewCreated(view: View, override fun onViewCreated(view: View,
@@ -133,7 +133,7 @@ class EntryFragment: DatabaseFragment() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
context?.let { context -> context?.let { context ->
attachmentsAdapter = EntryAttachmentsItemsAdapter(context) attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
attachmentsAdapter?.database = database attachmentsAdapter?.database = database
@@ -158,11 +158,9 @@ class EntryFragment: DatabaseFragment() {
setOnCopyActionClickListener { field -> setOnCopyActionClickListener { field ->
mClipboardHelper?.timeoutCopyToClipboard( mClipboardHelper?.timeoutCopyToClipboard(
TemplateField.getLocalizedName(context, field.name),
field.protectedValue.stringValue, field.protectedValue.stringValue,
getString( field.protectedValue.isProtected
R.string.copy_field,
TemplateField.getLocalizedName(context, field.name)
)
) )
} }
} }
@@ -251,8 +249,7 @@ class EntryFragment: DatabaseFragment() {
fun launchEntryCopyEducationAction() { fun launchEntryCopyEducationAction() {
val appNameString = getString(R.string.app_name) val appNameString = getString(R.string.app_name)
mClipboardHelper?.timeoutCopyToClipboard(appNameString, mClipboardHelper?.timeoutCopyToClipboard(appNameString, appNameString)
getString(R.string.copy_field, appNameString))
} }
companion object { companion object {

View File

@@ -4,16 +4,16 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.viewmodels.EntryViewModel import com.kunzisoft.keepass.viewmodels.EntryViewModel
class EntryHistoryFragment: StylishFragment() { class EntryHistoryFragment: Fragment() {
private lateinit var historyContainerView: View private lateinit var historyContainerView: View
private lateinit var historyListView: RecyclerView private lateinit var historyListView: RecyclerView
@@ -28,8 +28,7 @@ class EntryHistoryFragment: StylishFragment() {
): View? { ): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
return inflater.cloneInContext(contextThemed) return inflater.inflate(R.layout.fragment_entry_history, container, false)
.inflate(R.layout.fragment_entry_history, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -22,27 +22,33 @@ package com.kunzisoft.keepass.activities.fragments
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.* import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.view.MenuProvider
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.EntryEditActivity
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.adapters.NodesAdapter import com.kunzisoft.keepass.adapters.NodesAdapter
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
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.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.KeyboardUtil.hideKeyboard
import com.kunzisoft.keepass.viewmodels.GroupViewModel import com.kunzisoft.keepass.viewmodels.GroupViewModel
import java.util.* import java.util.LinkedList
class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener { class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener {
@@ -73,19 +79,6 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
private var mRecycleBinEnable: Boolean = false private var mRecycleBinEnable: Boolean = false
private var mRecycleBin: Group? = null private var mRecycleBin: Group? = null
var mEntryActivityResultLauncher = EntryEditActivity.registerForEntryResult(this) { entryId ->
entryId?.let {
// Simply refresh the list
rebuildList()
// Scroll to the new entry
mDatabase?.getEntryById(it)?.let { entry ->
mAdapter?.indexOf(entry)?.let { position ->
mNodesRecyclerView?.scrollToPosition(position)
}
}
} ?: Log.e(this.javaClass.name, "Entry cannot be retrieved in Activity Result")
}
private var mRecycleViewScrollListener = object : RecyclerView.OnScrollListener() { private var mRecycleViewScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState) super.onScrollStateChanged(recyclerView, newState)
@@ -99,6 +92,40 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
} }
} }
private val menuProvider: MenuProvider = object: MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.tree, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (mRecycleBinEnable) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context),
PreferencesUtil.getRecycleBinBottomSort(context)
)
} else {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context)
)
}
sortDialogFragment.show(childFragmentManager, "sortDialog")
}
true
}
else -> false
}
}
}
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
@@ -137,23 +164,16 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
super.onDetach() super.onDetach()
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onDatabaseRetrieved(database: Database?) {
mRecycleBinEnable = database?.isRecycleBinEnabled == true mRecycleBinEnable = database?.isRecycleBinEnabled == true
mRecycleBin = database?.recycleBin mRecycleBin = database?.recycleBin
contextThemed?.let { context -> context?.let { context ->
database?.let { database -> database?.let { database ->
mAdapter = NodesAdapter(context, database).apply { mAdapter = NodesAdapter(context, database).apply {
setOnNodeClickListener(object : NodesAdapter.NodeClickCallback { setOnNodeClickListener(object : NodesAdapter.NodeClickCallback {
override fun onNodeClick(database: Database, node: Node) { override fun onNodeClick(database: ContextualDatabase, node: Node) {
if (mCurrentGroup?.isVirtual == false if (nodeActionSelectionMode) {
&& nodeActionSelectionMode) {
if (listActionNodes.contains(node)) { if (listActionNodes.contains(node)) {
// Remove selected item if already selected // Remove selected item if already selected
listActionNodes.remove(node) listActionNodes.remove(node)
@@ -169,9 +189,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
} }
} }
override fun onNodeLongClick(database: Database, node: Node): Boolean { override fun onNodeLongClick(database: ContextualDatabase, node: Node): Boolean {
if (mCurrentGroup?.isVirtual == false if (nodeActionPasteMode == PasteMode.UNDEFINED) {
&& nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click // Select the first item after a long click
if (!listActionNodes.contains(node)) if (!listActionNodes.contains(node))
listActionNodes.add(node) listActionNodes.add(node)
@@ -180,6 +199,7 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
setActionNodes(listActionNodes) setActionNodes(listActionNodes)
notifyNodeChanged(node) notifyNodeChanged(node)
activity?.hideKeyboard()
} }
return true return true
} }
@@ -191,7 +211,7 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {
@@ -207,13 +227,14 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
// To apply theme // To apply theme
return inflater.cloneInContext(contextThemed) return inflater.inflate(R.layout.fragment_nodes, container, false)
.inflate(R.layout.fragment_nodes, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity?.addMenuProvider(menuProvider, viewLifecycleOwner)
mNodesRecyclerView = view.findViewById(R.id.nodes_list) mNodesRecyclerView = view.findViewById(R.id.nodes_list)
notFoundView = view.findViewById(R.id.not_found_container) notFoundView = view.findViewById(R.id.not_found_container)
@@ -242,8 +263,6 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
activity?.intent?.let { activity?.intent?.let {
specialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(it) specialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(it)
} }
rebuildList()
} }
override fun onPause() { override fun onPause() {
@@ -293,43 +312,7 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
} }
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { fun actionNodesCallback(database: ContextualDatabase,
inflater.inflate(R.menu.tree, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (mRecycleBinEnable) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context),
PreferencesUtil.getRecycleBinBottomSort(context)
)
} else {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context)
)
}
sortDialogFragment.show(childFragmentManager, "sortDialog")
}
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
fun actionNodesCallback(database: Database,
nodes: List<Node>, nodes: List<Node>,
menuListener: NodesActionMenuListener?, menuListener: NodesActionMenuListener?,
onDestroyActionMode: (mode: ActionMode?) -> Unit) : ActionMode.Callback { onDestroyActionMode: (mode: ActionMode?) -> Unit) : ActionMode.Callback {
@@ -363,14 +346,12 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
} }
// Move // Move
if (database.isReadOnly if (database.isReadOnly) {
|| isASearchResult) {
menu?.removeItem(R.id.menu_move) menu?.removeItem(R.id.menu_move)
} }
// Copy (not allowed for group) // Copy (not allowed for group)
if (database.isReadOnly if (database.isReadOnly
|| isASearchResult
|| nodes.any { it.type == Type.GROUP }) { || nodes.any { it.type == Type.GROUP }) {
menu?.removeItem(R.id.menu_copy) menu?.removeItem(R.id.menu_copy)
} }
@@ -433,20 +414,20 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
* Callback listener to redefine to do an action when a node is click * Callback listener to redefine to do an action when a node is click
*/ */
interface NodeClickListener { interface NodeClickListener {
fun onNodeClick(database: Database, node: Node) fun onNodeClick(database: ContextualDatabase, node: Node)
fun onNodeSelected(database: Database, nodes: List<Node>): Boolean fun onNodeSelected(database: ContextualDatabase, nodes: List<Node>): Boolean
} }
/** /**
* Menu listener to redefine to do an action in menu * Menu listener to redefine to do an action in menu
*/ */
interface NodesActionMenuListener { interface NodesActionMenuListener {
fun onOpenMenuClick(database: Database, node: Node): Boolean fun onOpenMenuClick(database: ContextualDatabase, node: Node): Boolean
fun onEditMenuClick(database: Database, node: Node): Boolean fun onEditMenuClick(database: ContextualDatabase, node: Node): Boolean
fun onCopyMenuClick(database: Database, nodes: List<Node>): Boolean fun onCopyMenuClick(database: ContextualDatabase, nodes: List<Node>): Boolean
fun onMoveMenuClick(database: Database, nodes: List<Node>): Boolean fun onMoveMenuClick(database: ContextualDatabase, nodes: List<Node>): Boolean
fun onDeleteMenuClick(database: Database, nodes: List<Node>): Boolean fun onDeleteMenuClick(database: ContextualDatabase, nodes: List<Node>): Boolean
fun onPasteMenuClick(database: Database, pasteMode: PasteMode?, nodes: List<Node>): Boolean fun onPasteMenuClick(database: ContextualDatabase, pasteMode: PasteMode?, nodes: List<Node>): Boolean
} }
enum class PasteMode { enum class PasteMode {

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.activities.fragments
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
@@ -32,7 +32,7 @@ class IconCustomFragment : IconFragment<IconImageCustom>() {
return R.layout.fragment_icon_grid return R.layout.fragment_icon_grid
} }
override fun defineIconList(database: Database?) { override fun defineIconList(database: ContextualDatabase?) {
database?.doForEachCustomIcons { customIcon, _ -> database?.doForEachCustomIcons { customIcon, _ ->
iconPickerAdapter.addIcon(customIcon, false) iconPickerAdapter.addIcon(customIcon, false)
} }

View File

@@ -28,7 +28,7 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.IconPickerAdapter import com.kunzisoft.keepass.adapters.IconPickerAdapter
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.icon.IconImageDraw import com.kunzisoft.keepass.database.element.icon.IconImageDraw
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -47,7 +47,7 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
abstract fun retrieveMainLayoutId(): Int abstract fun retrieveMainLayoutId(): Int
abstract fun defineIconList(database: Database?) abstract fun defineIconList(database: ContextualDatabase?)
override fun onCreateView(inflater: LayoutInflater, override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@@ -59,7 +59,7 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Retrieve the textColor to tint the icon // Retrieve the textColor to tint the icon
val ta = contextThemed?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor)) val ta = context?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
val tintColor = ta?.getColor(0, Color.BLACK) ?: Color.BLACK val tintColor = ta?.getColor(0, Color.BLACK) ?: Color.BLACK
ta?.recycle() ta?.recycle()
@@ -71,7 +71,7 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
resetAppTimeoutWhenViewFocusedOrChanged(view) resetAppTimeoutWhenViewFocusedOrChanged(view)
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
iconPickerAdapter.iconDrawableFactory = database?.iconDrawableFactory iconPickerAdapter.iconDrawableFactory = database?.iconDrawableFactory
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {

View File

@@ -10,7 +10,7 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.IconPickerPagerAdapter import com.kunzisoft.keepass.adapters.IconPickerPagerAdapter
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
class IconPickerFragment : DatabaseFragment() { class IconPickerFragment : DatabaseFragment() {
@@ -48,7 +48,7 @@ class IconPickerFragment : DatabaseFragment() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
iconPickerPagerAdapter = IconPickerPagerAdapter(this, iconPickerPagerAdapter = IconPickerPagerAdapter(this,
if (database?.allowCustomIcons == true) 2 else 1) if (database?.allowCustomIcons == true) 2 else 1)
viewPager.adapter = iconPickerPagerAdapter viewPager.adapter = iconPickerPagerAdapter

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.activities.fragments package com.kunzisoft.keepass.activities.fragments
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
@@ -30,7 +30,7 @@ class IconStandardFragment : IconFragment<IconImageStandard>() {
return R.layout.fragment_icon_grid return R.layout.fragment_icon_grid
} }
override fun defineIconList(database: Database?) { override fun defineIconList(database: ContextualDatabase?) {
database?.doForEachStandardIcons { standardIcon -> database?.doForEachStandardIcons { standardIcon ->
iconPickerAdapter.addIcon(standardIcon, false) iconPickerAdapter.addIcon(standardIcon, false)
} }

View File

@@ -30,7 +30,7 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.KeyGeneratorPagerAdapter import com.kunzisoft.keepass.adapters.KeyGeneratorPagerAdapter
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
class KeyGeneratorFragment : DatabaseFragment() { class KeyGeneratorFragment : DatabaseFragment() {
@@ -107,7 +107,7 @@ class KeyGeneratorFragment : DatabaseFragment() {
super.onDestroyView() super.onDestroyView()
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
// Nothing here // Nothing here
} }

View File

@@ -30,7 +30,7 @@ import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.password.PassphraseGenerator import com.kunzisoft.keepass.password.PassphraseGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
@@ -73,14 +73,16 @@ class PassphraseGeneratorFragment : DatabaseFragment() {
minSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_min) minSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_min)
maxSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_max) maxSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_max)
contextThemed?.let { context -> context?.let { context ->
passphraseCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context)) passphraseCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context))
View.VISIBLE else View.GONE View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(context) val clipboardHelper = ClipboardHelper(context)
passphraseCopyView?.setOnClickListener { passphraseCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(passKeyView.passwordString, clipboardHelper.timeoutCopyToClipboard(
getString(R.string.copy_field, getString(R.string.passphrase),
getString(R.string.entry_password))) passKeyView.passwordString,
true
)
} }
wordCaseAdapter = ArrayAdapter(context, wordCaseAdapter = ArrayAdapter(context,
@@ -242,7 +244,7 @@ class PassphraseGeneratorFragment : DatabaseFragment() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
// Nothing here // Nothing here
} }

View File

@@ -32,7 +32,7 @@ import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.password.PasswordGenerator import com.kunzisoft.keepass.password.PasswordGenerator
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
@@ -94,14 +94,16 @@ class PasswordGeneratorFragment : DatabaseFragment() {
atLeastOneCompound = view.findViewById(R.id.atLeastOne_filter) atLeastOneCompound = view.findViewById(R.id.atLeastOne_filter)
excludeAmbiguousCompound = view.findViewById(R.id.excludeAmbiguous_filter) excludeAmbiguousCompound = view.findViewById(R.id.excludeAmbiguous_filter)
contextThemed?.let { context -> context?.let { context ->
passwordCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context)) passwordCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context))
View.VISIBLE else View.GONE View.VISIBLE else View.GONE
val clipboardHelper = ClipboardHelper(context) val clipboardHelper = ClipboardHelper(context)
passwordCopyView?.setOnClickListener { passwordCopyView?.setOnClickListener {
clipboardHelper.timeoutCopyToClipboard(passKeyView.passwordString, clipboardHelper.timeoutCopyToClipboard(
getString(R.string.copy_field, getString(R.string.password),
getString(R.string.entry_password))) passKeyView.passwordString,
true
)
} }
} }
@@ -316,7 +318,7 @@ class PasswordGeneratorFragment : DatabaseFragment() {
super.onDestroy() super.onDestroy()
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
// Nothing here // Nothing here
} }

View File

@@ -26,7 +26,9 @@ import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import java.io.Serializable import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.utils.getEnumExtra
import com.kunzisoft.keepass.utils.putEnumExtra
object EntrySelectionHelper { object EntrySelectionHelper {
@@ -82,17 +84,17 @@ object EntrySelectionHelper {
} }
fun retrieveSearchInfoFromIntent(intent: Intent): SearchInfo? { fun retrieveSearchInfoFromIntent(intent: Intent): SearchInfo? {
return intent.getParcelableExtra(KEY_SEARCH_INFO) return intent.getParcelableExtraCompat(KEY_SEARCH_INFO)
} }
fun addRegisterInfoInIntent(intent: Intent, registerInfo: RegisterInfo?) { private fun addRegisterInfoInIntent(intent: Intent, registerInfo: RegisterInfo?) {
registerInfo?.let { registerInfo?.let {
intent.putExtra(KEY_REGISTER_INFO, it) intent.putExtra(KEY_REGISTER_INFO, it)
} }
} }
fun retrieveRegisterInfoFromIntent(intent: Intent): RegisterInfo? { fun retrieveRegisterInfoFromIntent(intent: Intent): RegisterInfo? {
return intent.getParcelableExtra(KEY_REGISTER_INFO) return intent.getParcelableExtraCompat(KEY_REGISTER_INFO)
} }
fun removeInfoFromIntent(intent: Intent) { fun removeInfoFromIntent(intent: Intent) {
@@ -101,7 +103,7 @@ object EntrySelectionHelper {
} }
fun addSpecialModeInIntent(intent: Intent, specialMode: SpecialMode) { fun addSpecialModeInIntent(intent: Intent, specialMode: SpecialMode) {
intent.putExtra(KEY_SPECIAL_MODE, specialMode as Serializable) intent.putEnumExtra(KEY_SPECIAL_MODE, specialMode)
} }
fun retrieveSpecialModeFromIntent(intent: Intent): SpecialMode { fun retrieveSpecialModeFromIntent(intent: Intent): SpecialMode {
@@ -109,12 +111,11 @@ object EntrySelectionHelper {
if (AutofillHelper.retrieveAutofillComponent(intent) != null) if (AutofillHelper.retrieveAutofillComponent(intent) != null)
return SpecialMode.SELECTION return SpecialMode.SELECTION
} }
return intent.getSerializableExtra(KEY_SPECIAL_MODE) as SpecialMode? return intent.getEnumExtra<SpecialMode>(KEY_SPECIAL_MODE) ?: SpecialMode.DEFAULT
?: SpecialMode.DEFAULT
} }
fun addTypeModeInIntent(intent: Intent, typeMode: TypeMode) { private fun addTypeModeInIntent(intent: Intent, typeMode: TypeMode) {
intent.putExtra(KEY_TYPE_MODE, typeMode as Serializable) intent.putEnumExtra(KEY_TYPE_MODE, typeMode)
} }
fun retrieveTypeModeFromIntent(intent: Intent): TypeMode { fun retrieveTypeModeFromIntent(intent: Intent): TypeMode {
@@ -122,7 +123,7 @@ object EntrySelectionHelper {
if (AutofillHelper.retrieveAutofillComponent(intent) != null) if (AutofillHelper.retrieveAutofillComponent(intent) != null)
return TypeMode.AUTOFILL return TypeMode.AUTOFILL
} }
return intent.getSerializableExtra(KEY_TYPE_MODE) as TypeMode? ?: TypeMode.DEFAULT return intent.getEnumExtra<TypeMode>(KEY_TYPE_MODE) ?: TypeMode.DEFAULT
} }
fun removeModesFromIntent(intent: Intent) { fun removeModesFromIntent(intent: Intent) {
@@ -175,7 +176,7 @@ object EntrySelectionHelper {
} }
} }
if (!autofillComponentInit) { if (!autofillComponentInit) {
if (intent.getSerializableExtra(KEY_SPECIAL_MODE) != null) { if (intent.getEnumExtra<SpecialMode>(KEY_SPECIAL_MODE) != null) {
when (retrieveTypeModeFromIntent(intent)) { when (retrieveTypeModeFromIntent(intent)) {
TypeMode.DEFAULT -> { TypeMode.DEFAULT -> {
removeModesFromIntent(intent) removeModesFromIntent(intent)

View File

@@ -22,7 +22,6 @@ package com.kunzisoft.keepass.activities.helpers
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
@@ -33,7 +32,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil.takeUriPermission
class ExternalFileHelper { class ExternalFileHelper {
@@ -56,11 +55,9 @@ class ExternalFileHelper {
fun buildOpenDocument(onFileSelected: ((uri: Uri?) -> Unit)?) { fun buildOpenDocument(onFileSelected: ((uri: Uri?) -> Unit)?) {
val resultCallback = ActivityResultCallback<Uri> { result -> val resultCallback = ActivityResultCallback<Uri?> { result ->
result?.let { uri -> activity?.contentResolver?.takeUriPermission(result)
UriUtil.takeUriPermission(activity?.contentResolver, uri) onFileSelected?.invoke(result)
onFileSelected?.invoke(uri)
}
} }
getContentResultLauncher = if (fragment != null) { getContentResultLauncher = if (fragment != null) {
@@ -91,7 +88,7 @@ class ExternalFileHelper {
fun buildCreateDocument(typeString: String = "application/octet-stream", fun buildCreateDocument(typeString: String = "application/octet-stream",
onFileCreated: (fileCreated: Uri?)->Unit) { onFileCreated: (fileCreated: Uri?)->Unit) {
val resultCallback = ActivityResultCallback<Uri> { result -> val resultCallback = ActivityResultCallback<Uri?> { result ->
onFileCreated.invoke(result) onFileCreated.invoke(result)
} }
@@ -150,7 +147,7 @@ class ExternalFileHelper {
class OpenDocument : ActivityResultContracts.OpenDocument() { class OpenDocument : ActivityResultContracts.OpenDocument() {
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
override fun createIntent(context: Context, input: Array<out String>): Intent { override fun createIntent(context: Context, input: Array<String>): Intent {
return super.createIntent(context, input).apply { return super.createIntent(context, input).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
@@ -178,34 +175,17 @@ class ExternalFileHelper {
} }
} }
class CreateDocument(private val typeString: String) : ActivityResultContracts.CreateDocument() { class CreateDocument(typeString: String) : ActivityResultContracts.CreateDocument(typeString) {
override fun createIntent(context: Context, input: String): Intent { override fun createIntent(context: Context, input: String): Intent {
return super.createIntent(context, input).apply { return super.createIntent(context, input).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
type = typeString
} }
} }
} }
companion object { companion object {
private const val TAG = "OpenFileHelper" private const val TAG = "OpenFileHelper"
@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
}
}
} }
} }

View File

@@ -4,23 +4,24 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.DatabaseTaskProvider
import com.kunzisoft.keepass.model.CipherEncryptDatabase import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.getBinaryDir
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval { abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
protected val mDatabaseViewModel: DatabaseViewModel by viewModels() protected val mDatabaseViewModel: DatabaseViewModel by viewModels()
protected var mDatabaseTaskProvider: DatabaseTaskProvider? = null protected var mDatabaseTaskProvider: DatabaseTaskProvider? = null
protected var mDatabase: Database? = null protected var mDatabase: ContextualDatabase? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mDatabaseTaskProvider = DatabaseTaskProvider(this) mDatabaseTaskProvider = DatabaseTaskProvider(this, showDatabaseDialog())
mDatabaseTaskProvider?.onDatabaseRetrieved = { database -> mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
val databaseWasReloaded = database?.wasReloaded == true val databaseWasReloaded = database?.wasReloaded == true
@@ -36,14 +37,25 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
} }
} }
override fun onDatabaseRetrieved(database: Database?) { protected open fun showDatabaseDialog(): Boolean {
return true
}
override fun onDestroy() {
mDatabaseTaskProvider?.destroy()
mDatabaseTaskProvider = null
mDatabase = null
super.onDestroy()
}
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
mDatabase = database mDatabase = database
mDatabaseViewModel.defineDatabase(database) mDatabaseViewModel.defineDatabase(database)
// optional method implementation // optional method implementation
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {
@@ -51,21 +63,25 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
// optional method implementation // optional method implementation
} }
fun createDatabase(databaseUri: Uri, fun createDatabase(
mainCredential: MainCredential) { databaseUri: Uri,
mainCredential: MainCredential
) {
mDatabaseTaskProvider?.startDatabaseCreate(databaseUri, mainCredential) mDatabaseTaskProvider?.startDatabaseCreate(databaseUri, mainCredential)
} }
fun loadDatabase(databaseUri: Uri, fun loadDatabase(
mainCredential: MainCredential, databaseUri: Uri,
readOnly: Boolean, mainCredential: MainCredential,
cipherEncryptDatabase: CipherEncryptDatabase?, readOnly: Boolean,
fixDuplicateUuid: Boolean) { cipherEncryptDatabase: CipherEncryptDatabase?,
fixDuplicateUuid: Boolean
) {
mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEncryptDatabase, fixDuplicateUuid) mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEncryptDatabase, fixDuplicateUuid)
} }
protected fun closeDatabase() { protected fun closeDatabase() {
mDatabase?.clearAndClose(this) mDatabase?.clearAndClose(this.getBinaryDir())
} }
override fun onResume() { override fun onResume() {

View File

@@ -24,6 +24,7 @@ import android.content.Context
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.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -35,14 +36,13 @@ import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.MainCredential
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.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.GroupInfo import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -66,8 +66,6 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
protected var mMergeDataAllowed: Boolean = false protected var mMergeDataAllowed: Boolean = false
private var mAutoSaveEnable: Boolean = true private var mAutoSaveEnable: Boolean = true
protected var mIconDrawableFactory: IconDrawableFactory? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -89,8 +87,8 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(save) mDatabaseTaskProvider?.startDatabaseSave(save)
} }
mDatabaseViewModel.mergeDatabase.observe(this) { mDatabaseViewModel.mergeDatabase.observe(this) { save ->
mDatabaseTaskProvider?.startDatabaseMerge() mDatabaseTaskProvider?.startDatabaseMerge(save)
} }
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid -> mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
@@ -166,7 +164,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
return true return true
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
super.onDatabaseRetrieved(database) super.onDatabaseRetrieved(database)
// End activity if database not loaded // End activity if database not loaded
@@ -206,16 +204,24 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseReadOnly = database.isReadOnly mDatabaseReadOnly = database.isReadOnly
mMergeDataAllowed = database.isMergeDataAllowed() mMergeDataAllowed = database.isMergeDataAllowed()
mIconDrawableFactory = database.iconDrawableFactory
checkRegister() checkRegister()
} }
} }
override fun finish() {
// To fix weird crash
try {
super.finish()
} catch (e: Exception) {
Log.e(TAG, "Unable to finish the activity", e)
}
}
abstract fun viewToInvalidateTimeout(): View? abstract fun viewToInvalidateTimeout(): View?
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(
database: Database, database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {
@@ -226,6 +232,9 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
// Reload the current activity // Reload the current activity
if (result.isSuccess) { if (result.isSuccess) {
reloadActivity() reloadActivity()
if (actionTask == DatabaseTaskNotificationService.ACTION_DATABASE_MERGE_TASK) {
Toast.makeText(this, R.string.merge_success, Toast.LENGTH_LONG).show()
}
} else { } else {
this.showActionErrorIfNeeded(result) this.showActionErrorIfNeeded(result)
finish() finish()
@@ -234,15 +243,19 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
} }
} }
override fun onPasswordEncodingValidateListener(databaseUri: Uri?, override fun onPasswordEncodingValidateListener(
mainCredential: MainCredential) { databaseUri: Uri?,
mainCredential: MainCredential
) {
assignDatabasePassword(databaseUri, mainCredential) assignDatabasePassword(databaseUri, mainCredential)
} }
private fun assignDatabasePassword(databaseUri: Uri?, private fun assignDatabasePassword(
mainCredential: MainCredential) { databaseUri: Uri?,
mainCredential: MainCredential
) {
if (databaseUri != null) { if (databaseUri != null) {
mDatabaseTaskProvider?.startDatabaseAssignPassword(databaseUri, mainCredential) mDatabaseTaskProvider?.startDatabaseAssignCredential(databaseUri, mainCredential)
} }
} }
@@ -250,7 +263,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabase?.let { database -> mDatabase?.let { database ->
database.fileUri?.let { databaseUri -> database.fileUri?.let { databaseUri ->
// Show the progress dialog now or after dialog confirmation // Show the progress dialog now or after dialog confirmation
if (database.validatePasswordEncoding(mainCredential)) { if (database.isValidCredential(mainCredential.toMasterCredential(contentResolver))) {
assignDatabasePassword(databaseUri, mainCredential) assignDatabasePassword(databaseUri, mainCredential)
} else { } else {
PasswordEncodingDialogFragment.getInstance(databaseUri, mainCredential) PasswordEncodingDialogFragment.getInstance(databaseUri, mainCredential)
@@ -269,11 +282,11 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
} }
fun mergeDatabase() { fun mergeDatabase() {
mDatabaseTaskProvider?.startDatabaseMerge() mDatabaseTaskProvider?.startDatabaseMerge(mAutoSaveEnable)
} }
fun mergeDatabaseFrom(uri: Uri, mainCredential: MainCredential) { fun mergeDatabaseFrom(uri: Uri, mainCredential: MainCredential) {
mDatabaseTaskProvider?.startDatabaseMerge(uri, mainCredential) mDatabaseTaskProvider?.startDatabaseMerge(mAutoSaveEnable, uri, mainCredential)
} }
fun reloadDatabase() { fun reloadDatabase() {
@@ -302,7 +315,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseMoveNodes(nodesToMove, newParent, mAutoSaveEnable) mDatabaseTaskProvider?.startDatabaseMoveNodes(nodesToMove, newParent, mAutoSaveEnable)
} }
private fun eachNodeRecyclable(database: Database, nodes: List<Node>): Boolean { private fun eachNodeRecyclable(database: ContextualDatabase, nodes: List<Node>): Boolean {
return nodes.find { node -> return nodes.find { node ->
var cannotRecycle = true var cannotRecycle = true
if (node is Entry) { if (node is Entry) {
@@ -318,7 +331,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabase?.let { database -> mDatabase?.let { database ->
// If recycle bin enabled, ensure it exists // If recycle bin enabled, ensure it exists
if (database.isRecycleBinEnabled) { if (database.isRecycleBinEnabled) {
database.ensureRecycleBinExists(resources) database.ensureRecycleBinExists(resources.getString(R.string.recycle_bin))
} }
// If recycle bin enabled and not in recycle bin, move in recycle bin // If recycle bin enabled and not in recycle bin, move in recycle bin
@@ -433,9 +446,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
protected fun lockAndExit() { protected fun lockAndExit() {
// Ask confirmation if modification not saved // Ask confirmation if modification not saved
if (mDatabase?.isReadOnly == false if (mDatabase?.dataModifiedSinceLastLoading == true) {
&& mDatabase?.dataModifiedSinceLastLoading == true
&& !PreferencesUtil.isAutoSaveDatabaseEnabled(this)) {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setMessage(R.string.discard_changes) .setMessage(R.string.discard_changes)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
@@ -452,14 +463,14 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabase?.loaded ?: false) mDatabase?.loaded ?: false)
} }
override fun onBackPressed() { override fun onDatabaseBackPressed() {
if (mTimeoutEnable) { if (mTimeoutEnable) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
mDatabase?.loaded == true) { mDatabase?.loaded == true) {
super.onBackPressed() super.onDatabaseBackPressed()
} }
} else { } else {
super.onBackPressed() super.onDatabaseBackPressed()
} }
} }
@@ -480,25 +491,33 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
*/ */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
fun View.resetAppTimeoutWhenViewTouchedOrFocused(context: Context, databaseLoaded: Boolean?) { fun View.resetAppTimeoutWhenViewTouchedOrFocused(context: Context, databaseLoaded: Boolean?) {
// Log.d(DatabaseLockActivity.TAG, "View prepared to reset app timeout") try {
setOnTouchListener { _, event -> // Log.d(DatabaseLockActivity.TAG, "View prepared to reset app timeout")
when (event.action) { setOnTouchListener { _, event ->
MotionEvent.ACTION_DOWN -> { when (event.action) {
// Log.d(DatabaseLockActivity.TAG, "View touched, try to reset app timeout") MotionEvent.ACTION_DOWN -> {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context, // Log.d(DatabaseLockActivity.TAG, "View touched, try to reset app timeout")
databaseLoaded ?: false) TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(
context,
databaseLoaded ?: false
)
}
}
false
}
setOnFocusChangeListener { _, _ ->
// Log.d(DatabaseLockActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(
context,
databaseLoaded ?: false
)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded)
} }
} }
false } catch (e: Exception) {
} Log.e("AppTimeout", "Unable to reset app timeout", e)
setOnFocusChangeListener { _, _ ->
// Log.d(DatabaseLockActivity.TAG, "View focused, try to reset app timeout")
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false)
}
if (this is ViewGroup) {
for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded)
}
} }
} }

View File

@@ -1,18 +1,16 @@
package com.kunzisoft.keepass.activities.legacy package com.kunzisoft.keepass.activities.legacy
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.helpers.TypeMode import com.kunzisoft.keepass.activities.helpers.TypeMode
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.SpecialModeView import com.kunzisoft.keepass.view.ToolbarSpecial
/** /**
@@ -23,20 +21,22 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
private var mTypeMode: TypeMode = TypeMode.DEFAULT private var mTypeMode: TypeMode = TypeMode.DEFAULT
private var mSpecialModeView: SpecialModeView? = null private var mToolbarSpecial: ToolbarSpecial? = null
override fun onBackPressed() { open fun onDatabaseBackPressed() {
if (mSpecialMode != SpecialMode.DEFAULT) if (mSpecialMode != SpecialMode.DEFAULT)
onCancelSpecialMode() onCancelSpecialMode()
else else
super.onBackPressed() onRegularBackPressed()
} }
/** /**
* To call the regular onBackPressed() method in special mode * To call the regular onBackPressed() method in special mode
*/ */
protected fun onRegularBackPressed() { protected fun onRegularBackPressed() {
super.onBackPressed() // Do not call onBackPressedDispatcher.onBackPressed() to avoid loop
// Calling onBackPressed() is now deprecated, directly finish the activity
finish()
} }
/** /**
@@ -75,7 +75,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
open fun onCancelSpecialMode() { open fun onCancelSpecialMode() {
if (isIntentSender()) { if (isIntentSender()) {
// To get the app caller, only for IntentSender // To get the app caller, only for IntentSender
super.onBackPressed() onRegularBackPressed()
} else { } else {
EntrySelectionHelper.removeModesFromIntent(intent) EntrySelectionHelper.removeModesFromIntent(intent)
EntrySelectionHelper.removeInfoFromIntent(intent) EntrySelectionHelper.removeInfoFromIntent(intent)
@@ -88,7 +88,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
protected fun backToTheAppCaller() { protected fun backToTheAppCaller() {
if (isIntentSender()) { if (isIntentSender()) {
// To get the app caller, only for IntentSender // To get the app caller, only for IntentSender
super.onBackPressed() onRegularBackPressed()
} else { } else {
backToTheMainAppAndFinish() backToTheMainAppAndFinish()
} }
@@ -96,14 +96,19 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
private fun backToTheMainAppAndFinish() { private fun backToTheMainAppAndFinish() {
// To move the app in background and return to the main app // To move the app in background and return to the main app
// Not visible as opened with FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
moveTaskToBack(true) moveTaskToBack(true)
// Not finish() to prevent service kill // Not using FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or finish() because kills the service
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onDatabaseBackPressed()
}
})
mSpecialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(intent) mSpecialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(intent)
mTypeMode = EntrySelectionHelper.retrieveTypeModeFromIntent(intent) mTypeMode = EntrySelectionHelper.retrieveTypeModeFromIntent(intent)
} }
@@ -117,8 +122,8 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
?: EntrySelectionHelper.retrieveSearchInfoFromIntent(intent) ?: EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
// To show the selection mode // To show the selection mode
mSpecialModeView = findViewById(R.id.special_mode_view) mToolbarSpecial = findViewById(R.id.special_mode_view)
mSpecialModeView?.apply { mToolbarSpecial?.apply {
// Populate title // Populate title
val selectionModeStringId = when (mSpecialMode) { val selectionModeStringId = when (mSpecialMode) {
SpecialMode.DEFAULT, // Not important because hidden SpecialMode.DEFAULT, // Not important because hidden

View File

@@ -1,11 +1,11 @@
package com.kunzisoft.keepass.activities.legacy package com.kunzisoft.keepass.activities.legacy
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
interface DatabaseRetrieval { interface DatabaseRetrieval {
fun onDatabaseRetrieved(database: Database?) fun onDatabaseRetrieved(database: ContextualDatabase?)
fun onDatabaseActionFinished(database: Database, fun onDatabaseActionFinished(database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result) result: ActionRunnable.Result)
} }

View File

@@ -23,6 +23,7 @@ import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.util.Log import android.util.Log
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import com.google.android.material.color.DynamicColors
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -38,7 +39,7 @@ object Stylish {
* @param context Context to retrieve the theme preference * @param context Context to retrieve the theme preference
*/ */
fun load(context: Context) { fun load(context: Context) {
Log.d(Stylish::class.java.name, "Attatching to " + context.packageName) Log.d(Stylish::class.java.name, "Attaching to " + context.packageName)
try { try {
themeString = PreferencesUtil.getStyle(context) themeString = PreferencesUtil.getStyle(context)
} catch (e: Exception) { } catch (e: Exception) {
@@ -46,7 +47,7 @@ object Stylish {
} }
} }
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
@@ -58,14 +59,21 @@ object Stylish {
} }
} }
return if (systemNightMode) { return if (systemNightMode) {
retrieveEquivalentNightStyle(context, styleString) retrieveEquivalentNightStyle(
context,
styleString ?: context.getString(R.string.list_style_name_night)
)
} else { } else {
retrieveEquivalentLightStyle(context, styleString) retrieveEquivalentLightStyle(
context,
styleString ?: context.getString(R.string.list_style_name_light)
)
} }
} }
fun retrieveEquivalentLightStyle(context: Context, styleString: String): String { fun retrieveEquivalentLightStyle(context: Context, styleString: String): String {
return when (styleString) { return when (styleString) {
context.getString(R.string.list_style_name_dynamic_night) -> context.getString(R.string.list_style_name_dynamic_light)
context.getString(R.string.list_style_name_night) -> context.getString(R.string.list_style_name_light) context.getString(R.string.list_style_name_night) -> context.getString(R.string.list_style_name_light)
context.getString(R.string.list_style_name_black) -> context.getString(R.string.list_style_name_white) context.getString(R.string.list_style_name_black) -> context.getString(R.string.list_style_name_white)
context.getString(R.string.list_style_name_dark) -> context.getString(R.string.list_style_name_clear) context.getString(R.string.list_style_name_dark) -> context.getString(R.string.list_style_name_clear)
@@ -80,6 +88,7 @@ object Stylish {
private fun retrieveEquivalentNightStyle(context: Context, styleString: String): String { private fun retrieveEquivalentNightStyle(context: Context, styleString: String): String {
return when (styleString) { return when (styleString) {
context.getString(R.string.list_style_name_dynamic_light) -> context.getString(R.string.list_style_name_dynamic_night)
context.getString(R.string.list_style_name_light) -> context.getString(R.string.list_style_name_night) context.getString(R.string.list_style_name_light) -> context.getString(R.string.list_style_name_night)
context.getString(R.string.list_style_name_white) -> context.getString(R.string.list_style_name_black) context.getString(R.string.list_style_name_white) -> context.getString(R.string.list_style_name_black)
context.getString(R.string.list_style_name_clear) -> context.getString(R.string.list_style_name_dark) context.getString(R.string.list_style_name_clear) -> context.getString(R.string.list_style_name_dark)
@@ -104,6 +113,13 @@ object Stylish {
PreferencesUtil.setStyle(context, styleString) PreferencesUtil.setStyle(context, styleString)
} }
fun isDynamic(context: Context): Boolean {
return DynamicColors.isDynamicColorAvailable() && (
themeString == context.getString(R.string.list_style_name_dynamic_night)
|| themeString == context.getString(R.string.list_style_name_dynamic_light)
)
}
/** /**
* Function that returns the current id of the style selected in the preference * Function that returns the current id of the style selected in the preference
* @param context Context to retrieve the id * @param context Context to retrieve the id
@@ -111,7 +127,7 @@ object Stylish {
*/ */
@StyleRes @StyleRes
fun getThemeId(context: Context): Int { fun getThemeId(context: Context): Int {
return when (retrieveEquivalentSystemStyle(context, themeString ?: context.getString(R.string.list_style_name_light))) { return when (retrieveEquivalentSystemStyle(context, themeString)) {
context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night
context.getString(R.string.list_style_name_white) -> R.style.KeepassDXStyle_White context.getString(R.string.list_style_name_white) -> R.style.KeepassDXStyle_White
context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black
@@ -127,6 +143,8 @@ object Stylish {
context.getString(R.string.list_style_name_reply_night) -> R.style.KeepassDXStyle_Reply_Night context.getString(R.string.list_style_name_reply_night) -> R.style.KeepassDXStyle_Reply_Night
context.getString(R.string.list_style_name_purple) -> R.style.KeepassDXStyle_Purple context.getString(R.string.list_style_name_purple) -> R.style.KeepassDXStyle_Purple
context.getString(R.string.list_style_name_purple_dark) -> R.style.KeepassDXStyle_Purple_Dark context.getString(R.string.list_style_name_purple_dark) -> R.style.KeepassDXStyle_Purple_Dark
context.getString(R.string.list_style_name_dynamic_light) -> R.style.KeepassDXStyle_Light_Dynamic
context.getString(R.string.list_style_name_dynamic_night) -> R.style.KeepassDXStyle_Night_Dynamic
else -> R.style.KeepassDXStyle_Light else -> R.style.KeepassDXStyle_Light
} }
} }

View File

@@ -21,14 +21,22 @@ package com.kunzisoft.keepass.activities.stylish
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.WindowManager import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_PREFERENCE_CHANGED import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_PREFERENCE_CHANGED
import com.kunzisoft.keepass.settings.PreferencesUtil
/** /**
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from * Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
@@ -77,12 +85,33 @@ abstract class StylishActivity : AppCompatActivity() {
customStyle = applyCustomStyle() customStyle = applyCustomStyle()
if (customStyle) { if (customStyle) {
// Preconfigured themes
this.themeId = Stylish.getThemeId(this) this.themeId = Stylish.getThemeId(this)
setTheme(themeId) setTheme(themeId)
if (Stylish.isDynamic(this)) {
// Material You theme
DynamicColors.applyToActivityIfAvailable(this)
}
} }
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(onScreenshotModePrefListener)
}
private val onScreenshotModePrefListener = OnSharedPreferenceChangeListener { _, key ->
if (key != getString(R.string.enable_screenshot_mode_key)) return@OnSharedPreferenceChangeListener
setScreenshotMode(PreferencesUtil.isScreenshotModeEnabled(this))
}
private fun setScreenshotMode(isEnabled: Boolean) {
findViewById<View>(R.id.screenshot_mode_banner)?.visibility = if (isEnabled) VISIBLE else GONE
// Several gingerbread devices have problems with FLAG_SECURE // Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) if (isEnabled) {
window.clearFlags(FLAG_SECURE)
} else {
window.setFlags(FLAG_SECURE, FLAG_SECURE)
}
} }
override fun onResume() { override fun onResume() {
@@ -94,6 +123,7 @@ abstract class StylishActivity : AppCompatActivity() {
Log.d(this.javaClass.name, "Theme change detected, restarting activity") Log.d(this.javaClass.name, "Theme change detected, restarting activity")
recreateActivity() recreateActivity()
} }
setScreenshotMode(PreferencesUtil.isScreenshotModeEnabled(this))
} }
private fun recreateActivity() { private fun recreateActivity() {

View File

@@ -1,99 +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.stylish
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StyleRes
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
abstract class StylishFragment : Fragment() {
@StyleRes
protected var themeId: Int = 0
protected var contextThemed: Context? = null
override fun onAttach(context: Context) {
super.onAttach(context)
this.themeId = Stylish.getThemeId(context)
contextThemed = ContextThemeWrapper(context, themeId)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// To fix status bar color
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = requireActivity().window
val defaultColor = Color.BLACK
val windowInset = WindowInsetsControllerCompat(window, window.decorView)
try {
val taStatusBarColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.statusBarColor))
window.statusBarColor = taStatusBarColor?.getColor(0, defaultColor) ?: defaultColor
taStatusBarColor?.recycle()
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve theme : status bar color", e)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
val taWindowStatusLight = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightStatusBar))
windowInset.isAppearanceLightStatusBars = taWindowStatusLight
?.getBoolean(0, false) == true
taWindowStatusLight?.recycle()
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve theme : window light status bar", e)
}
}
try {
val taNavigationBarColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.navigationBarColor))
window.navigationBarColor = taNavigationBarColor?.getColor(0, defaultColor) ?: defaultColor
taNavigationBarColor?.recycle()
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve theme : navigation bar color", e)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
try {
val taWindowLightNavigationBar = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightNavigationBar))
windowInset.isAppearanceLightNavigationBars = taWindowLightNavigationBar
?.getBoolean(0, false) == true
taWindowLightNavigationBar?.recycle()
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve theme : navigation light navigation bar", e)
}
}
}
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onDetach() {
contextThemed = null
super.onDetach()
}
companion object {
private val TAG = StylishFragment::class.java.simpleName
}
}

View File

@@ -39,10 +39,10 @@ class BreadcrumbAdapter(val context: Context)
mShowNumberEntries = PreferencesUtil.showNumberEntries(context) mShowNumberEntries = PreferencesUtil.showNumberEntries(context)
mShowUUID = PreferencesUtil.showUUID(context) mShowUUID = PreferencesUtil.showUUID(context)
// Retrieve the textColor to tint the icon // Retrieve the color to tint the icon
val taTextColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse)) val taIconColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnSurface))
mIconColor = taTextColor.getColor(0, Color.WHITE) mIconColor = taIconColor.getColor(0, Color.WHITE)
taTextColor.recycle() taIconColor.recycle()
} }
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
@@ -71,7 +71,7 @@ class BreadcrumbAdapter(val context: Context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BreadcrumbGroupViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BreadcrumbGroupViewHolder {
return BreadcrumbGroupViewHolder(inflater.inflate( return BreadcrumbGroupViewHolder(inflater.inflate(
when (viewType) { when (viewType) {
0 -> R.layout.item_group 0 -> R.layout.item_breadcrumb_important
else -> R.layout.item_breadcrumb else -> R.layout.item_breadcrumb
}, parent, false) }, parent, false)
) )
@@ -112,8 +112,12 @@ class BreadcrumbAdapter(val context: Context)
holder.groupNumbersView?.apply { holder.groupNumbersView?.apply {
if (mShowNumberEntries) { if (mShowNumberEntries) {
group.refreshNumberOfChildEntries(Group.ChildFilter.getDefaults(context)) group.refreshNumberOfChildEntries(
text = group.numberOfChildEntries.toString() Group.ChildFilter.getDefaults(
PreferencesUtil.showExpiredEntries(context)
)
)
text = group.recursiveNumberOfChildEntries.toString()
visibility = View.VISIBLE visibility = View.VISIBLE
} else { } else {
visibility = View.GONE visibility = View.GONE

View File

@@ -27,16 +27,18 @@ import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.ImageViewerActivity import com.kunzisoft.keepass.activities.ImageViewerActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.model.AttachmentState 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.services.AttachmentFileNotificationService.Companion.FILE_PROGRESSION_MAX
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import com.kunzisoft.keepass.view.expand import com.kunzisoft.keepass.view.expand
import kotlin.math.max import kotlin.math.max
@@ -45,7 +47,7 @@ import kotlin.math.max
class EntryAttachmentsItemsAdapter(context: Context) class EntryAttachmentsItemsAdapter(context: Context)
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) { : AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
var database: Database? = null var database: ContextualDatabase? = null
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null
@@ -130,13 +132,14 @@ class EntryAttachmentsItemsAdapter(context: Context)
holder.binaryFileSize.text = Formatter.formatFileSize(context, size) holder.binaryFileSize.text = Formatter.formatFileSize(context, size)
holder.binaryFileCompression.apply { holder.binaryFileCompression.apply {
if (entryAttachmentState.attachment.binaryData.isCompressed) { if (entryAttachmentState.attachment.binaryData.isCompressed) {
text = CompressionAlgorithm.GZip.getName(context.resources) text = CompressionAlgorithm.GZIP.getLocalizedName(context.resources)
visibility = View.VISIBLE visibility = View.VISIBLE
} else { } else {
text = "" text = ""
visibility = View.GONE visibility = View.GONE
} }
} }
holder.binaryFileProgress.max = FILE_PROGRESSION_MAX
when (entryAttachmentState.streamDirection) { when (entryAttachmentState.streamDirection) {
StreamDirection.UPLOAD -> { StreamDirection.UPLOAD -> {
holder.binaryFileProgressIcon.isActivated = true holder.binaryFileProgressIcon.isActivated = true
@@ -181,7 +184,7 @@ class EntryAttachmentsItemsAdapter(context: Context)
AttachmentState.START, AttachmentState.START,
AttachmentState.IN_PROGRESS -> View.VISIBLE AttachmentState.IN_PROGRESS -> View.VISIBLE
} }
progress = entryAttachmentState.downloadProgression setProgressCompat(entryAttachmentState.downloadProgression, true)
} }
holder.binaryFileInfo.setOnClickListener { holder.binaryFileInfo.setOnClickListener {
onItemClickListener?.invoke(entryAttachmentState) onItemClickListener?.invoke(entryAttachmentState)
@@ -200,7 +203,7 @@ class EntryAttachmentsItemsAdapter(context: Context)
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression) var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
var binaryFileProgressContainer: View = itemView.findViewById(R.id.item_attachment_progress_container) var binaryFileProgressContainer: View = itemView.findViewById(R.id.item_attachment_progress_container)
var binaryFileProgressIcon: ImageView = itemView.findViewById(R.id.item_attachment_icon) var binaryFileProgressIcon: ImageView = itemView.findViewById(R.id.item_attachment_icon)
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress) var binaryFileProgress: CircularProgressIndicator = itemView.findViewById(R.id.item_attachment_progress)
var binaryFileDeleteButton: View = itemView.findViewById(R.id.item_attachment_delete_button) var binaryFileDeleteButton: View = itemView.findViewById(R.id.item_attachment_delete_button)
} }
} }

View File

@@ -27,6 +27,7 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString
class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() { class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() {

View File

@@ -23,12 +23,14 @@ import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.net.Uri import android.net.Uri
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.CompoundButton
import androidx.annotation.ColorInt import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import android.widget.ViewSwitcher
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback import androidx.recyclerview.widget.SortedListAdapterCallback
@@ -84,20 +86,6 @@ class FileDatabaseHistoryAdapter(context: Context)
} }
) )
@ColorInt
private val defaultColor: Int
@ColorInt
private val warningColor: Int
init {
val typedValue = TypedValue()
val theme = context.theme
theme.resolveAttribute(R.attr.colorAccent, typedValue, true)
warningColor = typedValue.data
theme.resolveAttribute(android.R.attr.textColorHintInverse, typedValue, true)
defaultColor = typedValue.data
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileDatabaseHistoryViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FileDatabaseHistoryViewHolder {
val view = inflater.inflate(R.layout.item_file_info, parent, false) val view = inflater.inflate(R.layout.item_file_info, parent, false)
return FileDatabaseHistoryViewHolder(view) return FileDatabaseHistoryViewHolder(view)

View File

@@ -20,21 +20,23 @@
package com.kunzisoft.keepass.adapters package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback import androidx.recyclerview.widget.SortedListAdapterCallback
import com.google.android.material.progressindicator.CircularProgressIndicator import com.google.android.material.progressindicator.CircularProgressIndicator
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
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.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
@@ -42,21 +44,23 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpType import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.view.setTextSize import com.kunzisoft.keepass.view.setTextSize
import com.kunzisoft.keepass.view.strikeOut import com.kunzisoft.keepass.view.strikeOut
import java.util.* import java.util.LinkedList
/** /**
* Create node list adapter with contextMenu or not * Create node list adapter with contextMenu or not
* @param context Context to use * @param context Context to use
*/ */
class NodesAdapter (private val context: Context, class NodesAdapter (
private val database: Database) private val context: Context,
: RecyclerView.Adapter<NodesAdapter.NodeViewHolder>() { private val database: ContextualDatabase
) : RecyclerView.Adapter<NodesAdapter.NodeViewHolder>() {
private var mNodeComparator: Comparator<NodeVersionedInterface<Group>>? = null private var mNodeComparator: Comparator<NodeVersionedInterface<Group>>? = null
private val mNodeSortedListCallback: NodeSortedListCallback private val mNodeSortedListCallback: NodeSortedListCallback
@@ -86,6 +90,8 @@ class NodesAdapter (private val context: Context,
private var mNodeClickCallback: NodeClickCallback? = null private var mNodeClickCallback: NodeClickCallback? = null
private var mClipboardHelper = ClipboardHelper(context) private var mClipboardHelper = ClipboardHelper(context)
@ColorInt
private val mColorSurfaceContainer: Int
@ColorInt @ColorInt
private val mTextColorPrimary: Int private val mTextColorPrimary: Int
@ColorInt @ColorInt
@@ -93,9 +99,9 @@ class NodesAdapter (private val context: Context,
@ColorInt @ColorInt
private val mTextColorSecondary: Int private val mTextColorSecondary: Int
@ColorInt @ColorInt
private val mColorAccentLight: Int private val mColorSecondary: Int
@ColorInt @ColorInt
private val mColorOnAccentColor: Int private val mColorOnSecondary: Int
/** /**
* Determine if the adapter contains or not any element * Determine if the adapter contains or not any element
@@ -112,26 +118,29 @@ class NodesAdapter (private val context: Context,
this.mNodeSortedListCallback = NodeSortedListCallback() this.mNodeSortedListCallback = NodeSortedListCallback()
this.mNodeSortedList = SortedList(Node::class.java, mNodeSortedListCallback) this.mNodeSortedList = SortedList(Node::class.java, mNodeSortedListCallback)
val taColorSurfaceContainer = context.obtainStyledAttributes(intArrayOf(R.attr.colorSurfaceContainer))
this.mColorSurfaceContainer = taColorSurfaceContainer.getColor(0, Color.BLACK)
taColorSurfaceContainer.recycle()
// Retrieve the color to tint the icon // Retrieve the color to tint the icon
val taTextColorPrimary = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary)) val taTextColorPrimary = context.obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary))
this.mTextColorPrimary = taTextColorPrimary.getColor(0, Color.BLACK) this.mTextColorPrimary = taTextColorPrimary.getColor(0, Color.BLACK)
taTextColorPrimary.recycle() taTextColorPrimary.recycle()
// To get text color // To get text color
val taTextColor = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor)) val taTextColor = context.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
this.mTextColor = taTextColor.getColor(0, Color.BLACK) this.mTextColor = taTextColor.getColor(0, Color.BLACK)
taTextColor.recycle() taTextColor.recycle()
// To get text color secondary // To get text color secondary
val taTextColorSecondary = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorSecondary)) val taTextColorSecondary = context.obtainStyledAttributes(intArrayOf(android.R.attr.textColorSecondary))
this.mTextColorSecondary = taTextColorSecondary.getColor(0, Color.BLACK) this.mTextColorSecondary = taTextColorSecondary.getColor(0, Color.BLACK)
taTextColorSecondary.recycle() taTextColorSecondary.recycle()
// To get background color for selection // To get background color for selection
val taColorAccentLight = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccentLight)) val taColorSecondary = context.obtainStyledAttributes(intArrayOf(R.attr.colorSecondary))
this.mColorAccentLight = taColorAccentLight.getColor(0, Color.GRAY) this.mColorSecondary = taColorSecondary.getColor(0, Color.GRAY)
taColorAccentLight.recycle() taColorSecondary.recycle()
// To get text color for selection // To get text color for selection
val taColorOnAccentColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnAccentColor)) val taColorOnSecondary = context.obtainStyledAttributes(intArrayOf(R.attr.colorOnSecondary))
this.mColorOnAccentColor = taColorOnAccentColor.getColor(0, Color.WHITE) this.mColorOnSecondary = taColorOnSecondary.getColor(0, Color.WHITE)
taColorOnAccentColor.recycle() taColorOnSecondary.recycle()
} }
private fun assignPreferences() { private fun assignPreferences() {
@@ -152,7 +161,9 @@ class NodesAdapter (private val context: Context,
this.mShowOTP = PreferencesUtil.showOTPToken(context) this.mShowOTP = PreferencesUtil.showOTPToken(context)
this.mShowUUID = PreferencesUtil.showUUID(context) this.mShowUUID = PreferencesUtil.showUUID(context)
this.mEntryFilters = Group.ChildFilter.getDefaults(context) this.mEntryFilters = Group.ChildFilter.getDefaults(
PreferencesUtil.showExpiredEntries(context)
)
// Reinit textSize for all view type // Reinit textSize for all view type
mCalculateViewTypeTextSize.forEachIndexed { index, _ -> mCalculateViewTypeTextSize[index] = true } mCalculateViewTypeTextSize.forEachIndexed { index, _ -> mCalculateViewTypeTextSize[index] = true }
@@ -186,6 +197,7 @@ class NodesAdapter (private val context: Context,
&& oldItem.containsAttachment() == newItem.containsAttachment() && oldItem.containsAttachment() == newItem.containsAttachment()
} else if (oldItem is Group && newItem is Group) { } else if (oldItem is Group && newItem is Group) {
typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries
&& oldItem.recursiveNumberOfChildEntries == newItem.recursiveNumberOfChildEntries
&& oldItem.notes == newItem.notes && oldItem.notes == newItem.notes
} }
return typeContentTheSame return typeContentTheSame
@@ -376,10 +388,10 @@ class NodesAdapter (private val context: Context,
// Assign icon colors // Assign icon colors
var iconColor = if (holder.container.isSelected) var iconColor = if (holder.container.isSelected)
mColorOnAccentColor mColorOnSecondary
else when (subNode.type) { else when (subNode.type) {
Type.GROUP -> mTextColorPrimary Type.GROUP -> mTextColor
Type.ENTRY -> mTextColor Type.ENTRY -> mColorSecondary
} }
// Specific elements for entry // Specific elements for entry
@@ -424,16 +436,8 @@ class NodesAdapter (private val context: Context,
if (entry.containsAttachment()) View.VISIBLE else View.GONE if (entry.containsAttachment()) View.VISIBLE else View.GONE
// Assign colors // Assign colors
val backgroundColor = if (mShowEntryColors) entry.backgroundColor else null assignBackgroundColor(holder.container, entry)
if (!holder.container.isSelected) { assignBackgroundColor(holder.otpContainer, entry)
if (backgroundColor != null) {
holder.container.setBackgroundColor(backgroundColor)
} else {
holder.container.setBackgroundColor(Color.TRANSPARENT)
}
} else {
holder.container.setBackgroundColor(mColorAccentLight)
}
val foregroundColor = if (mShowEntryColors) entry.foregroundColor else null val foregroundColor = if (mShowEntryColors) entry.foregroundColor else null
if (!holder.container.isSelected) { if (!holder.container.isSelected) {
if (foregroundColor != null) { if (foregroundColor != null) {
@@ -453,12 +457,12 @@ class NodesAdapter (private val context: Context,
holder.meta.setTextColor(mTextColor) holder.meta.setTextColor(mTextColor)
} }
} else { } else {
holder.text.setTextColor(mColorOnAccentColor) holder.text.setTextColor(mColorOnSecondary)
holder.subText?.setTextColor(mColorOnAccentColor) holder.subText?.setTextColor(mColorOnSecondary)
holder.otpToken?.setTextColor(mColorOnAccentColor) holder.otpToken?.setTextColor(mColorOnSecondary)
holder.otpProgress?.setIndicatorColor(mColorOnAccentColor) holder.otpProgress?.setIndicatorColor(mColorOnSecondary)
holder.attachmentIcon?.setColorFilter(mColorOnAccentColor) holder.attachmentIcon?.setColorFilter(mColorOnSecondary)
holder.meta.setTextColor(mColorOnAccentColor) holder.meta.setTextColor(mColorOnSecondary)
} }
database.stopManageEntry(entry) database.stopManageEntry(entry)
@@ -469,7 +473,7 @@ class NodesAdapter (private val context: Context,
if (mShowNumberEntries) { if (mShowNumberEntries) {
holder.numberChildren?.apply { holder.numberChildren?.apply {
text = (subNode as Group) text = (subNode as Group)
.numberOfChildEntries .recursiveNumberOfChildEntries
.toString() .toString()
setTextSize(mTextSizeUnit, mNumberChildrenTextDefaultDimension, mPrefSizeMultiplier) setTextSize(mTextSizeUnit, mNumberChildrenTextDefaultDimension, mPrefSizeMultiplier)
visibility = View.VISIBLE visibility = View.VISIBLE
@@ -521,17 +525,35 @@ class NodesAdapter (private val context: Context,
} }
holder?.otpContainer?.setOnClickListener { holder?.otpContainer?.setOnClickListener {
otpElement?.token?.let { token -> otpElement?.token?.let { token ->
Toast.makeText( try {
context, mClipboardHelper.copyToClipboard(
context.getString(R.string.copy_field, TemplateField.getLocalizedName(context, TemplateField.LABEL_TOKEN),
TemplateField.getLocalizedName(context, TemplateField.LABEL_TOKEN)), token,
Toast.LENGTH_LONG true
).show() )
mClipboardHelper.copyToClipboard(token) } catch (e: Exception) {
Log.e(TAG, "Unable to copy the OTP token", e)
}
} }
} }
} }
private fun assignBackgroundColor(view: View?, entry: Entry) {
view?.let {
ViewCompat.setBackgroundTintList(
view,
ColorStateList.valueOf(
if (!view.isSelected) {
(if (mShowEntryColors) entry.backgroundColor else null)
?: mColorSurfaceContainer
} else {
mColorSecondary
}
)
)
}
}
class OtpRunnable(val view: View?): Runnable { class OtpRunnable(val view: View?): Runnable {
var action: (() -> Unit)? = null var action: (() -> Unit)? = null
@@ -561,8 +583,8 @@ class NodesAdapter (private val context: Context,
* Callback listener to redefine to do an action when a node is click * Callback listener to redefine to do an action when a node is click
*/ */
interface NodeClickCallback { interface NodeClickCallback {
fun onNodeClick(database: Database, node: Node) fun onNodeClick(database: ContextualDatabase, node: Node)
fun onNodeLongClick(database: Database, node: Node): Boolean fun onNodeLongClick(database: ContextualDatabase, node: Node): Boolean
} }
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

View File

@@ -11,11 +11,12 @@ import android.widget.TextView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.template.Template import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.icons.IconDrawableFactory import com.kunzisoft.keepass.icons.IconDrawableFactory
class TemplatesSelectorAdapter( class TemplatesSelectorAdapter(
context: Context, private val context: Context,
private var templates: List<Template>): BaseAdapter() { private var templates: List<Template>): BaseAdapter() {
var iconDrawableFactory: IconDrawableFactory? = null var iconDrawableFactory: IconDrawableFactory? = null
@@ -35,7 +36,9 @@ class TemplatesSelectorAdapter(
var templateView = convertView var templateView = convertView
if (templateView == null) { if (templateView == null) {
holder = TemplateSelectorViewHolder() holder = TemplateSelectorViewHolder()
templateView = inflater.inflate(R.layout.item_template, parent, false) templateView = inflater
.cloneInContext(context)
.inflate(R.layout.item_template, parent, false)
holder.background = templateView?.findViewById(R.id.template_background) holder.background = templateView?.findViewById(R.id.template_background)
holder.icon = templateView?.findViewById(R.id.template_image) holder.icon = templateView?.findViewById(R.id.template_image)
holder.name = templateView?.findViewById(R.id.template_name) holder.name = templateView?.findViewById(R.id.template_name)
@@ -74,4 +77,4 @@ class TemplatesSelectorAdapter(
var icon: ImageView? = null var icon: ImageView? = null
var name: TextView? = null var name: TextView? = null
} }
} }

View File

@@ -23,8 +23,15 @@ import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import android.content.Context import android.content.Context
import androidx.room.AutoMigration
@Database(version = 1, entities = [FileDatabaseHistoryEntity::class, CipherDatabaseEntity::class]) @Database(
version = 2,
entities = [FileDatabaseHistoryEntity::class, CipherDatabaseEntity::class],
autoMigrations = [
AutoMigration (from = 1, to = 2)
]
)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun fileDatabaseHistoryDao(): FileDatabaseHistoryDao abstract fun fileDatabaseHistoryDao(): FileDatabaseHistoryDao

View File

@@ -19,24 +19,27 @@
*/ */
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
import android.content.* import android.content.ComponentName
import android.content.Context
import android.content.IntentFilter
import android.content.ServiceConnection
import android.net.Uri import android.net.Uri
import android.os.IBinder import android.os.IBinder
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.database.element.binary.BinaryData.Companion.BASE64_FLAG
import com.kunzisoft.keepass.model.CipherEncryptDatabase import com.kunzisoft.keepass.model.CipherEncryptDatabase
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.IOActionTask
import com.kunzisoft.keepass.utils.SingletonHolderParameter import com.kunzisoft.keepass.utils.SingletonHolderParameter
import java.util.* import java.util.LinkedList
class CipherDatabaseAction(context: Context) { class CipherDatabaseAction(context: Context) {
private val applicationContext = context.applicationContext private val applicationContext = context.applicationContext
private val cipherDatabaseDao = private val cipherDatabaseDao =
AppDatabase AppDatabase.getDatabase(applicationContext).cipherDatabaseDao()
.getDatabase(applicationContext)
.cipherDatabaseDao()
// Temp DAO to easily remove content if object no longer in memory // Temp DAO to easily remove content if object no longer in memory
private var useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext) private var useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
@@ -83,7 +86,7 @@ class CipherDatabaseAction(context: Context) {
try { try {
AdvancedUnlockNotificationService.bindService(applicationContext, AdvancedUnlockNotificationService.bindService(applicationContext,
mServiceConnection!!, mServiceConnection!!,
Context.BIND_AUTO_CREATE) Context.BIND_AUTO_CREATE)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to start cipher action", e) Log.e(TAG, "Unable to start cipher action", e)
performedAction.invoke() performedAction.invoke()
@@ -136,11 +139,11 @@ class CipherDatabaseAction(context: Context) {
this.databaseUri = Uri.parse(cipherDatabaseEntity.databaseUri) this.databaseUri = Uri.parse(cipherDatabaseEntity.databaseUri)
this.encryptedValue = Base64.decode( this.encryptedValue = Base64.decode(
cipherDatabaseEntity.encryptedValue, cipherDatabaseEntity.encryptedValue,
Base64.NO_WRAP BASE64_FLAG
) )
this.specParameters = Base64.decode( this.specParameters = Base64.decode(
cipherDatabaseEntity.specParameters, cipherDatabaseEntity.specParameters,
Base64.NO_WRAP BASE64_FLAG
) )
} }
} }
@@ -148,8 +151,9 @@ class CipherDatabaseAction(context: Context) {
} }
} else { } else {
IOActionTask( IOActionTask(
{ {
cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())?.let { cipherDatabaseEntity -> cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
?.let { cipherDatabaseEntity ->
CipherEncryptDatabase().apply { CipherEncryptDatabase().apply {
this.databaseUri = Uri.parse(cipherDatabaseEntity.databaseUri) this.databaseUri = Uri.parse(cipherDatabaseEntity.databaseUri)
this.encryptedValue = Base64.decode( this.encryptedValue = Base64.decode(
@@ -162,10 +166,10 @@ class CipherDatabaseAction(context: Context) {
) )
} }
} }
}, },
{ {
cipherDatabaseResultListener.invoke(it) cipherDatabaseResultListener.invoke(it)
} }
).execute() ).execute()
} }
} }
@@ -183,8 +187,8 @@ class CipherDatabaseAction(context: Context) {
val cipherDatabaseEntity = CipherDatabaseEntity( val cipherDatabaseEntity = CipherDatabaseEntity(
databaseUri.toString(), databaseUri.toString(),
Base64.encodeToString(cipherEncryptDatabase.encryptedValue, Base64.NO_WRAP), Base64.encodeToString(cipherEncryptDatabase.encryptedValue, BASE64_FLAG),
Base64.encodeToString(cipherEncryptDatabase.specParameters, Base64.NO_WRAP), Base64.encodeToString(cipherEncryptDatabase.specParameters, BASE64_FLAG),
) )
if (useTempDao) { if (useTempDao) {
@@ -222,12 +226,12 @@ class CipherDatabaseAction(context: Context) {
} }
} else { } else {
IOActionTask( IOActionTask(
{ {
cipherDatabaseDao.deleteByDatabaseUri(databaseUri.toString()) cipherDatabaseDao.deleteByDatabaseUri(databaseUri.toString())
}, },
{ {
cipherDatabaseResultListener?.invoke() cipherDatabaseResultListener?.invoke()
} }
).execute() ).execute()
} }
} }
@@ -240,9 +244,9 @@ class CipherDatabaseAction(context: Context) {
} }
// To erase the residues // To erase the residues
IOActionTask( IOActionTask(
{ {
cipherDatabaseDao.deleteAll() cipherDatabaseDao.deleteAll()
} }
).execute() ).execute()
// Unbind // Unbind
removeAllDataAndDetach() removeAllDataAndDetach()
@@ -251,4 +255,4 @@ class CipherDatabaseAction(context: Context) {
companion object : SingletonHolderParameter<CipherDatabaseAction, Context>(::CipherDatabaseAction) { companion object : SingletonHolderParameter<CipherDatabaseAction, Context>(::CipherDatabaseAction) {
private val TAG = CipherDatabaseAction::class.java.name private val TAG = CipherDatabaseAction::class.java.name
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
* KeePassDX is free software: you can redistribute it and/or modify * KeePassDX is free software: you can redistribute it and/or modify
@@ -22,204 +22,225 @@ package com.kunzisoft.keepass.app.database
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.DatabaseFile import com.kunzisoft.keepass.model.DatabaseFile
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.utils.SingletonHolderParameter import com.kunzisoft.keepass.utils.SingletonHolderParameter
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.decodeUri
import com.kunzisoft.keepass.utils.parseUri
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
class FileDatabaseHistoryAction(private val applicationContext: Context) { class FileDatabaseHistoryAction(private val applicationContext: Context) {
private val databaseFileHistoryDao = private val databaseFileHistoryDao =
AppDatabase AppDatabase.getDatabase(applicationContext).fileDatabaseHistoryDao()
.getDatabase(applicationContext)
.fileDatabaseHistoryDao()
fun getDatabaseFile(databaseUri: Uri, fun getDatabaseFile(databaseUri: Uri,
databaseFileResult: (DatabaseFile?) -> Unit) { databaseFileResult: (DatabaseFile?) -> Unit) {
IOActionTask( IOActionTask(
{ {
val fileDatabaseHistoryEntity = databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString()) val fileDatabaseHistoryEntity =
val fileDatabaseInfo = FileDatabaseInfo(applicationContext, databaseUri) databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())
DatabaseFile( val fileDatabaseInfo = FileDatabaseInfo(
databaseUri, applicationContext,
UriUtil.parse(fileDatabaseHistoryEntity?.keyFileUri), databaseUri)
UriUtil.decode(fileDatabaseHistoryEntity?.databaseUri), DatabaseFile(
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias ?: ""), databaseUri,
fileDatabaseInfo.exists, fileDatabaseHistoryEntity?.keyFileUri?.parseUri(),
fileDatabaseInfo.getLastModificationString(), HardwareKey.getHardwareKeyFromString(fileDatabaseHistoryEntity?.hardwareKey),
fileDatabaseInfo.getSizeString() fileDatabaseHistoryEntity?.databaseUri?.decodeUri(),
) fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity?.databaseAlias
}, ?: ""),
{ fileDatabaseInfo.exists,
databaseFileResult.invoke(it) fileDatabaseInfo.getLastModificationString(),
} fileDatabaseInfo.getSizeString()
)
},
{
databaseFileResult.invoke(it)
}
).execute() ).execute()
} }
fun getKeyFileUriByDatabaseUri(databaseUri: Uri, fun getKeyFileUriByDatabaseUri(databaseUri: Uri,
keyFileUriResultListener: (Uri?) -> Unit) { keyFileUriResultListener: (Uri?) -> Unit) {
IOActionTask( IOActionTask(
{ {
databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString()) databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())
}, },
{ {
it?.let { fileHistoryEntity -> it?.let { fileHistoryEntity ->
fileHistoryEntity.keyFileUri?.let { keyFileUri -> fileHistoryEntity.keyFileUri?.let { keyFileUri ->
keyFileUriResultListener.invoke(UriUtil.parse(keyFileUri)) keyFileUriResultListener.invoke(keyFileUri.parseUri())
} }
} ?: keyFileUriResultListener.invoke(null) } ?: keyFileUriResultListener.invoke(null)
} }
).execute() ).execute()
} }
fun getDatabaseFileList(databaseFileListResult: (List<DatabaseFile>) -> Unit) { fun getDatabaseFileList(databaseFileListResult: (List<DatabaseFile>) -> Unit) {
IOActionTask( IOActionTask(
{ {
val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(applicationContext) val hideBrokenLocations =
// Show only uri accessible PreferencesUtil.hideBrokenLocations(
val databaseFileListLoaded = ArrayList<DatabaseFile>() applicationContext)
databaseFileHistoryDao.getAll().forEach { fileDatabaseHistoryEntity -> // Show only uri accessible
val fileDatabaseInfo = FileDatabaseInfo(applicationContext, fileDatabaseHistoryEntity.databaseUri) val databaseFileListLoaded = ArrayList<DatabaseFile>()
if (hideBrokenLocations && fileDatabaseInfo.exists databaseFileHistoryDao.getAll().forEach { fileDatabaseHistoryEntity ->
|| !hideBrokenLocations) { val fileDatabaseInfo = FileDatabaseInfo(
databaseFileListLoaded.add( applicationContext,
DatabaseFile( fileDatabaseHistoryEntity.databaseUri)
UriUtil.parse(fileDatabaseHistoryEntity.databaseUri), if (hideBrokenLocations && fileDatabaseInfo.exists
UriUtil.parse(fileDatabaseHistoryEntity.keyFileUri), || !hideBrokenLocations
UriUtil.decode(fileDatabaseHistoryEntity.databaseUri), ) {
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias), databaseFileListLoaded.add(
fileDatabaseInfo.exists, DatabaseFile(
fileDatabaseInfo.getLastModificationString(), fileDatabaseHistoryEntity.databaseUri.parseUri(),
fileDatabaseInfo.getSizeString() fileDatabaseHistoryEntity.keyFileUri?.parseUri(),
) HardwareKey.getHardwareKeyFromString(fileDatabaseHistoryEntity.hardwareKey),
fileDatabaseHistoryEntity.databaseUri.decodeUri(),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistoryEntity.databaseAlias),
fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
) )
} )
}
databaseFileListLoaded
},
{
databaseFileList ->
databaseFileList?.let {
databaseFileListResult.invoke(it)
} }
} }
databaseFileListLoaded
},
{ databaseFileList ->
databaseFileList?.let {
databaseFileListResult.invoke(it)
}
}
).execute() ).execute()
} }
fun addOrUpdateDatabaseUri(databaseUri: Uri, keyFileUri: Uri? = null, fun addOrUpdateDatabaseUri(databaseUri: Uri,
keyFileUri: Uri? = null,
hardwareKey: HardwareKey? = null,
databaseFileAddedOrUpdatedResult: ((DatabaseFile?) -> Unit)? = null) { databaseFileAddedOrUpdatedResult: ((DatabaseFile?) -> Unit)? = null) {
addOrUpdateDatabaseFile(DatabaseFile( addOrUpdateDatabaseFile(DatabaseFile(
databaseUri, databaseUri,
keyFileUri keyFileUri,
hardwareKey
), databaseFileAddedOrUpdatedResult) ), databaseFileAddedOrUpdatedResult)
} }
fun addOrUpdateDatabaseFile(databaseFileToAddOrUpdate: DatabaseFile, fun addOrUpdateDatabaseFile(databaseFileToAddOrUpdate: DatabaseFile,
databaseFileAddedOrUpdatedResult: ((DatabaseFile?) -> Unit)? = null) { databaseFileAddedOrUpdatedResult: ((DatabaseFile?) -> Unit)? = null) {
IOActionTask( IOActionTask(
{ {
databaseFileToAddOrUpdate.databaseUri?.let { databaseUri -> databaseFileToAddOrUpdate.databaseUri?.let { databaseUri ->
// Try to get info in database first // Try to get info in database first
val fileDatabaseHistoryRetrieve = databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString()) val fileDatabaseHistoryRetrieve =
databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())
// Complete alias if not exists // Complete alias if not exists
val fileDatabaseHistory = FileDatabaseHistoryEntity( val fileDatabaseHistory =
databaseUri.toString(), FileDatabaseHistoryEntity(
databaseFileToAddOrUpdate.databaseAlias databaseUri.toString(),
?: fileDatabaseHistoryRetrieve?.databaseAlias databaseFileToAddOrUpdate.databaseAlias
?: "", ?: fileDatabaseHistoryRetrieve?.databaseAlias
databaseFileToAddOrUpdate.keyFileUri?.toString(), ?: "",
System.currentTimeMillis() databaseFileToAddOrUpdate.keyFileUri?.toString(),
databaseFileToAddOrUpdate.hardwareKey?.value,
System.currentTimeMillis()
) )
// Update values if history element not yet in the database // Update values if history element not yet in the database
try { try {
if (fileDatabaseHistoryRetrieve == null) { if (fileDatabaseHistoryRetrieve == null) {
databaseFileHistoryDao.add(fileDatabaseHistory) databaseFileHistoryDao.add(fileDatabaseHistory)
} else { } else {
databaseFileHistoryDao.update(fileDatabaseHistory) databaseFileHistoryDao.update(fileDatabaseHistory)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to add or update database history", e)
} }
} catch (e: Exception) {
val fileDatabaseInfo = FileDatabaseInfo(applicationContext, Log.e(TAG, "Unable to add or update database history", e)
fileDatabaseHistory.databaseUri)
DatabaseFile(
UriUtil.parse(fileDatabaseHistory.databaseUri),
UriUtil.parse(fileDatabaseHistory.keyFileUri),
UriUtil.decode(fileDatabaseHistory.databaseUri),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
} }
},
{ val fileDatabaseInfo =
databaseFileAddedOrUpdatedResult?.invoke(it) FileDatabaseInfo(applicationContext,
fileDatabaseHistory.databaseUri)
DatabaseFile(
fileDatabaseHistory.databaseUri.parseUri(),
fileDatabaseHistory.keyFileUri?.parseUri(),
HardwareKey.getHardwareKeyFromString(fileDatabaseHistory.hardwareKey),
fileDatabaseHistory.databaseUri.decodeUri(),
fileDatabaseInfo.retrieveDatabaseAlias(fileDatabaseHistory.databaseAlias),
fileDatabaseInfo.exists,
fileDatabaseInfo.getLastModificationString(),
fileDatabaseInfo.getSizeString()
)
} }
},
{
databaseFileAddedOrUpdatedResult?.invoke(it)
}
).execute() ).execute()
} }
fun deleteDatabaseFile(databaseFileToDelete: DatabaseFile, fun deleteDatabaseFile(databaseFileToDelete: DatabaseFile,
databaseFileDeletedResult: (DatabaseFile?) -> Unit) { databaseFileDeletedResult: (DatabaseFile?) -> Unit) {
IOActionTask( IOActionTask(
{ {
databaseFileToDelete.databaseUri?.let { databaseUri -> databaseFileToDelete.databaseUri?.let { databaseUri ->
databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())?.let { fileDatabaseHistory -> databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())
?.let { fileDatabaseHistory ->
val returnValue = databaseFileHistoryDao.delete(fileDatabaseHistory) val returnValue = databaseFileHistoryDao.delete(fileDatabaseHistory)
if (returnValue > 0) { if (returnValue > 0) {
DatabaseFile( DatabaseFile(
UriUtil.parse(fileDatabaseHistory.databaseUri), fileDatabaseHistory.databaseUri.parseUri(),
UriUtil.parse(fileDatabaseHistory.keyFileUri), fileDatabaseHistory.keyFileUri?.parseUri(),
UriUtil.decode(fileDatabaseHistory.databaseUri), HardwareKey.getHardwareKeyFromString(fileDatabaseHistory.hardwareKey),
databaseFileToDelete.databaseAlias fileDatabaseHistory.databaseUri.decodeUri(),
databaseFileToDelete.databaseAlias
) )
} else { } else {
null null
} }
} }
}
},
{
databaseFileDeletedResult.invoke(it)
} }
},
{
databaseFileDeletedResult.invoke(it)
}
).execute() ).execute()
} }
fun deleteKeyFileByDatabaseUri(databaseUri: Uri, fun deleteKeyFileByDatabaseUri(databaseUri: Uri,
result: (() ->Unit)? = null) { result: (() ->Unit)? = null) {
IOActionTask( IOActionTask(
{ {
databaseFileHistoryDao.deleteKeyFileByDatabaseUri(databaseUri.toString()) databaseFileHistoryDao.deleteKeyFileByDatabaseUri(databaseUri.toString())
}, },
{ {
result?.invoke() result?.invoke()
} }
).execute() ).execute()
} }
fun deleteAllKeyFiles(result: (() ->Unit)? = null) { fun deleteAllKeyFiles(result: (() ->Unit)? = null) {
IOActionTask( IOActionTask(
{ {
databaseFileHistoryDao.deleteAllKeyFiles() databaseFileHistoryDao.deleteAllKeyFiles()
}, },
{ {
result?.invoke() result?.invoke()
} }
).execute() ).execute()
} }
fun deleteAll(result: (() ->Unit)? = null) { fun deleteAll(result: (() ->Unit)? = null) {
IOActionTask( IOActionTask(
{ {
databaseFileHistoryDao.deleteAll() databaseFileHistoryDao.deleteAll()
}, },
{ {
result?.invoke() result?.invoke()
} }
).execute() ).execute()
} }

View File

@@ -35,6 +35,9 @@ data class FileDatabaseHistoryEntity(
@ColumnInfo(name = "keyfile_uri") @ColumnInfo(name = "keyfile_uri")
var keyFileUri: String?, var keyFileUri: String?,
@ColumnInfo(name = "hardware_key")
var hardwareKey: String?,
@ColumnInfo(name = "updated") @ColumnInfo(name = "updated")
val updated: Long val updated: Long
) { ) {

View File

@@ -1,7 +1,6 @@
package com.kunzisoft.keepass.autofill package com.kunzisoft.keepass.autofill
import android.app.assist.AssistStructure import android.app.assist.AssistStructure
import android.view.inputmethod.InlineSuggestionsRequest
data class AutofillComponent(val assistStructure: AssistStructure, data class AutofillComponent(val assistStructure: AssistStructure,
val compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?) val compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?)

View File

@@ -29,9 +29,12 @@ import android.graphics.BlendMode
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import android.service.autofill.Dataset import android.service.autofill.Dataset
import android.service.autofill.Field
import android.service.autofill.FillResponse import android.service.autofill.FillResponse
import android.service.autofill.InlinePresentation import android.service.autofill.InlinePresentation
import android.service.autofill.Presentations
import android.util.Log import android.util.Log
import android.view.autofill.AutofillId
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue import android.view.autofill.AutofillValue
import android.widget.RemoteViews import android.widget.RemoteViews
@@ -48,7 +51,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AutofillLauncherActivity import com.kunzisoft.keepass.activities.AutofillLauncherActivity
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
@@ -56,6 +59,7 @@ import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.AutofillSettingsActivity import com.kunzisoft.keepass.settings.AutofillSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.LOCK_ACTION import com.kunzisoft.keepass.utils.LOCK_ACTION
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
@@ -65,10 +69,10 @@ object AutofillHelper {
private const val EXTRA_INLINE_SUGGESTIONS_REQUEST = "com.kunzisoft.keepass.autofill.INLINE_SUGGESTIONS_REQUEST" private const val EXTRA_INLINE_SUGGESTIONS_REQUEST = "com.kunzisoft.keepass.autofill.INLINE_SUGGESTIONS_REQUEST"
fun retrieveAutofillComponent(intent: Intent?): AutofillComponent? { fun retrieveAutofillComponent(intent: Intent?): AutofillComponent? {
intent?.getParcelableExtra<AssistStructure?>(EXTRA_ASSIST_STRUCTURE)?.let { assistStructure -> intent?.getParcelableExtraCompat<AssistStructure>(EXTRA_ASSIST_STRUCTURE)?.let { assistStructure ->
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AutofillComponent(assistStructure, AutofillComponent(assistStructure,
intent.getParcelableExtra(EXTRA_INLINE_SUGGESTIONS_REQUEST)) intent.getParcelableExtraCompat(EXTRA_INLINE_SUGGESTIONS_REQUEST))
} else { } else {
AutofillComponent(assistStructure, null) AutofillComponent(assistStructure, null)
} }
@@ -89,39 +93,85 @@ object AutofillHelper {
} }
private fun newRemoteViews(context: Context, private fun newRemoteViews(context: Context,
database: Database, database: ContextualDatabase,
remoteViewsText: String, remoteViewsText: String,
remoteViewsIcon: IconImage? = null): RemoteViews { remoteViewsIcon: IconImage? = null): RemoteViews {
val presentation = RemoteViews(context.packageName, R.layout.item_autofill_entry) val remoteViews = RemoteViews(context.packageName, R.layout.item_autofill_entry)
presentation.setTextViewText(R.id.autofill_entry_text, remoteViewsText) remoteViews.setTextViewText(R.id.autofill_entry_text, remoteViewsText)
if (remoteViewsIcon != null) { if (remoteViewsIcon != null) {
try { try {
database.iconDrawableFactory.getBitmapFromIcon(context, database.iconDrawableFactory.getBitmapFromIcon(context,
remoteViewsIcon, ContextCompat.getColor(context, R.color.green))?.let { bitmap -> remoteViewsIcon, ContextCompat.getColor(context, R.color.green))?.let { bitmap ->
presentation.setImageViewBitmap(R.id.autofill_entry_icon, bitmap) remoteViews.setImageViewBitmap(R.id.autofill_entry_icon, bitmap)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e) Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e)
} }
} }
return presentation return remoteViews
} }
private fun buildDataset(context: Context, private fun Dataset.Builder.addValueToDatasetBuilder(
database: Database, id: AutofillId,
entryInfo: EntryInfo, autofillValue: AutofillValue?
struct: StructureParser.Result, ): Dataset.Builder {
additionalBuild: ((build: Dataset.Builder) -> Unit)? = null): Dataset? { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val title = makeEntryTitle(entryInfo) setField(
val views = newRemoteViews(context, database, title, entryInfo.icon) id, autofillValue?.let {
val builder = Dataset.Builder(views) Field.Builder()
builder.setId(entryInfo.id.toString()) .setValue(it)
.build()
}
)
} else {
@Suppress("DEPRECATION")
setValue(id, autofillValue)
}
Log.d(TAG, "Set Autofill value $autofillValue for id $id")
return this
}
private fun buildDatasetForEntry(context: Context,
database: ContextualDatabase,
entryInfo: EntryInfo,
struct: StructureParser.Result,
inlinePresentation: InlinePresentation?): Dataset {
val remoteViews: RemoteViews = newRemoteViews(context, database, makeEntryTitle(entryInfo), entryInfo.icon)
val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Dataset.Builder(Presentations.Builder()
.apply {
inlinePresentation?.let {
setInlinePresentation(inlinePresentation)
}
}
.setDialogPresentation(remoteViews)
.setMenuPresentation(remoteViews)
.build())
} else {
@Suppress("DEPRECATION")
Dataset.Builder(remoteViews).apply {
inlinePresentation?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
setInlinePresentation(inlinePresentation)
}
}
}
}
datasetBuilder.setId(entryInfo.id.toString())
struct.usernameId?.let { usernameId -> struct.usernameId?.let { usernameId ->
builder.setValue(usernameId, AutofillValue.forText(entryInfo.username)) datasetBuilder.addValueToDatasetBuilder(
usernameId,
AutofillValue.forText(entryInfo.username)
)
} }
struct.passwordId?.let { passwordId -> struct.passwordId?.let { passwordId ->
builder.setValue(passwordId, AutofillValue.forText(entryInfo.password)) datasetBuilder.addValueToDatasetBuilder(
passwordId,
AutofillValue.forText(entryInfo.password)
)
} }
if (entryInfo.expires) { if (entryInfo.expires) {
@@ -134,9 +184,15 @@ object AutofillHelper {
struct.creditCardExpirationDateId?.let { struct.creditCardExpirationDateId?.let {
if (struct.isWebView) { if (struct.isWebView) {
// set date string as defined in https://html.spec.whatwg.org // set date string as defined in https://html.spec.whatwg.org
builder.setValue(it, AutofillValue.forText("$year\u002D$monthString")) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText("$year\u002D$monthString")
)
} else { } else {
builder.setValue(it, AutofillValue.forDate(entryInfo.expiryTime.date.time)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forDate(entryInfo.expiryTime.date.time)
)
} }
} }
struct.creditCardExpirationYearId?.let { struct.creditCardExpirationYearId?.let {
@@ -150,34 +206,58 @@ object AutofillHelper {
} }
if (yearIndex != -1) { if (yearIndex != -1) {
autofillValue = AutofillValue.forList(yearIndex) autofillValue = AutofillValue.forList(yearIndex)
builder.setValue(it, autofillValue) datasetBuilder.addValueToDatasetBuilder(
it,
autofillValue
)
} }
} }
if (autofillValue == null) { if (autofillValue == null) {
builder.setValue(it, AutofillValue.forText(year.toString())) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(year.toString())
)
} }
} }
struct.creditCardExpirationMonthId?.let { struct.creditCardExpirationMonthId?.let {
if (struct.isWebView) { if (struct.isWebView) {
builder.setValue(it, AutofillValue.forText(monthString)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(monthString)
)
} else { } else {
if (struct.creditCardExpirationMonthOptions != null) { if (struct.creditCardExpirationMonthOptions != null) {
// index starts at 0 // index starts at 0
builder.setValue(it, AutofillValue.forList(month - 1)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forList(month - 1)
)
} else { } else {
builder.setValue(it, AutofillValue.forText(monthString)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(monthString)
)
} }
} }
} }
struct.creditCardExpirationDayId?.let { struct.creditCardExpirationDayId?.let {
if (struct.isWebView) { if (struct.isWebView) {
builder.setValue(it, AutofillValue.forText(dayString)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(dayString)
)
} else { } else {
if (struct.creditCardExpirationDayOptions != null) { if (struct.creditCardExpirationDayOptions != null) {
builder.setValue(it, AutofillValue.forList(day - 1)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forList(day - 1)
)
} else { } else {
builder.setValue(it, AutofillValue.forText(dayString)) datasetBuilder.addValueToDatasetBuilder(
it,
AutofillValue.forText(dayString)
)
} }
} }
} }
@@ -185,36 +265,39 @@ object AutofillHelper {
for (field in entryInfo.customFields) { for (field in entryInfo.customFields) {
if (field.name == TemplateField.LABEL_HOLDER) { if (field.name == TemplateField.LABEL_HOLDER) {
struct.creditCardHolderId?.let { ccNameId -> struct.creditCardHolderId?.let { ccNameId ->
builder.setValue(ccNameId, AutofillValue.forText(field.protectedValue.stringValue)) datasetBuilder.addValueToDatasetBuilder(
ccNameId,
AutofillValue.forText(field.protectedValue.stringValue)
)
} }
} }
if (field.name == TemplateField.LABEL_NUMBER) { if (field.name == TemplateField.LABEL_NUMBER) {
struct.creditCardNumberId?.let { ccnId -> struct.creditCardNumberId?.let { ccnId ->
builder.setValue(ccnId, AutofillValue.forText(field.protectedValue.stringValue)) datasetBuilder.addValueToDatasetBuilder(
ccnId,
AutofillValue.forText(field.protectedValue.stringValue)
)
} }
} }
if (field.name == TemplateField.LABEL_CVV) { if (field.name == TemplateField.LABEL_CVV) {
struct.cardVerificationValueId?.let { cvvId -> struct.cardVerificationValueId?.let { cvvId ->
builder.setValue(cvvId, AutofillValue.forText(field.protectedValue.stringValue)) datasetBuilder.addValueToDatasetBuilder(
cvvId,
AutofillValue.forText(field.protectedValue.stringValue)
)
} }
} }
} }
val dataset = datasetBuilder.build()
additionalBuild?.invoke(builder) Log.d(TAG, "Autofill Dataset $dataset created")
return dataset
return try {
builder.build()
} catch (e: Exception) {
// at least one value must be set
null
}
} }
/** /**
* Method to assign a drawable to a new icon from a database icon * Method to assign a drawable to a new icon from a database icon
*/ */
private fun buildIconFromEntry(context: Context, private fun buildIconFromEntry(context: Context,
database: Database, database: ContextualDatabase,
entryInfo: EntryInfo): Icon? { entryInfo: EntryInfo): Icon? {
try { try {
database.iconDrawableFactory.getBitmapFromIcon(context, database.iconDrawableFactory.getBitmapFromIcon(context,
@@ -227,10 +310,10 @@ object AutofillHelper {
return null return null
} }
@RequiresApi(Build.VERSION_CODES.R)
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
@RequiresApi(Build.VERSION_CODES.R)
private fun buildInlinePresentationForEntry(context: Context, private fun buildInlinePresentationForEntry(context: Context,
database: Database, database: ContextualDatabase,
compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest, compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest,
positionItem: Int, positionItem: Int,
entryInfo: EntryInfo): InlinePresentation? { entryInfo: EntryInfo): InlinePresentation? {
@@ -302,7 +385,7 @@ object AutofillHelper {
} }
fun buildResponse(context: Context, fun buildResponse(context: Context,
database: Database, database: ContextualDatabase,
entriesInfo: List<EntryInfo>, entriesInfo: List<EntryInfo>,
parseResult: StructureParser.Result, parseResult: StructureParser.Result,
compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?): FillResponse? { compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest?): FillResponse? {
@@ -334,25 +417,33 @@ object AutofillHelper {
} }
} }
} }
} }
entriesInfo.forEachIndexed { _, entry -> entriesInfo.forEachIndexed { _, entry ->
if (numberInlineSuggestions > 0 try {
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.R // Build inline presentation for compatible keyboard
&& compatInlineSuggestionsRequest != null) { var inlinePresentation: InlinePresentation? = null
responseBuilder.addDataset(buildDataset(context, database, entry, parseResult) { builder -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
buildInlinePresentationForEntry(context, database, && numberInlineSuggestions > 0
compatInlineSuggestionsRequest, numberInlineSuggestions--, entry && compatInlineSuggestionsRequest != null) {
)?.let { inlinePresentation -> inlinePresentation = buildInlinePresentationForEntry(
builder.setInlinePresentation(inlinePresentation) context,
} database,
}) compatInlineSuggestionsRequest,
} else { numberInlineSuggestions--,
responseBuilder.addDataset(buildDataset(context, database, entry, parseResult)) entry
)
}
// Create dataset for each entry
responseBuilder.addDataset(
buildDatasetForEntry(context, database, entry, parseResult, inlinePresentation)
)
} catch (e: Exception) {
Log.e(TAG, "Unable to add dataset")
} }
} }
// Add a new dataset for manual selection
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) { if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
val searchInfo = SearchInfo().apply { val searchInfo = SearchInfo().apply {
applicationId = parseResult.applicationId applicationId = parseResult.applicationId
@@ -364,29 +455,50 @@ object AutofillHelper {
val pendingIntent = AutofillLauncherActivity.getPendingIntentForSelection(context, val pendingIntent = AutofillLauncherActivity.getPendingIntentForSelection(context,
searchInfo, compatInlineSuggestionsRequest) searchInfo, compatInlineSuggestionsRequest)
parseResult.allAutofillIds().let { autofillIds -> var inlinePresentation: InlinePresentation? = null
autofillIds.forEach { id -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val builder = Dataset.Builder(manualSelectionView) compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest ->
val inlinePresentationSpec = inlineSuggestionsRequest.inlinePresentationSpecs[0]
inlinePresentation = buildInlinePresentationForManualSelection(context, inlinePresentationSpec, pendingIntent)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val datasetBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
compatInlineSuggestionsRequest?.inlineSuggestionsRequest?.let { inlineSuggestionsRequest -> Dataset.Builder(Presentations.Builder()
val inlinePresentationSpec = inlineSuggestionsRequest.inlinePresentationSpecs[0] .apply {
val inlinePresentation = buildInlinePresentationForManualSelection(context, inlinePresentationSpec, pendingIntent) inlinePresentation?.let {
inlinePresentation?.let { setInlinePresentation(it)
builder.setInlinePresentation(it) }
} }
.setDialogPresentation(manualSelectionView)
.setMenuPresentation(manualSelectionView)
.build())
} else {
@Suppress("DEPRECATION")
Dataset.Builder(manualSelectionView).apply {
inlinePresentation?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
setInlinePresentation(it)
} }
} }
builder.setValue(id, null)
builder.setAuthentication(pendingIntent.intentSender)
responseBuilder.addDataset(builder.build())
} }
} }
parseResult.allAutofillIds().let { autofillIds ->
autofillIds.forEach { id ->
datasetBuilder.addValueToDatasetBuilder(id, null)
datasetBuilder.setAuthentication(pendingIntent.intentSender)
}
val dataset = datasetBuilder.build()
Log.d(TAG, "Autofill Dataset for manual selection $dataset created")
responseBuilder.addDataset(dataset)
}
} }
return try { return try {
responseBuilder.build() responseBuilder.build()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to create Autofill response", e)
null null
} }
} }
@@ -395,7 +507,7 @@ object AutofillHelper {
* Build the Autofill response for one entry * Build the Autofill response for one entry
*/ */
fun buildResponseAndSetResult(activity: Activity, fun buildResponseAndSetResult(activity: Activity,
database: Database, database: ContextualDatabase,
entryInfo: EntryInfo) { entryInfo: EntryInfo) {
buildResponseAndSetResult(activity, database, ArrayList<EntryInfo>().apply { add(entryInfo) }) buildResponseAndSetResult(activity, database, ArrayList<EntryInfo>().apply { add(entryInfo) })
} }
@@ -404,17 +516,17 @@ object AutofillHelper {
* Build the Autofill response for many entry * Build the Autofill response for many entry
*/ */
fun buildResponseAndSetResult(activity: Activity, fun buildResponseAndSetResult(activity: Activity,
database: Database, database: ContextualDatabase,
entriesInfo: List<EntryInfo>) { entriesInfo: List<EntryInfo>) {
if (entriesInfo.isEmpty()) { if (entriesInfo.isEmpty()) {
activity.setResult(Activity.RESULT_CANCELED) activity.setResult(Activity.RESULT_CANCELED)
} else { } else {
var setResultOk = false var setResultOk = false
activity.intent?.getParcelableExtra<AssistStructure>(EXTRA_ASSIST_STRUCTURE)?.let { structure -> activity.intent?.getParcelableExtraCompat<AssistStructure>(EXTRA_ASSIST_STRUCTURE)?.let { structure ->
StructureParser(structure).parse()?.let { result -> StructureParser(structure).parse()?.let { result ->
// New Response // New Response
val response = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val response = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val compatInlineSuggestionsRequest = activity.intent?.getParcelableExtra<CompatInlineSuggestionsRequest?>(EXTRA_INLINE_SUGGESTIONS_REQUEST) val compatInlineSuggestionsRequest = activity.intent?.getParcelableExtraCompat<CompatInlineSuggestionsRequest>(EXTRA_INLINE_SUGGESTIONS_REQUEST)
if (compatInlineSuggestionsRequest != null) { if (compatInlineSuggestionsRequest != null) {
Toast.makeText(activity.applicationContext, R.string.autofill_inline_suggestions_keyboard, Toast.LENGTH_SHORT).show() Toast.makeText(activity.applicationContext, R.string.autofill_inline_suggestions_keyboard, Toast.LENGTH_SHORT).show()
} }
@@ -423,7 +535,7 @@ object AutofillHelper {
buildResponse(activity, database, entriesInfo, result, null) buildResponse(activity, database, entriesInfo, result, null)
} }
val mReplyIntent = Intent() val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.") Log.d(activity.javaClass.name, "Success Autofill auth.")
mReplyIntent.putExtra( mReplyIntent.putExtra(
AutofillManager.EXTRA_AUTHENTICATION_RESULT, AutofillManager.EXTRA_AUTHENTICATION_RESULT,
response) response)
@@ -478,4 +590,6 @@ object AutofillHelper {
EntrySelectionHelper.addSearchInfoInIntent(intent, searchInfo) EntrySelectionHelper.addSearchInfoInIntent(intent, searchInfo)
activityResultLauncher?.launch(intent) activityResultLauncher?.launch(intent)
} }
private val TAG = AutofillHelper::class.java.name
} }

View File

@@ -26,6 +26,7 @@ import android.os.Parcelable
import android.service.autofill.FillRequest import android.service.autofill.FillRequest
import android.view.inputmethod.InlineSuggestionsRequest import android.view.inputmethod.InlineSuggestionsRequest
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.kunzisoft.keepass.utils.readParcelableCompat
/** /**
* Utility class only to prevent java.lang.NoClassDefFoundError for old Android version and new lib compilation * Utility class only to prevent java.lang.NoClassDefFoundError for old Android version and new lib compilation
@@ -52,8 +53,7 @@ class CompatInlineSuggestionsRequest : Parcelable {
constructor(parcel: Parcel) { constructor(parcel: Parcel) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
this.inlineSuggestionsRequest = this.inlineSuggestionsRequest = parcel.readParcelableCompat()
parcel.readParcelable(FillRequest::class.java.classLoader)
} }
else { else {
this.inlineSuggestionsRequest = null this.inlineSuggestionsRequest = null

View File

@@ -29,35 +29,33 @@ import android.os.CancellationSignal
import android.service.autofill.* import android.service.autofill.*
import android.util.Log import android.util.Log
import android.view.autofill.AutofillId import android.view.autofill.AutofillId
import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.RemoteViews import android.widget.RemoteViews
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.autofill.inline.UiVersions import androidx.autofill.inline.UiVersions
import androidx.autofill.inline.v1.InlineSuggestionUi import androidx.autofill.inline.v1.InlineSuggestionUi
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.AutofillLauncherActivity import com.kunzisoft.keepass.activities.AutofillLauncherActivity
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.DatabaseTaskProvider
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.helper.SearchHelper
import com.kunzisoft.keepass.model.CreditCard import com.kunzisoft.keepass.model.CreditCard
import com.kunzisoft.keepass.model.RegisterInfo import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.AutofillSettingsActivity import com.kunzisoft.keepass.settings.AutofillSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.WebDomain
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.concurrent.atomic.AtomicBoolean
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
class KeeAutofillService : AutofillService() { class KeeAutofillService : AutofillService() {
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
private var mDatabase: Database? = null private var mDatabase: ContextualDatabase? = null
private var applicationIdBlocklist: Set<String>? = null private var applicationIdBlocklist: Set<String>? = null
private var webDomainBlocklist: Set<String>? = null private var webDomainBlocklist: Set<String>? = null
private var askToSaveData: Boolean = false private var askToSaveData: Boolean = false
private var autofillInlineSuggestionsEnabled: Boolean = false private var autofillInlineSuggestionsEnabled: Boolean = false
private var mLock = AtomicBoolean()
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@@ -90,41 +88,43 @@ class KeeAutofillService : AutofillService() {
cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") } cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") }
// Lock if (request.flags and FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST != 0) {
if (!mLock.get()) { Log.d(TAG, "Autofill requested in compatibility mode")
mLock.set(true) } else {
// Check user's settings for authenticating Responses and Datasets. Log.d(TAG, "Autofill requested in native mode")
val latestStructure = request.fillContexts.last().structure }
StructureParser(latestStructure).parse()?.let { parseResult ->
// Build search info only if applicationId or webDomain are not blocked // Check user's settings for authenticating Responses and Datasets.
if (autofillAllowedFor(parseResult.applicationId, applicationIdBlocklist) val latestStructure = request.fillContexts.last().structure
&& autofillAllowedFor(parseResult.webDomain, webDomainBlocklist)) { StructureParser(latestStructure).parse()?.let { parseResult ->
val searchInfo = SearchInfo().apply {
applicationId = parseResult.applicationId // Build search info only if applicationId or webDomain are not blocked
webDomain = parseResult.webDomain if (autofillAllowedFor(parseResult.applicationId, applicationIdBlocklist)
webScheme = parseResult.webScheme && autofillAllowedFor(parseResult.webDomain, webDomainBlocklist)) {
} val searchInfo = SearchInfo().apply {
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { webDomainWithoutSubDomain -> applicationId = parseResult.applicationId
searchInfo.webDomain = webDomainWithoutSubDomain webDomain = parseResult.webDomain
val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R webScheme = parseResult.webScheme
&& autofillInlineSuggestionsEnabled) { }
CompatInlineSuggestionsRequest(request) WebDomain.getConcreteWebDomain(this, searchInfo.webDomain) { webDomainWithoutSubDomain ->
} else { searchInfo.webDomain = webDomainWithoutSubDomain
null val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
} && autofillInlineSuggestionsEnabled) {
launchSelection(mDatabase, CompatInlineSuggestionsRequest(request)
searchInfo, } else {
parseResult, null
inlineSuggestionsRequest,
callback)
} }
launchSelection(mDatabase,
searchInfo,
parseResult,
inlineSuggestionsRequest,
callback)
} }
} }
} }
} }
private fun launchSelection(database: Database?, private fun launchSelection(database: ContextualDatabase?,
searchInfo: SearchInfo, searchInfo: SearchInfo,
parseResult: StructureParser.Result, parseResult: StructureParser.Result,
inlineSuggestionsRequest: CompatInlineSuggestionsRequest?, inlineSuggestionsRequest: CompatInlineSuggestionsRequest?,
@@ -153,10 +153,11 @@ class KeeAutofillService : AutofillService() {
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
private fun showUIForEntrySelection(parseResult: StructureParser.Result, private fun showUIForEntrySelection(parseResult: StructureParser.Result,
database: Database?, database: ContextualDatabase?,
searchInfo: SearchInfo, searchInfo: SearchInfo,
inlineSuggestionsRequest: CompatInlineSuggestionsRequest?, inlineSuggestionsRequest: CompatInlineSuggestionsRequest?,
callback: FillCallback) { callback: FillCallback) {
var success = false
parseResult.allAutofillIds().let { autofillIds -> parseResult.allAutofillIds().let { autofillIds ->
if (autofillIds.isNotEmpty()) { if (autofillIds.isNotEmpty()) {
// If the entire Autofill Response is authenticated, AuthActivity is used // If the entire Autofill Response is authenticated, AuthActivity is used
@@ -279,17 +280,44 @@ class KeeAutofillService : AutofillService() {
} }
} }
} }
// Build response // Build response
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
try {
// Buggy method on some API 33 devices
responseBuilder.setAuthentication(
autofillIds,
intentSender,
Presentations.Builder().apply {
inlinePresentation?.let {
setInlinePresentation(it)
}
setDialogPresentation(remoteViewsUnlock)
}.build()
)
} catch (e: Exception) {
Log.e(TAG, "Unable to use the new setAuthentication method.", e)
@Suppress("DEPRECATION")
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation)
}
} else {
@Suppress("DEPRECATION")
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock, inlinePresentation)
}
} else { } else {
@Suppress("DEPRECATION")
responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock) responseBuilder.setAuthentication(autofillIds, intentSender, remoteViewsUnlock)
} }
success = true
callback.onSuccess(responseBuilder.build()) callback.onSuccess(responseBuilder.build())
} }
} }
if (!success)
callback.onFailure("Unable to get Autofill ids for UI selection")
} }
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
var success = false
if (askToSaveData) { if (askToSaveData) {
val latestStructure = request.fillContexts.last().structure val latestStructure = request.fillContexts.last().structure
StructureParser(latestStructure).parse(true)?.let { parseResult -> StructureParser(latestStructure).parse(true)?.let { parseResult ->
@@ -332,14 +360,16 @@ class KeeAutofillService : AutofillService() {
// callback.onSuccess(AutofillLauncherActivity.getAuthIntentSenderForRegistration(this, // callback.onSuccess(AutofillLauncherActivity.getAuthIntentSenderForRegistration(this,
// registerInfo)) // registerInfo))
//} else { //} else {
AutofillLauncherActivity.launchForRegistration(this, registerInfo) AutofillLauncherActivity.launchForRegistration(this, registerInfo)
callback.onSuccess() success = true
callback.onSuccess()
//} //}
return
} }
} }
} }
callback.onFailure("Saving form values is not allowed") if (!success) {
callback.onFailure("Saving form values is not allowed")
}
} }
override fun onConnected() { override fun onConnected() {
@@ -348,7 +378,6 @@ class KeeAutofillService : AutofillService() {
} }
override fun onDisconnected() { override fun onDisconnected() {
mLock.set(false)
Log.d(TAG, "onDisconnected") Log.d(TAG, "onDisconnected")
} }

View File

@@ -32,14 +32,13 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.getkeepsafe.taptargetview.TapTargetView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.app.database.CipherDatabaseAction import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.database.exception.IODatabaseException import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.model.CipherDecryptDatabase import com.kunzisoft.keepass.model.CipherDecryptDatabase
import com.kunzisoft.keepass.model.CipherEncryptDatabase import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.CredentialStorage import com.kunzisoft.keepass.model.CredentialStorage
@@ -51,7 +50,7 @@ import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedUnlockCallback { class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCallback {
private var mBuilderListener: BuilderListener? = null private var mBuilderListener: BuilderListener? = null
@@ -68,17 +67,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
// TODO Retrieve credential storage from app database // TODO Retrieve credential storage from app database
var credentialDatabaseStorage: CredentialStorage = CredentialStorage.DEFAULT var credentialDatabaseStorage: CredentialStorage = CredentialStorage.DEFAULT
/**
* Manage setting to auto open biometric prompt
*/
private var mAutoOpenPrompt: Boolean
get() {
return mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt && mAutoOpenPromptEnabled
}
set(value) {
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = value
}
// Variable to check if the prompt can be open (if the right activity is currently shown) // Variable to check if the prompt can be open (if the right activity is currently shown)
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization // checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
private var allowOpenBiometricPrompt = false private var allowOpenBiometricPrompt = false
@@ -96,15 +84,37 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
// Only keep connection when we request a device credential activity // Only keep connection when we request a device credential activity
private var keepConnection = false private var keepConnection = false
private var mDeviceCredentialResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> private var mDeviceCredentialResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false
// To wait resume // To wait resume
if (keepConnection) { if (keepConnection) {
mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded = result.resultCode == Activity.RESULT_OK mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded =
result.resultCode == Activity.RESULT_OK
} }
keepConnection = false keepConnection = false
} }
private val menuProvider: MenuProvider = object: MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
if (mAllowAdvancedUnlockMenu)
menuInflater.inflate(R.menu.advanced_unlock, menu)
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deleteEncryptedDatabaseKey()
}
}
return false
}
}
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
@@ -123,8 +133,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext) cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext)
mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) { mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) {
@@ -143,14 +151,19 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
val rootView = inflater.cloneInContext(contextThemed) val rootView = inflater.inflate(R.layout.fragment_advanced_unlock, container, false)
.inflate(R.layout.fragment_advanced_unlock, container, false)
mAdvancedUnlockInfoView = rootView.findViewById(R.id.advanced_unlock_view) mAdvancedUnlockInfoView = rootView.findViewById(R.id.advanced_unlock_view)
return rootView return rootView
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.addMenuProvider(menuProvider, viewLifecycleOwner)
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
context?.let { context?.let {
@@ -160,26 +173,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
keepConnection = false keepConnection = false
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
if (mAllowAdvancedUnlockMenu)
inflater.inflate(R.menu.advanced_unlock, menu)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deleteEncryptedDatabaseKey()
}
}
return super.onOptionsItemSelected(item)
}
private fun onDatabaseLoaded(databaseUri: Uri?) { private fun onDatabaseLoaded(databaseUri: Uri?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// To get device credential unlock result, only if same database uri // To get device credential unlock result, only if same database uri
@@ -216,8 +209,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
allowOpenBiometricPrompt = true allowOpenBiometricPrompt = true
if (PreferencesUtil.isBiometricUnlockEnable(context)) { if (PreferencesUtil.isBiometricUnlockEnable(context)) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
// biometric not supported (by API level or hardware) so keep option hidden // biometric not supported (by API level or hardware) so keep option hidden
// or manually disable // or manually disable
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(context) val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(context)
@@ -236,7 +227,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
} }
} }
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) { } else if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.bolt)
if (AdvancedUnlockManager.isDeviceSecure(context)) { if (AdvancedUnlockManager.isDeviceSecure(context)) {
selectMode() selectMode()
} else { } else {
@@ -292,14 +282,29 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
private fun initNotAvailable() { private fun initNotAvailable() {
showViews(false) showViews(false)
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null) mAdvancedUnlockInfoView?.setIconViewClickListener(null)
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun openBiometricSetting() { private fun openBiometricSetting() {
mAdvancedUnlockInfoView?.setIconViewClickListener(false) { mAdvancedUnlockInfoView?.setIconViewClickListener {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices... try {
context?.startActivity(Intent(Settings.ACTION_SETTINGS)) when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
context?.startActivity(Intent(Settings.ACTION_BIOMETRIC_ENROLL))
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> {
@Suppress("DEPRECATION") context
?.startActivity(Intent(Settings.ACTION_FINGERPRINT_ENROLL))
}
else -> {
context?.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
}
}
} catch (e: Exception) {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
context?.startActivity(Intent(Settings.ACTION_SETTINGS))
}
} }
} }
@@ -331,11 +336,11 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun initWaitData() { private fun initWaitData() {
showViews(true) showViews(true)
setAdvancedUnlockedTitleView(R.string.no_credentials_stored) setAdvancedUnlockedTitleView(R.string.unavailable)
setAdvancedUnlockedMessageView("") setAdvancedUnlockedMessageView("")
context?.let { context -> context?.let { context ->
mAdvancedUnlockInfoView?.setIconViewClickListener(false) { mAdvancedUnlockInfoView?.setIconViewClickListener {
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS, onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
context.getString(R.string.credential_before_click_advanced_unlock_button)) context.getString(R.string.credential_before_click_advanced_unlock_button))
} }
@@ -362,7 +367,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun initEncryptData() { private fun initEncryptData() {
showViews(true) showViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_store_credential) setAdvancedUnlockedTitleView(R.string.unlock_and_link_biometric)
setAdvancedUnlockedMessageView("") setAdvancedUnlockedMessageView("")
advancedUnlockManager?.initEncryptData { cryptoPrompt -> advancedUnlockManager?.initEncryptData { cryptoPrompt ->
@@ -376,7 +381,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun initDecryptData() { private fun initDecryptData() {
showViews(true) showViews(true)
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_unlock_database) setAdvancedUnlockedTitleView(R.string.unlock)
setAdvancedUnlockedMessageView("") setAdvancedUnlockedMessageView("")
advancedUnlockManager?.let { unlockHelper -> advancedUnlockManager?.let { unlockHelper ->
@@ -391,14 +396,15 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
} }
// Auto open the biometric prompt // Auto open the biometric prompt
if (mAutoOpenPrompt) { if (mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt
mAutoOpenPrompt = false && mAutoOpenPromptEnabled) {
mAdvancedUnlockViewModel.allowAutoOpenBiometricPrompt = false
openAdvancedUnlockPrompt(cryptoPrompt) openAdvancedUnlockPrompt(cryptoPrompt)
} }
} }
} ?: deleteEncryptedDatabaseKey() } ?: deleteEncryptedDatabaseKey()
} }
} ?: throw IODatabaseException() } ?: throw UnknownDatabaseLocationException()
} ?: throw Exception("AdvancedUnlockManager not initialized") } ?: throw Exception("AdvancedUnlockManager not initialized")
} }
@@ -609,7 +615,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedMessageView(text: CharSequence) { private fun setAdvancedUnlockedMessageView(text: CharSequence) {
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
mAdvancedUnlockInfoView?.message = text mAdvancedUnlockInfoView?.setMessage(text)
} }
} }

View File

@@ -403,13 +403,11 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
} }
} }
@RequiresApi(api = Build.VERSION_CODES.M)
fun isDeviceSecure(context: Context): Boolean { fun isDeviceSecure(context: Context): Boolean {
val keyguardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java) return ContextCompat.getSystemService(context, KeyguardManager::class.java)
return keyguardManager?.isDeviceSecure ?: false ?.isDeviceSecure ?: false
} }
@RequiresApi(api = Build.VERSION_CODES.M)
fun biometricUnlockSupported(context: Context): Boolean { fun biometricUnlockSupported(context: Context): Boolean {
val biometricCanAuthenticate = try { val biometricCanAuthenticate = try {
BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG) BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG)
@@ -430,28 +428,23 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
) )
} }
@RequiresApi(api = Build.VERSION_CODES.M)
fun deviceCredentialUnlockSupported(context: Context): Boolean { fun deviceCredentialUnlockSupported(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL) val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN || biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
) )
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { } else {
ContextCompat.getSystemService(context, KeyguardManager::class.java)?.apply { true
return isDeviceSecure
}
} }
return false
} }
/** /**
* Remove entry key in keystore * Remove entry key in keystore
*/ */
@RequiresApi(api = Build.VERSION_CODES.M)
fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity, fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity,
advancedCallback: AdvancedUnlockErrorCallback) { advancedCallback: AdvancedUnlockErrorCallback) {
AdvancedUnlockManager{ fragmentActivity }.apply { AdvancedUnlockManager{ fragmentActivity }.apply {

View File

@@ -1,63 +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.biometric
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.RequiresApi
import android.widget.ImageView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.kunzisoft.keepass.R
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerPrintAnimatedVector(context: Context, imageView: ImageView) {
private val scanFingerprint: AnimatedVectorDrawableCompat? =
AnimatedVectorDrawableCompat.create(context, R.drawable.scan_fingerprint)
init {
imageView.setImageDrawable(scanFingerprint)
}
private var animationCallback = object : Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable) {
imageView.post {
scanFingerprint?.start()
}
}
}
fun startScan() {
scanFingerprint?.registerAnimationCallback(animationCallback)
if (scanFingerprint?.isRunning != true)
scanFingerprint?.start()
}
fun stopScan() {
scanFingerprint?.unregisterAnimationCallback(animationCallback)
if (scanFingerprint?.isRunning == true)
scanFingerprint.stop()
}
}

View File

@@ -0,0 +1,37 @@
package com.kunzisoft.keepass.database
import android.net.Uri
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.utils.SingletonHolder
import java.io.File
class ContextualDatabase: Database() {
var fileUri: Uri? = null
val iconDrawableFactory = IconDrawableFactory(
retrieveBinaryCache = { binaryCache },
retrieveCustomIconBinary = { iconId -> getBinaryForCustomIcon(iconId) }
)
override fun removeCustomIcon(customIcon: IconImageCustom) {
iconDrawableFactory.clearFromCache(customIcon)
super.removeCustomIcon(customIcon)
}
override fun clearIndexesAndBinaries(filesDirectory: File?) {
iconDrawableFactory.clearCache()
super.clearIndexesAndBinaries(filesDirectory)
}
override fun clearAndClose(filesDirectory: File?) {
super.clearAndClose(filesDirectory)
this.fileUri = null
}
companion object : SingletonHolder<ContextualDatabase>(::ContextualDatabase) {
private val TAG = ContextualDatabase::class.java.name
}
}

View File

@@ -17,17 +17,29 @@
* 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.action package com.kunzisoft.keepass.database
import android.app.Service import android.Manifest
import android.content.* import android.content.BroadcastReceiver
import android.content.Context.* import android.content.ComponentName
import android.content.Context
import android.content.Context.BIND_ABOVE_CLIENT
import android.content.Context.BIND_AUTO_CREATE
import android.content.Context.BIND_IMPORTANT
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
@@ -35,7 +47,6 @@ 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.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
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.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
@@ -43,10 +54,10 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.model.CipherEncryptDatabase import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_CHALLENGE_RESPONDED
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
@@ -82,26 +93,33 @@ import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import com.kunzisoft.keepass.utils.putParcelableList
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.UUID
/** /**
* Utility class to connect an activity or a service to the DatabaseTaskNotificationService, * Utility class to connect an activity or a service to the DatabaseTaskNotificationService,
* Useful to retrieve a database instance and sending tasks commands * Useful to retrieve a database instance and sending tasks commands
*/ */
class DatabaseTaskProvider { class DatabaseTaskProvider(
private var context: Context,
private var showDialog: Boolean = true
) {
private var activity: FragmentActivity? = null // To show dialog only if context is an activity
private var service: Service? = null private var activity: FragmentActivity? = try { context as? FragmentActivity? }
private var context: Context catch (_: Exception) { null }
var onDatabaseRetrieved: ((database: Database?) -> Unit)? = null var onDatabaseRetrieved: ((database: ContextualDatabase?) -> Unit)? = null
var onActionFinish: ((database: Database, var onActionFinish: ((database: ContextualDatabase,
actionTask: String, actionTask: String,
result: ActionRunnable.Result) -> Unit)? = null result: ActionRunnable.Result) -> Unit)? = null
private var intentDatabaseTask: Intent private var intentDatabaseTask: Intent = Intent(
context.applicationContext,
DatabaseTaskNotificationService::class.java
)
private var databaseTaskBroadcastReceiver: BroadcastReceiver? = null private var databaseTaskBroadcastReceiver: BroadcastReceiver? = null
private var mBinder: DatabaseTaskNotificationService.ActionTaskBinder? = null private var mBinder: DatabaseTaskNotificationService.ActionTaskBinder? = null
@@ -111,34 +129,49 @@ class DatabaseTaskProvider {
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
constructor(activity: FragmentActivity) { fun destroy() {
this.activity = activity this.activity = null
this.context = activity this.onDatabaseRetrieved = null
this.intentDatabaseTask = Intent(activity.applicationContext, this.onActionFinish = null
DatabaseTaskNotificationService::class.java) this.databaseTaskBroadcastReceiver = null
} this.mBinder = null
this.serviceConnection = null
constructor(service: Service) { this.progressTaskDialogFragment = null
this.service = service this.databaseChangedDialogFragment = null
this.context = service
this.intentDatabaseTask = Intent(service.applicationContext,
DatabaseTaskNotificationService::class.java)
} }
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener { private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) { override fun onActionStarted(
startDialog(titleId, messageId, warningId) database: ContextualDatabase,
progressMessage: ProgressMessage
) {
if (showDialog)
startDialog(progressMessage)
} }
override fun onUpdateAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) { override fun onActionUpdated(
updateDialog(titleId, messageId, warningId) database: ContextualDatabase,
progressMessage: ProgressMessage
) {
if (showDialog)
updateDialog(progressMessage)
} }
override fun onStopAction(database: Database, actionTask: String, result: ActionRunnable.Result) { override fun onActionStopped(
onActionFinish?.invoke(database, actionTask, result) database: ContextualDatabase
) {
// Remove the progress task // Remove the progress task
stopDialog() stopDialog()
} }
override fun onActionFinished(
database: ContextualDatabase,
actionTask: String,
result: ActionRunnable.Result
) {
onActionFinish?.invoke(database, actionTask, result)
onActionStopped(database)
}
} }
private val mActionDatabaseListener = object: DatabaseChangedDialogFragment.ActionDatabaseChangedListener { private val mActionDatabaseListener = object: DatabaseChangedDialogFragment.ActionDatabaseChangedListener {
@@ -147,7 +180,8 @@ class DatabaseTaskProvider {
} }
} }
private var databaseInfoListener = object: DatabaseTaskNotificationService.DatabaseInfoListener { private var databaseInfoListener = object:
DatabaseTaskNotificationService.DatabaseInfoListener {
override fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo, override fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
newDatabaseInfo: SnapFileDatabaseInfo) { newDatabaseInfo: SnapFileDatabaseInfo) {
activity?.let { activity -> activity?.let { activity ->
@@ -176,14 +210,12 @@ class DatabaseTaskProvider {
} }
private var databaseListener = object: DatabaseTaskNotificationService.DatabaseListener { private var databaseListener = object: DatabaseTaskNotificationService.DatabaseListener {
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: ContextualDatabase?) {
onDatabaseRetrieved?.invoke(database) onDatabaseRetrieved?.invoke(database)
} }
} }
private fun startDialog(titleId: Int? = null, private fun startDialog(progressMessage: ProgressMessage) {
messageId: Int? = null,
warningId: Int? = null) {
activity?.let { activity -> activity?.let { activity ->
activity.lifecycleScope.launch { activity.lifecycleScope.launch {
if (progressTaskDialogFragment == null) { if (progressTaskDialogFragment == null) {
@@ -197,22 +229,17 @@ class DatabaseTaskProvider {
PROGRESS_TASK_DIALOG_TAG PROGRESS_TASK_DIALOG_TAG
) )
} }
updateDialog(titleId, messageId, warningId) updateDialog(progressMessage)
} }
} }
} }
private fun updateDialog(titleId: Int?, messageId: Int?, warningId: Int?) { private fun updateDialog(progressMessage: ProgressMessage) {
progressTaskDialogFragment?.apply { progressTaskDialogFragment?.apply {
titleId?.let { updateTitle(progressMessage.titleId)
updateTitle(it) updateMessage(progressMessage.messageId)
} updateWarning(progressMessage.warningId)
messageId?.let { setCancellable(progressMessage.cancelable)
updateMessage(it)
}
warningId?.let {
updateWarning(it)
}
} }
} }
@@ -224,11 +251,17 @@ class DatabaseTaskProvider {
private fun initServiceConnection() { private fun initServiceConnection() {
if (serviceConnection == null) { if (serviceConnection == null) {
serviceConnection = object : ServiceConnection { serviceConnection = object : ServiceConnection {
override fun onBindingDied(name: ComponentName?) {
stopDialog()
}
override fun onNullBinding(name: ComponentName?) {
stopDialog()
}
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) { override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply { mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addDatabaseListener(databaseListener) addServiceListeners(this)
addDatabaseFileInfoListener(databaseInfoListener)
addActionTaskListener(actionTaskListener)
getService().checkDatabase() getService().checkDatabase()
getService().checkDatabaseInfo() getService().checkDatabaseInfo()
getService().checkAction() getService().checkAction()
@@ -236,19 +269,29 @@ class DatabaseTaskProvider {
} }
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
mBinder?.removeActionTaskListener(actionTaskListener) removeServiceListeners(mBinder)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
mBinder = null mBinder = null
} }
} }
} }
} }
private fun addServiceListeners(service: DatabaseTaskNotificationService.ActionTaskBinder?) {
service?.addDatabaseListener(databaseListener)
service?.addDatabaseFileInfoListener(databaseInfoListener)
service?.addActionTaskListener(actionTaskListener)
}
private fun removeServiceListeners(service: DatabaseTaskNotificationService.ActionTaskBinder?) {
service?.removeActionTaskListener(actionTaskListener)
service?.removeDatabaseFileInfoListener(databaseInfoListener)
service?.removeDatabaseListener(databaseListener)
}
private fun bindService() { private fun bindService() {
initServiceConnection() initServiceConnection()
serviceConnection?.let { serviceConnection?.let {
context.bindService(intentDatabaseTask, it, BIND_AUTO_CREATE or BIND_NOT_FOREGROUND or BIND_ABOVE_CLIENT) context.bindService(intentDatabaseTask, it, BIND_AUTO_CREATE or BIND_IMPORTANT or BIND_ABOVE_CLIENT)
} }
} }
@@ -256,19 +299,18 @@ class DatabaseTaskProvider {
* Unbind the service and assign null to the service connection to check if already unbind or not * Unbind the service and assign null to the service connection to check if already unbind or not
*/ */
private fun unBindService() { private fun unBindService() {
serviceConnection?.let { try {
context.unbindService(it) serviceConnection?.let {
context.unbindService(it)
}
} catch (e: java.lang.Exception) {
Log.e(TAG, "Unable to unbind the database task service", e)
} finally {
serviceConnection = null
} }
serviceConnection = null
}
fun isBinded(): Boolean {
return mBinder != null
} }
fun registerProgressTask() { fun registerProgressTask() {
stopDialog()
// Register a database task receiver to stop loading dialog when service finish the task // Register a database task receiver to stop loading dialog when service finish the task
databaseTaskBroadcastReceiver = object : BroadcastReceiver() { databaseTaskBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
@@ -279,17 +321,16 @@ class DatabaseTaskProvider {
} }
DATABASE_STOP_TASK_ACTION -> { DATABASE_STOP_TASK_ACTION -> {
// Remove the progress task // Remove the progress task
stopDialog()
unBindService() unBindService()
} }
} }
} }
} }
context.registerReceiver(databaseTaskBroadcastReceiver, context.registerReceiver(databaseTaskBroadcastReceiver,
IntentFilter().apply { IntentFilter().apply {
addAction(DATABASE_START_TASK_ACTION) addAction(DATABASE_START_TASK_ACTION)
addAction(DATABASE_STOP_TASK_ACTION) addAction(DATABASE_STOP_TASK_ACTION)
} }
) )
// Check if a service is currently running else do nothing // Check if a service is currently running else do nothing
@@ -297,11 +338,7 @@ class DatabaseTaskProvider {
} }
fun unregisterProgressTask() { fun unregisterProgressTask() {
stopDialog() removeServiceListeners(mBinder)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
mBinder = null mBinder = null
unBindService() unBindService()
@@ -313,7 +350,50 @@ class DatabaseTaskProvider {
} }
} }
private val tempServiceParameters = mutableListOf<Pair<Bundle?, String>>()
private val requestPermissionLauncher = activity?.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { _ ->
// Whether or not the user has accepted, the service can be started,
// There just won't be any notification if it's not allowed.
tempServiceParameters.removeFirstOrNull()?.let {
startService(it.first, it.second)
}
}
private fun start(bundle: Bundle? = null, actionTask: String) { private fun start(bundle: Bundle? = null, actionTask: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val contextActivity = activity
if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED
) {
startService(bundle, actionTask)
} else if (contextActivity != null && shouldShowRequestPermissionRationale(
contextActivity,
Manifest.permission.POST_NOTIFICATIONS
)
) {
// it's not the first time, so the user deliberately chooses not to display the notification
startService(bundle, actionTask)
} else {
AlertDialog.Builder(context)
.setMessage(R.string.warning_database_notification_permission)
.setNegativeButton(R.string.later) { _, _ ->
// Refuses the notification, so start the service
startService(bundle, actionTask)
}
.setPositiveButton(R.string.ask) { _, _ ->
// Save the temp parameters to ask the permission
tempServiceParameters.add(Pair(bundle, actionTask))
requestPermissionLauncher?.launch(Manifest.permission.POST_NOTIFICATIONS)
}.create().show()
}
} else {
startService(bundle, actionTask)
}
}
private fun startService(bundle: Bundle? = null, actionTask: String) {
try { try {
if (bundle != null) if (bundle != null)
intentDatabaseTask.putExtras(bundle) intentDatabaseTask.putExtras(bundle)
@@ -321,7 +401,7 @@ class DatabaseTaskProvider {
context.startService(intentDatabaseTask) context.startService(intentDatabaseTask)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to perform database action", e) Log.e(TAG, "Unable to perform database action", e)
Toast.makeText(activity, R.string.error_start_database_action, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.error_start_database_action, Toast.LENGTH_LONG).show()
} }
} }
@@ -332,12 +412,13 @@ class DatabaseTaskProvider {
*/ */
fun startDatabaseCreate(databaseUri: Uri, fun startDatabaseCreate(databaseUri: Uri,
mainCredential: MainCredential) { mainCredential: MainCredential
) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri) putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential) putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
} }
, ACTION_DATABASE_CREATE_TASK) , ACTION_DATABASE_CREATE_TASK)
} }
fun startDatabaseLoad(databaseUri: Uri, fun startDatabaseLoad(databaseUri: Uri,
@@ -352,12 +433,14 @@ class DatabaseTaskProvider {
putParcelable(DatabaseTaskNotificationService.CIPHER_DATABASE_KEY, cipherEncryptDatabase) putParcelable(DatabaseTaskNotificationService.CIPHER_DATABASE_KEY, cipherEncryptDatabase)
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid) putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
} }
, ACTION_DATABASE_LOAD_TASK) , ACTION_DATABASE_LOAD_TASK)
} }
fun startDatabaseMerge(fromDatabaseUri: Uri? = null, fun startDatabaseMerge(save: Boolean,
fromDatabaseUri: Uri? = null,
mainCredential: MainCredential? = null) { mainCredential: MainCredential? = null) {
start(Bundle().apply { start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, fromDatabaseUri) putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, fromDatabaseUri)
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential) putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
} }
@@ -368,7 +451,7 @@ class DatabaseTaskProvider {
start(Bundle().apply { start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid) putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
} }
, ACTION_DATABASE_RELOAD_TASK) , ACTION_DATABASE_RELOAD_TASK)
} }
fun askToStartDatabaseReload(conditionToAsk: Boolean, approved: () -> Unit) { fun askToStartDatabaseReload(conditionToAsk: Boolean, approved: () -> Unit) {
@@ -384,14 +467,15 @@ class DatabaseTaskProvider {
} }
} }
fun startDatabaseAssignPassword(databaseUri: Uri, fun startDatabaseAssignCredential(databaseUri: Uri,
mainCredential: MainCredential) { mainCredential: MainCredential
) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri) putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential) putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
} }
, ACTION_DATABASE_ASSIGN_PASSWORD_TASK) , ACTION_DATABASE_ASSIGN_CREDENTIAL_TASK)
} }
/* /*
@@ -408,7 +492,7 @@ class DatabaseTaskProvider {
putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, parent.nodeId) putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, parent.nodeId)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_CREATE_GROUP_TASK) , ACTION_DATABASE_CREATE_GROUP_TASK)
} }
fun startDatabaseUpdateGroup(oldGroup: Group, fun startDatabaseUpdateGroup(oldGroup: Group,
@@ -419,7 +503,7 @@ class DatabaseTaskProvider {
putParcelable(DatabaseTaskNotificationService.GROUP_KEY, groupToUpdate) putParcelable(DatabaseTaskNotificationService.GROUP_KEY, groupToUpdate)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_GROUP_TASK) , ACTION_DATABASE_UPDATE_GROUP_TASK)
} }
fun startDatabaseCreateEntry(newEntry: Entry, fun startDatabaseCreateEntry(newEntry: Entry,
@@ -430,7 +514,7 @@ class DatabaseTaskProvider {
putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, parent.nodeId) putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, parent.nodeId)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_CREATE_ENTRY_TASK) , ACTION_DATABASE_CREATE_ENTRY_TASK)
} }
fun startDatabaseUpdateEntry(oldEntry: Entry, fun startDatabaseUpdateEntry(oldEntry: Entry,
@@ -441,7 +525,7 @@ class DatabaseTaskProvider {
putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, entryToUpdate) putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, entryToUpdate)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_ENTRY_TASK) , ACTION_DATABASE_UPDATE_ENTRY_TASK)
} }
private fun startDatabaseActionListNodes(actionTask: String, private fun startDatabaseActionListNodes(actionTask: String,
@@ -464,13 +548,13 @@ class DatabaseTaskProvider {
start(Bundle().apply { start(Bundle().apply {
putAll(getBundleFromListNodes(nodesPaste)) putAll(getBundleFromListNodes(nodesPaste))
putParcelableArrayList(DatabaseTaskNotificationService.GROUPS_ID_KEY, groupsIdToCopy) putParcelableList(DatabaseTaskNotificationService.GROUPS_ID_KEY, groupsIdToCopy)
putParcelableArrayList(DatabaseTaskNotificationService.ENTRIES_ID_KEY, entriesIdToCopy) putParcelableList(DatabaseTaskNotificationService.ENTRIES_ID_KEY, entriesIdToCopy)
if (newParentId != null) if (newParentId != null)
putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, newParentId) putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, newParentId)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, actionTask) , actionTask)
} }
fun startDatabaseCopyNodes(nodesToCopy: List<Node>, fun startDatabaseCopyNodes(nodesToCopy: List<Node>,
@@ -504,7 +588,7 @@ class DatabaseTaskProvider {
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition) putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_RESTORE_ENTRY_HISTORY) , ACTION_DATABASE_RESTORE_ENTRY_HISTORY)
} }
fun startDatabaseDeleteEntryHistory(mainEntryId: NodeId<UUID>, fun startDatabaseDeleteEntryHistory(mainEntryId: NodeId<UUID>,
@@ -515,7 +599,7 @@ class DatabaseTaskProvider {
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition) putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_DELETE_ENTRY_HISTORY) , ACTION_DATABASE_DELETE_ENTRY_HISTORY)
} }
/* /*
@@ -532,7 +616,7 @@ class DatabaseTaskProvider {
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newName) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newName)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_NAME_TASK) , ACTION_DATABASE_UPDATE_NAME_TASK)
} }
fun startDatabaseSaveDescription(oldDescription: String, fun startDatabaseSaveDescription(oldDescription: String,
@@ -543,7 +627,7 @@ class DatabaseTaskProvider {
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDescription) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDescription)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK) , ACTION_DATABASE_UPDATE_DESCRIPTION_TASK)
} }
fun startDatabaseSaveDefaultUsername(oldDefaultUsername: String, fun startDatabaseSaveDefaultUsername(oldDefaultUsername: String,
@@ -554,7 +638,7 @@ class DatabaseTaskProvider {
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDefaultUsername) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDefaultUsername)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK) , ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK)
} }
fun startDatabaseSaveColor(oldColor: String, fun startDatabaseSaveColor(oldColor: String,
@@ -565,7 +649,7 @@ class DatabaseTaskProvider {
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newColor) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newColor)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_COLOR_TASK) , ACTION_DATABASE_UPDATE_COLOR_TASK)
} }
fun startDatabaseSaveCompression(oldCompression: CompressionAlgorithm, fun startDatabaseSaveCompression(oldCompression: CompressionAlgorithm,
@@ -576,14 +660,14 @@ class DatabaseTaskProvider {
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newCompression) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newCompression)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_COMPRESSION_TASK) , ACTION_DATABASE_UPDATE_COMPRESSION_TASK)
} }
fun startDatabaseRemoveUnlinkedData(save: Boolean) { fun startDatabaseRemoveUnlinkedData(save: Boolean) {
start(Bundle().apply { start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK) , ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK)
} }
fun startDatabaseSaveRecycleBin(oldRecycleBin: Group?, fun startDatabaseSaveRecycleBin(oldRecycleBin: Group?,
@@ -616,7 +700,7 @@ class DatabaseTaskProvider {
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistoryItems) putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistoryItems)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK) , ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK)
} }
fun startDatabaseSaveMaxHistorySize(oldMaxHistorySize: Long, fun startDatabaseSaveMaxHistorySize(oldMaxHistorySize: Long,
@@ -627,7 +711,7 @@ class DatabaseTaskProvider {
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistorySize) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistorySize)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK) , ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK)
} }
/* /*
@@ -644,7 +728,7 @@ class DatabaseTaskProvider {
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newEncryption) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newEncryption)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_ENCRYPTION_TASK) , ACTION_DATABASE_UPDATE_ENCRYPTION_TASK)
} }
fun startDatabaseSaveKeyDerivation(oldKeyDerivation: KdfEngine, fun startDatabaseSaveKeyDerivation(oldKeyDerivation: KdfEngine,
@@ -655,7 +739,7 @@ class DatabaseTaskProvider {
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newKeyDerivation) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newKeyDerivation)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK) , ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK)
} }
fun startDatabaseSaveIterations(oldIterations: Long, fun startDatabaseSaveIterations(oldIterations: Long,
@@ -666,7 +750,7 @@ class DatabaseTaskProvider {
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newIterations) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newIterations)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_ITERATIONS_TASK) , ACTION_DATABASE_UPDATE_ITERATIONS_TASK)
} }
fun startDatabaseSaveMemoryUsage(oldMemoryUsage: Long, fun startDatabaseSaveMemoryUsage(oldMemoryUsage: Long,
@@ -677,7 +761,7 @@ class DatabaseTaskProvider {
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMemoryUsage) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMemoryUsage)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK) , ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
} }
fun startDatabaseSaveParallelism(oldParallelism: Long, fun startDatabaseSaveParallelism(oldParallelism: Long,
@@ -688,7 +772,7 @@ class DatabaseTaskProvider {
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
} }
, ACTION_DATABASE_UPDATE_PARALLELISM_TASK) , ACTION_DATABASE_UPDATE_PARALLELISM_TASK)
} }
/** /**
@@ -699,10 +783,17 @@ class DatabaseTaskProvider {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, saveToUri) putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, saveToUri)
} }
, ACTION_DATABASE_SAVE) , ACTION_DATABASE_SAVE)
}
fun startChallengeResponded(response: ByteArray?) {
start(Bundle().apply {
putByteArray(DatabaseTaskNotificationService.DATA_BYTES, response)
}
, ACTION_CHALLENGE_RESPONDED)
} }
companion object { companion object {
private val TAG = DatabaseTaskProvider::class.java.name private val TAG = DatabaseTaskProvider::class.java.name
} }
} }

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2022 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database
import android.content.ContentResolver
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.MasterCredential
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.utils.readParcelableCompat
import com.kunzisoft.keepass.utils.getUriInputStream
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
data class MainCredential(var password: String? = null,
var keyFileUri: Uri? = null,
var hardwareKey: HardwareKey? = null): Parcelable {
constructor(parcel: Parcel) : this() {
password = parcel.readString()
keyFileUri = parcel.readParcelableCompat()
hardwareKey = parcel.readEnum<HardwareKey>()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(password)
parcel.writeParcelable(keyFileUri, flags)
parcel.writeEnum(hardwareKey)
}
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MainCredential
if (password != other.password) return false
if (keyFileUri != other.keyFileUri) return false
if (hardwareKey != other.hardwareKey) return false
return true
}
override fun hashCode(): Int {
var result = password?.hashCode() ?: 0
result = 31 * result + (keyFileUri?.hashCode() ?: 0)
result = 31 * result + (hardwareKey?.hashCode() ?: 0)
return result
}
fun toMasterCredential(contentResolver: ContentResolver): MasterCredential {
return MasterCredential(
this.password,
this.keyFileUri?.let {
getKeyFileData(contentResolver, it)
},
this.hardwareKey
)
}
private fun getKeyFileData(contentResolver: ContentResolver,
keyFileUri: Uri): ByteArray? {
contentResolver.getUriInputStream(keyFileUri)?.use { keyFileInputStream ->
return keyFileInputStream.readBytes()
}
return null
}
companion object CREATOR : Parcelable.Creator<MainCredential> {
override fun createFromParcel(parcel: Parcel): MainCredential {
return MainCredential(parcel)
}
override fun newArray(size: Int): Array<MainCredential?> {
return arrayOfNulls(size)
}
private val TAG = MainCredential::class.java.simpleName
}
}

View File

@@ -0,0 +1,13 @@
package com.kunzisoft.keepass.database
import androidx.annotation.StringRes
data class ProgressMessage(
@StringRes
var titleId: Int,
@StringRes
var messageId: Int? = null,
@StringRes
var warningId: Int? = null,
var cancelable: (() -> Unit)? = null
)

View File

@@ -1,83 +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.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
open class AssignMainCredentialInDatabaseRunnable (
context: Context,
database: Database,
protected val mDatabaseUri: Uri,
protected val mMainCredential: MainCredential)
: SaveDatabaseRunnable(context, database, true) {
private var mBackupKey: ByteArray? = null
override fun onStartRun() {
// Set key
try {
mBackupKey = ByteArray(database.masterKey.size)
System.arraycopy(database.masterKey, 0, mBackupKey!!, 0, mBackupKey!!.size)
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mMainCredential.keyFileUri)
database.assignMasterKey(mMainCredential.masterPassword, uriInputStream)
} catch (e: Exception) {
erase(mBackupKey)
setError(e)
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
// Erase the biometric
CipherDatabaseAction.getInstance(context)
.deleteByDatabaseUri(mDatabaseUri)
// Erase the register keyfile
FileDatabaseHistoryAction.getInstance(context)
.deleteKeyFileByDatabaseUri(mDatabaseUri)
if (!result.isSuccess) {
// Erase the current master key
erase(database.masterKey)
mBackupKey?.let {
database.masterKey = it
}
}
}
/**
* Overwrite the array as soon as we don't need it to avoid keeping the extra data in memory
*/
private fun erase(array: ByteArray?) {
if (array == null) return
for (i in array.indices) {
array[i] = 0
}
}
}

View File

@@ -21,57 +21,46 @@ package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.utils.getBinaryDir
import com.kunzisoft.keepass.settings.PreferencesUtil
class CreateDatabaseRunnable(context: Context,
private val mDatabase: Database,
databaseUri: Uri,
private val databaseName: String,
private val rootName: String,
private val templateGroupName: String?,
mainCredential: MainCredential,
private val createDatabaseResult: ((Result) -> Unit)?)
: AssignMainCredentialInDatabaseRunnable(context, mDatabase, databaseUri, mainCredential) {
class CreateDatabaseRunnable(
context: Context,
private val mDatabase: ContextualDatabase,
private val databaseUri: Uri,
private val databaseName: String,
private val rootName: String,
private val templateGroupName: String?,
val mainCredential: MainCredential,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
) : SaveDatabaseRunnable(
context,
mDatabase,
true,
mainCredential,
challengeResponseRetriever
) {
override fun onStartRun() { override fun onStartRun() {
try { try {
// Create new database record // Create new database record
mDatabase.apply { mDatabase.apply {
createData(mDatabaseUri, databaseName, rootName, templateGroupName) this.fileUri = databaseUri
createData(databaseName, rootName, templateGroupName)
} }
} catch (e: Exception) { } catch (e: Exception) {
mDatabase.clearAndClose(context) mDatabase.clearAndClose(context.getBinaryDir())
setError(e) setError(e)
} }
super.onStartRun() super.onStartRun()
} }
override fun onActionRun() {
super.onActionRun()
if (result.isSuccess) {
// Add database to recent files
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context.applicationContext)
.addOrUpdateDatabaseUri(mDatabaseUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mMainCredential.keyFileUri else null)
}
// Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context)
} else {
Log.e("CreateDatabaseRunnable", "Unable to create the database")
}
}
override fun onFinishRun() { override fun onFinishRun() {
if (result.isSuccess) {
mDatabase.loaded = true
}
super.onFinishRun() super.onFinishRun()
createDatabaseResult?.invoke(result)
} }
} }

View File

@@ -21,73 +21,65 @@ package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.DatabaseInputException
import com.kunzisoft.keepass.model.CipherEncryptDatabase import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.getBinaryDir
import com.kunzisoft.keepass.utils.getUriInputStream
class LoadDatabaseRunnable(private val context: Context, class LoadDatabaseRunnable(
private val mDatabase: Database, private val context: Context,
private val mUri: Uri, private val mDatabase: ContextualDatabase,
private val mMainCredential: MainCredential, private val mDatabaseUri: Uri,
private val mReadonly: Boolean, private val mMainCredential: MainCredential,
private val mCipherEncryptDatabase: CipherEncryptDatabase?, private val mChallengeResponseRetriever: (hardwareKey: HardwareKey, seed: ByteArray?) -> ByteArray,
private val mFixDuplicateUUID: Boolean, private val mReadonly: Boolean,
private val progressTaskUpdater: ProgressTaskUpdater?, private val mFixDuplicateUUID: Boolean,
private val mLoadDatabaseResult: ((Result) -> Unit)?) private val progressTaskUpdater: ProgressTaskUpdater?
: ActionRunnable() { ) : ActionRunnable() {
var afterLoadDatabase : ((Result) -> Unit)? = null
private val binaryDir = context.getBinaryDir()
override fun onStartRun() { override fun onStartRun() {
// Clear before we load // Clear before we load
mDatabase.clearAndClose(context) mDatabase.clearAndClose(binaryDir)
} }
override fun onActionRun() { override fun onActionRun() {
try { try {
mDatabase.loadData(mUri, val contentResolver = context.contentResolver
mMainCredential, // Save database URI
mReadonly, mDatabase.fileUri = mDatabaseUri
context.contentResolver, mDatabase.loadData(
UriUtil.getBinaryDir(context), contentResolver.getUriInputStream(mDatabaseUri)
{ memoryWanted -> ?: throw UnknownDatabaseLocationException(),
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted) mMainCredential.toMasterCredential(contentResolver),
}, mChallengeResponseRetriever,
mFixDuplicateUUID, mReadonly,
progressTaskUpdater) binaryDir,
} { memoryWanted ->
catch (e: LoadDatabaseException) { BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
mFixDuplicateUUID,
progressTaskUpdater
)
} catch (e: DatabaseInputException) {
setError(e) setError(e)
} }
if (result.isSuccess) { if (!result.isSuccess) {
// Save keyFile in app database mDatabase.clearAndClose(binaryDir)
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context)
.addOrUpdateDatabaseUri(mUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mMainCredential.keyFileUri else null)
}
// Register the biometric
mCipherEncryptDatabase?.let { cipherDatabase ->
CipherDatabaseAction.getInstance(context)
.addOrUpdateCipherDatabase(cipherDatabase) // return value not called
}
// Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context)
} else {
mDatabase.clearAndClose(context)
} }
} }
override fun onFinishRun() { override fun onFinishRun() {
mLoadDatabaseResult?.invoke(result) afterLoadDatabase?.invoke(result)
} }
} }

View File

@@ -21,47 +21,54 @@ package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.element.binary.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.getUriInputStream
class MergeDatabaseRunnable(private val context: Context, class MergeDatabaseRunnable(
private val mDatabase: Database, context: Context,
private val mDatabaseToMergeUri: Uri?, private val mDatabaseToMergeUri: Uri?,
private val mDatabaseToMergeMainCredential: MainCredential?, private val mDatabaseToMergeMainCredential: MainCredential?,
private val progressTaskUpdater: ProgressTaskUpdater?, private val mDatabaseToMergeChallengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
private val mLoadDatabaseResult: ((Result) -> Unit)?) database: ContextualDatabase,
: ActionRunnable() { saveDatabase: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
private val progressTaskUpdater: ProgressTaskUpdater?,
) : SaveDatabaseRunnable(
context,
database,
saveDatabase,
null,
challengeResponseRetriever
) {
override fun onStartRun() { override fun onStartRun() {
mDatabase.wasReloaded = true database.wasReloaded = true
super.onStartRun()
} }
override fun onActionRun() { override fun onActionRun() {
try { try {
mDatabase.mergeData(mDatabaseToMergeUri, val contentResolver = context.contentResolver
mDatabaseToMergeMainCredential, database.mergeData(
context.contentResolver, context.contentResolver.getUriInputStream(
mDatabaseToMergeUri ?: database.fileUri
) ?: throw UnknownDatabaseLocationException(),
mDatabaseToMergeMainCredential?.toMasterCredential(contentResolver),
mDatabaseToMergeChallengeResponseRetriever,
{ memoryWanted -> { memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted) BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
}, },
progressTaskUpdater progressTaskUpdater
) )
} catch (e: LoadDatabaseException) { } catch (e: DatabaseException) {
setError(e) setError(e)
} }
if (result.isSuccess) { super.onActionRun()
// Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context)
}
}
override fun onFinishRun() {
mLoadDatabaseResult?.invoke(result)
} }
} }

View File

@@ -20,46 +20,50 @@
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.binary.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.getBinaryDir
import com.kunzisoft.keepass.utils.getUriInputStream
class ReloadDatabaseRunnable(private val context: Context, class ReloadDatabaseRunnable(
private val mDatabase: Database, private val context: Context,
private val progressTaskUpdater: ProgressTaskUpdater?, private val mDatabase: ContextualDatabase,
private val mLoadDatabaseResult: ((Result) -> Unit)?) private val progressTaskUpdater: ProgressTaskUpdater?
: ActionRunnable() { ) : ActionRunnable() {
var afterReloadDatabase : ((Result) -> Unit)? = null
private val binaryDir = context.getBinaryDir()
override fun onStartRun() { override fun onStartRun() {
// Clear before we load // Clear before we load
mDatabase.clearIndexesAndBinaries(UriUtil.getBinaryDir(context)) mDatabase.clearIndexesAndBinaries(binaryDir)
mDatabase.wasReloaded = true mDatabase.wasReloaded = true
} }
override fun onActionRun() { override fun onActionRun() {
try { try {
mDatabase.reloadData(context.contentResolver, mDatabase.reloadData(
{ memoryWanted -> context.contentResolver.getUriInputStream(mDatabase.fileUri)
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted) ?: throw UnknownDatabaseLocationException(),
}, { memoryWanted ->
progressTaskUpdater) BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
} catch (e: LoadDatabaseException) { },
progressTaskUpdater)
} catch (e: DatabaseException) {
setError(e) setError(e)
} }
if (result.isSuccess) { if (!result.isSuccess) {
// Register the current time to init the lock timer mDatabase.clearAndClose(binaryDir)
PreferencesUtil.saveCurrentTime(context)
} else {
mDatabase.clearAndClose(context)
} }
} }
override fun onFinishRun() { override fun onFinishRun() {
mLoadDatabaseResult?.invoke(result) afterReloadDatabase?.invoke(result)
} }
} }

View File

@@ -20,13 +20,21 @@
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.hardware.HardwareKey
class RemoveUnlinkedDataDatabaseRunnable ( class RemoveUnlinkedDataDatabaseRunnable (
context: Context, context: Context,
database: Database, database: ContextualDatabase,
saveDatabase: Boolean) saveDatabase: Boolean,
: SaveDatabaseRunnable(context, database, saveDatabase) { challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
) : SaveDatabaseRunnable(
context,
database,
saveDatabase,
null,
challengeResponseRetriever
) {
override fun onActionRun() { override fun onActionRun() {
try { try {

View File

@@ -21,17 +21,24 @@ package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.exception.DatabaseException import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.getUriOutputStream
import java.io.File
open class SaveDatabaseRunnable(protected var context: Context, open class SaveDatabaseRunnable(
protected var database: Database, protected var context: Context,
private var saveDatabase: Boolean, protected var database: ContextualDatabase,
private var databaseCopyUri: Uri? = null) private var saveDatabase: Boolean,
: ActionRunnable() { private var mainCredential: MainCredential?, // If null, uses composite Key
private var challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
private var databaseCopyUri: Uri? = null
) : ActionRunnable() {
var mAfterSaveDatabase: ((Result) -> Unit)? = null var afterSaveDatabase: ((Result) -> Unit)? = null
override fun onStartRun() {} override fun onStartRun() {}
@@ -39,7 +46,15 @@ open class SaveDatabaseRunnable(protected var context: Context,
database.checkVersion() database.checkVersion()
if (saveDatabase && result.isSuccess) { if (saveDatabase && result.isSuccess) {
try { try {
database.saveData(databaseCopyUri, context.contentResolver) val contentResolver = context.contentResolver
// Build temp database file to avoid file corruption if error
database.saveData(
cacheFile = File(context.cacheDir, databaseCopyUri.hashCode().toString()),
databaseOutputStream = contentResolver
.getUriOutputStream(databaseCopyUri ?: database.fileUri),
isNewLocation = databaseCopyUri == null,
mainCredential?.toMasterCredential(contentResolver),
challengeResponseRetriever)
} catch (e: DatabaseException) { } catch (e: DatabaseException) {
setError(e) setError(e)
} }
@@ -48,6 +63,6 @@ open class SaveDatabaseRunnable(protected var context: Context,
override fun onFinishRun() { override fun onFinishRun() {
// Need to call super.onFinishRun() in child class // Need to call super.onFinishRun() in child class
mAfterSaveDatabase?.invoke(result) afterSaveDatabase?.invoke(result)
} }
} }

View File

@@ -20,16 +20,24 @@
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.hardware.HardwareKey
class UpdateCompressionBinariesDatabaseRunnable ( class UpdateCompressionBinariesDatabaseRunnable (
context: Context, context: Context,
database: Database, database: ContextualDatabase,
private val oldCompressionAlgorithm: CompressionAlgorithm, private val oldCompressionAlgorithm: CompressionAlgorithm,
private val newCompressionAlgorithm: CompressionAlgorithm, private val newCompressionAlgorithm: CompressionAlgorithm,
saveDatabase: Boolean) saveDatabase: Boolean,
: SaveDatabaseRunnable(context, database, saveDatabase) { challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
) : SaveDatabaseRunnable(
context,
database,
saveDatabase,
null,
challengeResponseRetriever
) {
override fun onStartRun() { override fun onStartRun() {
// Set new compression // Set new compression

View File

@@ -20,17 +20,19 @@
package com.kunzisoft.keepass.database.action.history package com.kunzisoft.keepass.database.action.history
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
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.hardware.HardwareKey
class DeleteEntryHistoryDatabaseRunnable ( class DeleteEntryHistoryDatabaseRunnable (
context: Context, context: Context,
database: Database, database: ContextualDatabase,
private val mainEntry: Entry, private val mainEntry: Entry,
private val entryHistoryPosition: Int, private val entryHistoryPosition: Int,
saveDatabase: Boolean) saveDatabase: Boolean,
: SaveDatabaseRunnable(context, database, saveDatabase) { challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
) : SaveDatabaseRunnable(context, database, saveDatabase, null, challengeResponseRetriever) {
override fun onStartRun() { override fun onStartRun() {
try { try {

View File

@@ -20,18 +20,20 @@
package com.kunzisoft.keepass.database.action.history package com.kunzisoft.keepass.database.action.history
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
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.hardware.HardwareKey
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
class RestoreEntryHistoryDatabaseRunnable ( class RestoreEntryHistoryDatabaseRunnable (
private val context: Context, private val context: Context,
private val database: Database, private val database: ContextualDatabase,
private val mainEntry: Entry, private val mainEntry: Entry,
private val entryHistoryPosition: Int, private val entryHistoryPosition: Int,
private val saveDatabase: Boolean) private val saveDatabase: Boolean,
: ActionRunnable() { private val challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
) : ActionRunnable() {
private var updateEntryRunnable: UpdateEntryRunnable? = null private var updateEntryRunnable: UpdateEntryRunnable? = null
@@ -43,12 +45,15 @@ class RestoreEntryHistoryDatabaseRunnable (
historyToRestore.addEntryToHistory(it) historyToRestore.addEntryToHistory(it)
} }
// Update the entry with the fresh formatted entry to restore // Update the entry with the fresh formatted entry to restore
updateEntryRunnable = UpdateEntryRunnable(context, updateEntryRunnable = UpdateEntryRunnable(
database, context,
mainEntry, database,
historyToRestore, mainEntry,
saveDatabase, historyToRestore,
null) saveDatabase,
null,
challengeResponseRetriever
)
updateEntryRunnable?.onStartRun() updateEntryRunnable?.onStartRun()

View File

@@ -20,15 +20,17 @@
package com.kunzisoft.keepass.database.action.node package com.kunzisoft.keepass.database.action.node
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.hardware.HardwareKey
abstract class ActionNodeDatabaseRunnable( abstract class ActionNodeDatabaseRunnable(
context: Context, context: Context,
database: Database, database: ContextualDatabase,
private val afterActionNodesFinish: AfterActionNodesFinish?, private val afterActionNodesFinish: AfterActionNodesFinish?,
save: Boolean) save: Boolean,
: SaveDatabaseRunnable(context, database, save) { challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
) : SaveDatabaseRunnable(context, database, save, null, challengeResponseRetriever) {
/** /**
* Function do to a node action * Function do to a node action

View File

@@ -20,19 +20,21 @@
package com.kunzisoft.keepass.database.action.node package com.kunzisoft.keepass.database.action.node
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
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.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class AddEntryRunnable constructor( class AddEntryRunnable constructor(
context: Context, context: Context,
database: Database, database: ContextualDatabase,
private val mNewEntry: Entry, private val mNewEntry: Entry,
private val mParent: Group, private val mParent: Group,
save: Boolean, save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?) afterActionNodesFinish: AfterActionNodesFinish?,
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() { override fun nodeAction() {
mNewEntry.touch(modified = true, touchParents = true) mNewEntry.touch(modified = true, touchParents = true)

View File

@@ -20,18 +20,20 @@
package com.kunzisoft.keepass.database.action.node package com.kunzisoft.keepass.database.action.node
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class AddGroupRunnable constructor( class AddGroupRunnable constructor(
context: Context, context: Context,
database: Database, database: ContextualDatabase,
private val mNewGroup: Group, private val mNewGroup: Group,
private val mParent: Group, private val mParent: Group,
save: Boolean, save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?) afterActionNodesFinish: AfterActionNodesFinish?,
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray
) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() { override fun nodeAction() {
mNewGroup.touch(modified = true, touchParents = true) mNewGroup.touch(modified = true, touchParents = true)

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