Compare commits

..

117 Commits
2.8.2 ... 2.8.3

Author SHA1 Message Date
J-Jamet
fe7074736a Merge branch 'release/2.8.3' 2020-09-02 12:29:36 +02:00
J-Jamet
69c523ffad Image button at 48dp 2020-09-02 12:21:18 +02:00
J-Jamet
6aeefdf43d Merge branch 'translations' into develop 2020-09-01 20:27:41 +02:00
J-Jamet
5f4c8be3d3 Replace strong tags 2020-09-01 20:27:19 +02:00
J-Jamet
3092e4c557 Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations
# Conflicts:
#	app/src/main/res/values-cs/strings.xml
#	app/src/main/res/values-da/strings.xml
#	app/src/main/res/values-de/strings.xml
#	app/src/main/res/values-hr/strings.xml
#	app/src/main/res/values-ja/strings.xml
#	app/src/main/res/values-pl/strings.xml
#	app/src/main/res/values-ru/strings.xml
#	app/src/main/res/values-tr/strings.xml
#	app/src/main/res/values-uk/strings.xml
#	app/src/main/res/values-zh-rCN/strings.xml
#	app/src/main/res/values-zh-rTW/strings.xml
2020-09-01 20:19:46 +02:00
J-Jamet
ea119068da Remove full file path setting 2020-09-01 19:19:15 +02:00
J-Jamet
14371ecf94 Educational hint for attachment 2020-09-01 19:12:25 +02:00
James Alison
45b0fcfe15 Translated using Weblate (Persian)
Currently translated at 57.3% (257 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fa/
2020-09-01 18:49:37 +02:00
behnam ghafari
00aa5f5586 Translated using Weblate (Persian)
Currently translated at 57.3% (257 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fa/
2020-09-01 18:49:37 +02:00
J-Jamet
79fd53fd4c Edit custom fields #675 2020-09-01 18:45:52 +02:00
J-Jamet
357ee3daf0 Fix styles color in Kitkat 2020-09-01 13:22:41 +02:00
J-Jamet
c2f7897f10 Information icon as home button 2020-09-01 12:43:29 +02:00
J-Jamet
75455c0c48 Change file modification info #667 2020-09-01 12:14:37 +02:00
J-Jamet
a394bb9f8e Remove modification date when not available 2020-09-01 12:00:08 +02:00
J-Jamet
2f5a846493 Change bottom bar color 2020-09-01 00:30:18 +02:00
J-Jamet
90376b361d Fix populate OTP 2020-09-01 00:30:03 +02:00
J-Jamet
229cf6bf5f Change purple background cardview 2020-09-01 00:19:00 +02:00
J-Jamet
bc46737353 Fix tab selection 2020-08-31 23:29:00 +02:00
J-Jamet
1db2243a2e Upgrade CHANGELOG 2020-08-31 22:16:28 +02:00
J-Jamet
7cf836b3cb Refactor backup methods for KDB database 2020-08-31 22:14:04 +02:00
J-Jamet
f7bbd295d6 Fix backup group 2020-08-31 21:45:39 +02:00
behnam ghafari
62dbd95b48 Added translation using Weblate (Persian) 2020-08-31 20:01:41 +02:00
HARADA Hiroyuki
df07e9c719 Translated using Weblate (Japanese)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-08-31 19:46:13 +02:00
J-Jamet
9aaf72726e Fix deletion for database V1 #394 2020-08-31 17:18:37 +02:00
J-Jamet
5289927619 Removes max lines in notes #676 2020-08-31 16:54:26 +02:00
J-Jamet
fa8c686f75 Revert EditTextVisibility #660 2020-08-31 16:35:02 +02:00
J-Jamet
df5f28b7c4 Smooth scroll when adding element and fix #660 2020-08-31 16:05:22 +02:00
HARADA Hiroyuki
280d8368fa Translated using Weblate (Japanese)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-08-31 15:22:27 +02:00
HARADA Hiroyuki
80dbff1f21 Translated using Weblate (Japanese)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-08-31 15:11:27 +02:00
J-Jamet
7ee68a8481 Change item attachment focus 2020-08-31 13:35:38 +02:00
J-Jamet
ac8dd42c45 Change small code 2020-08-31 13:35:18 +02:00
J-Jamet
eed2148b2a Remove unused import 2020-08-28 21:49:15 +02:00
J-Jamet
dc5345b6d3 Fix database alias #670 2020-08-28 21:49:00 +02:00
J-Jamet
221af0b5bb Fix attachment history with the same name 2020-08-28 13:48:07 +02:00
J-Jamet
a10ccc1eb0 Second pass to fix attachment deleted in history 2020-08-28 13:12:54 +02:00
J-Jamet
b72d858480 First pass to fix attachment deleted in history 2020-08-28 11:08:30 +02:00
J-Jamet
60412cc90b Update CHANGELOG 2020-08-28 10:43:54 +02:00
J-Jamet
512ac87dc9 Fix attachment icon color 2020-08-28 10:38:48 +02:00
J-Jamet
d97020d1c5 Merge branch 'wiomoc-patch-1' into develop 2020-08-28 10:22:12 +02:00
Zidan Pragata
f82cb617ba Translated using Weblate (Indonesian)
Currently translated at 39.2% (176 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2020-08-28 09:00:49 +02:00
Small Ku
f79281a1a0 Translated using Weblate (Chinese (Traditional))
Currently translated at 55.5% (249 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2020-08-28 09:00:45 +02:00
HARADA Hiroyuki
7be1dbb78b Translated using Weblate (Japanese)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-08-28 09:00:44 +02:00
C. Rüdinger
f875787799 Translated using Weblate (German)
Currently translated at 99.7% (447 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-08-28 09:00:44 +02:00
jan madsen
f82c208556 Translated using Weblate (Danish)
Currently translated at 94.8% (425 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-08-28 09:00:43 +02:00
Christoph Walcher
f2150e3d85 Early return in readHeaderField
In the old version the outer loop won't terminate if `EndOfHeader` is a zero sized field.
2020-08-28 03:09:23 +02:00
J-Jamet
ecc198e8a0 Fix toolbar appearance #669 2020-08-27 20:39:03 +02:00
J-Jamet
949bc58a80 Merge branch 'feature/File_Attachment' into develop #189 2020-08-27 19:14:51 +02:00
J-Jamet
7d79fff16f Disable compression by entry attachment in DatabaseV4 2020-08-27 19:04:58 +02:00
J-Jamet
3aeb678292 Fix binaries indexes in DatabaseV4 2020-08-27 18:45:56 +02:00
Zidan Pragata
160ac41bed Added translation using Weblate (Indonesian) 2020-08-27 16:41:50 +02:00
J-Jamet
9dfbcbe89c Not compress by default for database v4 2020-08-27 14:21:36 +02:00
J-Jamet
30da529348 Fix corruption in header 2020-08-27 13:57:34 +02:00
J-Jamet
dd8d114711 Change save binaries compression for database 3.1 & 4 2020-08-27 13:49:48 +02:00
J-Jamet
2191a4a848 Allow swipe notification when download completed 2020-08-27 10:27:45 +02:00
J-Jamet
8b9ea8d988 Fix remove oldest attachments 2020-08-27 10:16:37 +02:00
J-Jamet
46dda8567d Remove warnings and better gzip view implementation 2020-08-26 23:43:49 +02:00
J-Jamet
6953da4d9a Change containsAttachment method 2020-08-26 23:35:16 +02:00
J-Jamet
e987d6647e Fix header binary compression 2020-08-26 23:25:13 +02:00
J-Jamet
359d85727e Remove oldest attachments files when deleted from entries 2020-08-26 21:25:51 +02:00
J-Jamet
a994bf9dd8 Change string to Gzip 2020-08-26 20:35:45 +02:00
J-Jamet
14c4e095f6 Remove attachment path view 2020-08-26 20:33:13 +02:00
J-Jamet
59dce0e56f Restore unknown compression 2020-08-26 20:27:13 +02:00
J-Jamet
1f54a893a7 Move classes 2020-08-26 19:28:26 +02:00
J-Jamet
9489f1ee3d Rename removeUnlinkedAttachments 2020-08-26 19:25:30 +02:00
J-Jamet
dc3d720e8d Binary files as time 2020-08-26 19:20:37 +02:00
J-Jamet
efe30b598b Write only attachments in header
Remove unlinked attachments
Simpler compression
2020-08-26 18:51:25 +02:00
J-Jamet
42515bfb2d Encapsulate consume attachment action 2020-08-26 11:44:56 +02:00
J-Jamet
acb3657d95 Better compression - decompression implementation 2020-08-26 11:43:35 +02:00
J-Jamet
e7159c9d36 Try to fix decompression 2020-08-26 10:39:56 +02:00
J-Jamet
f3fdca368b Fix compression after download and upload attachment 2020-08-25 20:07:41 +02:00
J-Jamet
4ea3e08a45 Refactor EntryAttachment to Attachment 2020-08-25 19:28:41 +02:00
J-Jamet
1eebc72b21 Encapsulate binary methods 2020-08-25 19:20:58 +02:00
J-Jamet
9a91be7e36 Add attachment icon in entry list 2020-08-25 18:13:44 +02:00
J-Jamet
48476f9b88 Add attachments 2020-08-25 17:51:01 +02:00
J-Jamet
68c991eb9b Ask to replace file 2020-08-25 17:48:17 +02:00
J-Jamet
b51c77b01b Ask for big files 2020-08-25 17:19:58 +02:00
J-Jamet
57105db554 Allow only one attachment for Database KDB 2020-08-25 12:52:41 +02:00
J-Jamet
4bd3bdaddf Fix concurrent exception and upload the same file 2020-08-25 12:39:17 +02:00
J-Jamet
9cf59b8d73 Refactoring code 2020-08-25 12:27:43 +02:00
J-Jamet
a793b0bb42 Remove not compatible elements below KitKat 2020-08-25 12:00:17 +02:00
J-Jamet
4d9e2e1471 Upload progression 2020-08-25 11:37:11 +02:00
J-Jamet
1719887e55 Fix lock button after download or upload 2020-08-24 22:52:12 +02:00
J-Jamet
2be00aca9d Merge branch 'develop' into feature/File_Attachment 2020-08-24 22:33:05 +02:00
J-Jamet
6fd05c5ad7 Rotate arrow drawable 2020-08-24 22:32:40 +02:00
J-Jamet
65e404374f Change upload icon 2020-08-24 22:32:05 +02:00
J-Jamet
0b78731bb3 Strings for upload notification 2020-08-24 22:20:58 +02:00
J-Jamet
65d318ed88 Fix many upload issues 2020-08-24 21:54:21 +02:00
Milo Ivir
1bfcea55a9 Translated using Weblate (Croatian)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-08-24 16:06:20 +02:00
Eric
6780eb004d Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-08-24 16:06:20 +02:00
ihor_ck
0e12b2d021 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2020-08-24 16:06:20 +02:00
Andrew
1b356f87ec Translated using Weblate (Russian)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-08-24 16:06:20 +02:00
solokot
bf24d0bae1 Translated using Weblate (Russian)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-08-24 16:06:19 +02:00
WaldiS
97c831d4bb Translated using Weblate (Polish)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-08-24 16:06:19 +02:00
HARADA Hiroyuki
e3e48ffa6d Translated using Weblate (Japanese)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-08-24 16:06:19 +02:00
zeritti
b678416122 Translated using Weblate (Czech)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-08-24 16:06:19 +02:00
J-Jamet
df722925fa Upload attachments #189 2020-08-24 13:03:59 +02:00
J-Jamet
4cbc0d9806 Merge branch 'develop' into feature/File_Attachment 2020-08-23 12:49:44 +02:00
J-Jamet
b0f3711b4e Remove previous allow lock code 2020-08-23 12:49:12 +02:00
J-Jamet
14ec6579b2 Merge branch 'develop' into feature/File_Attachment 2020-08-23 12:41:56 +02:00
J-Jamet
30bf039473 Fix last item visibility 2020-08-23 12:26:10 +02:00
Oğuz Ersen
33bea317b0 Translated using Weblate (Turkish)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-08-22 05:31:03 +02:00
Andrew
c6814dc05e Translated using Weblate (Russian)
Currently translated at 97.9% (439 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-08-22 05:31:03 +02:00
HARADA Hiroyuki
16808069ec Translated using Weblate (Japanese)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-08-22 05:31:02 +02:00
J-Jamet
e813974e29 Fix file info issue 2020-08-21 20:17:05 +02:00
J-Jamet
7e0010b536 Images buttons width 36dp 2020-08-21 20:02:32 +02:00
J-Jamet
93171adcb3 Smaller download icon 2020-08-21 19:48:16 +02:00
J-Jamet
9501cc76a4 Notes and URL as EntryField 2020-08-21 19:43:54 +02:00
J-Jamet
5d7db046ac Encapsulate username and OTP as EntryField 2020-08-21 18:35:54 +02:00
J-Jamet
46c259bc3e Fix field delete button position 2020-08-21 18:24:41 +02:00
J-Jamet
3bacff91d3 Visibility button for each field, password as EntryField view 2020-08-21 17:31:15 +02:00
J-Jamet
bd79d483d2 Upgrade to 2.8.3 2020-08-21 16:16:35 +02:00
Hosted Weblate
16e31f4881 Merge branch 'origin/master' into Weblate. 2020-08-21 16:14:09 +02:00
ssantos
afa23c393d Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_PT/
2020-08-21 16:14:08 +02:00
HARADA Hiroyuki
54d23cb781 Translated using Weblate (Japanese)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2020-08-21 16:14:08 +02:00
random r
c757e410e9 Translated using Weblate (Italian)
Currently translated at 100.0% (448 of 448 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-08-21 16:14:08 +02:00
J-Jamet
39dd25567d Merge tag '2.8.2' into develop
2.8.2
2020-08-21 16:04:09 +02:00
J-Jamet
b3c0494618 Better file attachment download implementation 2020-08-06 16:59:15 +02:00
122 changed files with 3028 additions and 1562 deletions

View File

@@ -1,3 +1,10 @@
KeePassDX(2.8.3)
* Upload attachments
* Visibility button for each hidden field
* Fix read header file
* Fix deletion in KDB database
* Fix minor issues
KeePassDX(2.8.2) KeePassDX(2.8.2)
* Fix themes / new UI * Fix themes / new UI
* Fix multiples notifications * Fix multiples notifications

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 29 targetSdkVersion 29
versionCode = 38 versionCode = 39
versionName = "2.8.2" versionName = "2.8.3"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"

View File

@@ -45,8 +45,9 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.model.EntryAttachment import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
@@ -86,7 +87,7 @@ class EntryActivity : LockingActivity() {
private var mShowPassword: Boolean = false private var mShowPassword: Boolean = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap() private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
private var clipboardHelper: ClipboardHelper? = null private var clipboardHelper: ClipboardHelper? = null
private var mFirstLaunchOfActivity: Boolean = false private var mFirstLaunchOfActivity: Boolean = false
@@ -212,8 +213,8 @@ class EntryActivity : LockingActivity() {
mAttachmentFileBinderManager?.apply { mAttachmentFileBinderManager?.apply {
registerProgressTask() registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener { onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) { override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
entryContentsView?.updateAttachmentDownloadProgress(attachment) entryContentsView?.putAttachment(entryAttachmentState)
} }
} }
} }
@@ -240,14 +241,13 @@ class EntryActivity : LockingActivity() {
toolbar?.title = entryTitle toolbar?.title = entryTitle
// Assign basic fields // Assign basic fields
entryContentsView?.assignUserName(entry.username) entryContentsView?.assignUserName(entry.username) {
entryContentsView?.assignUserNameCopyListener(View.OnClickListener {
database.startManageEntry(entry) database.startManageEntry(entry)
clipboardHelper?.timeoutCopyToClipboard(entry.username, clipboardHelper?.timeoutCopyToClipboard(entry.username,
getString(R.string.copy_field, getString(R.string.copy_field,
getString(R.string.entry_user_name))) getString(R.string.entry_user_name)))
database.stopManageEntry(entry) database.stopManageEntry(entry)
}) }
val isFirstTimeAskAllowCopyPasswordAndProtectedFields = val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(this) PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)
@@ -274,23 +274,25 @@ class EntryActivity : LockingActivity() {
} }
} }
entryContentsView?.assignPassword(entry.password, allowCopyPasswordAndProtectedFields) val onPasswordCopyClickListener: View.OnClickListener? = if (allowCopyPasswordAndProtectedFields) {
if (allowCopyPasswordAndProtectedFields) { View.OnClickListener {
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
database.startManageEntry(entry) database.startManageEntry(entry)
clipboardHelper?.timeoutCopyToClipboard(entry.password, clipboardHelper?.timeoutCopyToClipboard(entry.password,
getString(R.string.copy_field, getString(R.string.copy_field,
getString(R.string.entry_password))) getString(R.string.entry_password)))
database.stopManageEntry(entry) database.stopManageEntry(entry)
}) }
} else { } else {
// If dialog not already shown // If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) { if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
entryContentsView?.assignPasswordCopyListener(showWarningClipboardDialogOnClickListener) showWarningClipboardDialogOnClickListener
} else { } else {
entryContentsView?.assignPasswordCopyListener(null) null
} }
} }
entryContentsView?.assignPassword(entry.password,
allowCopyPasswordAndProtectedFields,
onPasswordCopyClickListener)
//Assign OTP field //Assign OTP field
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress, entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
@@ -304,7 +306,7 @@ class EntryActivity : LockingActivity() {
}) })
entryContentsView?.assignURL(entry.url) entryContentsView?.assignURL(entry.url)
entryContentsView?.assignComment(entry.notes) entryContentsView?.assignNotes(entry.notes)
// Assign custom fields // Assign custom fields
if (entry.allowCustomFields()) { if (entry.allowCustomFields()) {
@@ -312,12 +314,12 @@ class EntryActivity : LockingActivity() {
for ((label, value) in entry.customFields) { for ((label, value) in entry.customFields) {
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
if (allowCopyProtectedField) { if (allowCopyProtectedField) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, View.OnClickListener { entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
clipboardHelper?.timeoutCopyToClipboard( clipboardHelper?.timeoutCopyToClipboard(
value.toString(), value.toString(),
getString(R.string.copy_field, label) getString(R.string.copy_field, label)
) )
}) }
} else { } else {
// If dialog not already shown // If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) { if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
@@ -328,18 +330,13 @@ class EntryActivity : LockingActivity() {
} }
} }
} }
entryContentsView?.setHiddenPasswordStyle(!mShowPassword) entryContentsView?.setHiddenProtectedValue(!mShowPassword)
// Manage attachments // Manage attachments
entryContentsView?.assignAttachments(entry.getAttachments()) { attachmentItem -> mDatabase?.binaryPool?.let { binaryPool ->
when (attachmentItem.downloadState) { entryContentsView?.assignAttachments(entry.getAttachments(binaryPool).toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> { createDocument(this, attachmentItem.name)?.let { requestCode ->
createDocument(this, attachmentItem.name)?.let { requestCode -> mAttachmentsToDownload[requestCode] = attachmentItem
mAttachmentsToDownload[requestCode] = attachmentItem
}
}
else -> {
// TODO Stop download
} }
} }
} }
@@ -393,16 +390,6 @@ class EntryActivity : LockingActivity() {
} }
} }
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
if (mShowPassword) {
togglePassword?.setTitle(R.string.menu_hide_password)
togglePassword?.setIcon(R.drawable.ic_visibility_off_white_24dp)
} else {
togglePassword?.setTitle(R.string.menu_showpass)
togglePassword?.setIcon(R.drawable.ic_visibility_white_24dp)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
@@ -418,15 +405,6 @@ class EntryActivity : LockingActivity() {
menu.findItem(R.id.menu_edit)?.isVisible = false menu.findItem(R.id.menu_edit)?.isVisible = false
} }
val togglePassword = menu.findItem(R.id.menu_toggle_pass)
entryContentsView?.let {
if (it.isPasswordPresent || it.atLeastOneFieldProtectedPresent()) {
changeShowPasswordIcon(togglePassword)
} else {
togglePassword?.isVisible = false
}
}
val gotoUrl = menu.findItem(R.id.menu_goto_url) val gotoUrl = menu.findItem(R.id.menu_goto_url)
gotoUrl?.apply { gotoUrl?.apply {
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes // In API >= 11 onCreateOptionsMenu may be called before onCreate completes
@@ -449,28 +427,31 @@ class EntryActivity : LockingActivity() {
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation, private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
menu: Menu) { menu: Menu) {
val entryCopyEducationPerformed = entryContentsView?.isUserNamePresent == true val entryFieldCopyView = findViewById<View>(R.id.entry_field_copy)
val entryCopyEducationPerformed = entryFieldCopyView != null
&& entryActivityEducation.checkAndPerformedEntryCopyEducation( && entryActivityEducation.checkAndPerformedEntryCopyEducation(
findViewById(R.id.entry_user_name_action_image), entryFieldCopyView,
{ {
clipboardHelper?.timeoutCopyToClipboard(mEntry!!.username, val appNameString = getString(R.string.app_name)
getString(R.string.copy_field, clipboardHelper?.timeoutCopyToClipboard(appNameString,
getString(R.string.entry_user_name))) getString(R.string.copy_field, appNameString))
}, },
{ {
performedNextEducation(entryActivityEducation, menu) performedNextEducation(entryActivityEducation, menu)
}) })
if (!entryCopyEducationPerformed) { if (!entryCopyEducationPerformed) {
val menuEditView = toolbar?.findViewById<View>(R.id.menu_edit)
// entryEditEducationPerformed // entryEditEducationPerformed
toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation( menuEditView != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
toolbar!!.findViewById(R.id.menu_edit), menuEditView,
{ {
onOptionsItemSelected(menu.findItem(R.id.menu_edit)) onOptionsItemSelected(menu.findItem(R.id.menu_edit))
}, },
{ {
performedNextEducation(entryActivityEducation, menu) performedNextEducation(entryActivityEducation, menu)
}) }
)
} }
} }
@@ -480,12 +461,6 @@ class EntryActivity : LockingActivity() {
MenuUtil.onContributionItemSelected(this) MenuUtil.onContributionItemSelected(this)
return true return true
} }
R.id.menu_toggle_pass -> {
mShowPassword = !mShowPassword
changeShowPasswordIcon(item)
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
return true
}
R.id.menu_edit -> { R.id.menu_edit -> {
mEntry?.let { mEntry?.let {
EntryEditActivity.launch(this@EntryActivity, it) EntryEditActivity.launch(this@EntryActivity, it)

View File

@@ -22,6 +22,8 @@ import android.app.Activity
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.app.TimePickerDialog import android.app.TimePickerDialog
import android.content.Intent import android.content.Intent
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.util.Log import android.util.Log
@@ -36,18 +38,17 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.* import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.education.EntryEditActivityEducation import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.model.FocusedEditField import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
@@ -55,8 +56,10 @@ import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.EntryEditContentsView import com.kunzisoft.keepass.view.EntryEditContentsView
import com.kunzisoft.keepass.view.showActionError import com.kunzisoft.keepass.view.showActionError
import com.kunzisoft.keepass.view.updateLockPaddingLeft import com.kunzisoft.keepass.view.updateLockPaddingLeft
@@ -69,7 +72,9 @@ class EntryEditActivity : LockingActivity(),
GeneratePasswordDialogFragment.GeneratePasswordListener, GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener, SetOTPDialogFragment.CreateOtpListener,
DatePickerDialog.OnDateSetListener, DatePickerDialog.OnDateSetListener,
TimePickerDialog.OnTimeSetListener { TimePickerDialog.OnTimeSetListener,
FileTooBigDialogFragment.ActionChooseListener,
ReplaceFileDialogFragment.ActionChooseListener {
private var mDatabase: Database? = null private var mDatabase: Database? = null
@@ -90,6 +95,11 @@ class EntryEditActivity : LockingActivity(),
private var mFocusedEditExtraField: FocusedEditField? = null private var mFocusedEditExtraField: FocusedEditField? = null
// To manage attachments
private var mSelectFileHelper: SelectFileHelper? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAllowMultipleAttachments: Boolean = false
// Education // Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null private var entryEditActivityEducation: EntryEditActivityEducation? = null
@@ -215,22 +225,22 @@ class EntryEditActivity : LockingActivity(),
entryEditAddToolBar?.apply { entryEditAddToolBar?.apply {
menuInflater.inflate(R.menu.entry_edit, menu) menuInflater.inflate(R.menu.entry_edit, menu)
menu.findItem(R.id.menu_add_field).apply {
val allowLock = PreferencesUtil.showLockDatabaseButton(context)
isEnabled = allowLock
isVisible = allowLock
}
menu.findItem(R.id.menu_add_field).apply { menu.findItem(R.id.menu_add_field).apply {
val allowCustomField = mNewEntry?.allowCustomFields() == true val allowCustomField = mNewEntry?.allowCustomFields() == true
isEnabled = allowCustomField isEnabled = allowCustomField
isVisible = allowCustomField isVisible = allowCustomField
} }
// Attachment not compatible below KitKat
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
menu.findItem(R.id.menu_add_attachment).isVisible = false
}
menu.findItem(R.id.menu_add_otp).apply { menu.findItem(R.id.menu_add_otp).apply {
val allowOTP = mDatabase?.allowOTP == true val allowOTP = mDatabase?.allowOTP == true
isEnabled = allowOTP isEnabled = allowOTP
isVisible = allowOTP // OTP not compatible below KitKat
isVisible = allowOTP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
} }
setOnMenuItemClickListener { item -> setOnMenuItemClickListener { item ->
@@ -239,6 +249,10 @@ class EntryEditActivity : LockingActivity(),
addNewCustomField() addNewCustomField()
true true
} }
R.id.menu_add_attachment -> {
addNewAttachment(item)
true
}
R.id.menu_add_otp -> { R.id.menu_add_otp -> {
setupOTP() setupOTP()
true true
@@ -248,6 +262,10 @@ class EntryEditActivity : LockingActivity(),
} }
} }
// To retrieve attachment
mSelectFileHelper = SelectFileHelper(this)
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
// Save button // Save button
validateButton = findViewById(R.id.entry_edit_validate) validateButton = findViewById(R.id.entry_edit_validate)
validateButton?.setOnClickListener { saveEntry() } validateButton?.setOnClickListener { saveEntry() }
@@ -279,6 +297,54 @@ class EntryEditActivity : LockingActivity(),
// Padding if lock button visible // Padding if lock button visible
entryEditAddToolBar?.updateLockPaddingLeft() entryEditAddToolBar?.updateLockPaddingLeft()
mAllowMultipleAttachments = mDatabase?.allowMultipleAttachments == true
mAttachmentFileBinderManager?.apply {
registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
when (entryAttachmentState.downloadState) {
AttachmentState.START -> {
entryEditContentsView?.apply {
// When only one attachment is allowed
if (!mAllowMultipleAttachments) {
clearAttachments()
}
putAttachment(entryAttachmentState)
requestLayout()
// Scroll to the attachment position
getAttachmentViewPosition(entryAttachmentState) {
scrollView?.smoothScrollTo(0, it.toInt())
}
}
}
AttachmentState.IN_PROGRESS -> {
entryEditContentsView?.putAttachment(entryAttachmentState)
}
AttachmentState.COMPLETE -> {
entryEditContentsView?.apply {
putAttachment(entryAttachmentState)
// Scroll to the attachment position
getAttachmentViewPosition(entryAttachmentState) {
scrollView?.smoothScrollTo(0, it.toInt())
}
}
}
AttachmentState.ERROR -> {
mDatabase?.removeAttachmentIfNotUsed(entryAttachmentState.attachment)
entryEditContentsView?.removeAttachment(entryAttachmentState)
}
else -> {}
}
}
}
}
}
override fun onPause() {
mAttachmentFileBinderManager?.unregisterProgressTask()
super.onPause()
} }
private fun populateViewsWithEntry(newEntry: Entry) { private fun populateViewsWithEntry(newEntry: Entry) {
@@ -303,9 +369,14 @@ class EntryEditActivity : LockingActivity(),
notes = newEntry.notes notes = newEntry.notes
assignExtraFields(newEntry.customFields.mapTo(ArrayList()) { assignExtraFields(newEntry.customFields.mapTo(ArrayList()) {
Field(it.key, it.value) Field(it.key, it.value)
}, {
editCustomField(it)
}, mFocusedEditExtraField) }, mFocusedEditExtraField)
assignAttachments(newEntry.getAttachments()) { attachment ->
newEntry.removeAttachment(attachment) mDatabase?.binaryPool?.let { binaryPool ->
assignAttachments(newEntry.getAttachments(binaryPool).toSet(), StreamDirection.UPLOAD) { attachment ->
newEntry.removeAttachment(attachment)
}
} }
} }
} }
@@ -327,9 +398,14 @@ class EntryEditActivity : LockingActivity(),
expiryTime = entryView.expiresDate expiryTime = entryView.expiresDate
} }
notes = entryView.notes notes = entryView.notes
entryView.getExtraField().forEach { customField -> entryView.getExtraFields().forEach { customField ->
putExtraField(customField.name, customField.protectedValue) putExtraField(customField.name, customField.protectedValue)
} }
mDatabase?.binaryPool?.let { binaryPool ->
entryView.getAttachments().forEach {
putAttachment(it, binaryPool)
}
}
mFocusedEditExtraField = entryView.getExtraFieldFocused() mFocusedEditExtraField = entryView.getExtraFieldFocused()
} }
} }
@@ -358,12 +434,84 @@ class EntryEditActivity : LockingActivity(),
EntryCustomFieldDialogFragment.getInstance().show(supportFragmentManager, "customFieldDialog") EntryCustomFieldDialogFragment.getInstance().show(supportFragmentManager, "customFieldDialog")
} }
override fun onNewCustomFieldApproved(label: String, protection: Boolean) { private fun editCustomField(field: Field) {
entryEditContentsView?.putExtraField(Field(label, ProtectedString(protection))) EntryCustomFieldDialogFragment.getInstance(field).show(supportFragmentManager, "customFieldDialog")
} }
override fun onNewCustomFieldCanceled(label: String, protection: Boolean) {} override fun onNewCustomFieldApproved(newField: Field) {
entryEditContentsView?.apply {
putExtraField(newField)
getExtraFieldViewPosition(newField) { position ->
scrollView?.smoothScrollTo(0, position.toInt())
}
}
}
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
entryEditContentsView?.replaceExtraField(oldField, newField)
}
override fun onDeleteCustomFieldApproved(oldField: Field) {
entryEditContentsView?.removeExtraField(oldField)
}
/**
* Add a new attachment
*/
private fun addNewAttachment(item: MenuItem) {
mSelectFileHelper?.selectFileOnClickViewListener?.onMenuItemClick(item)
}
override fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?) {
if (attachmentToUploadUri != null && fileName != null) {
buildNewAttachment(attachmentToUploadUri, fileName)
}
}
override fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?) {
if (attachmentToUploadUri != null && attachment != null) {
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, attachment)
}
}
private fun buildNewAttachment(attachmentToUploadUri: Uri, fileName: String) {
val compression = mDatabase?.compressionForNewEntry() ?: false
mDatabase?.buildNewBinary(applicationContext.filesDir, false, compression)?.let { binaryAttachment ->
val entryAttachment = Attachment(fileName, binaryAttachment)
// Ask to replace the current attachment
if ((mDatabase?.allowMultipleAttachments != true && entryEditContentsView?.containsAttachment() == true) ||
entryEditContentsView?.containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD)) == true) {
ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
.show(supportFragmentManager, "replacementFileFragment")
} else {
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, entryAttachment)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
uri?.let { attachmentToUploadUri ->
// TODO Async to get the name
UriUtil.getFileData(this, attachmentToUploadUri)?.also { documentFile ->
documentFile.name?.let { fileName ->
if (documentFile.length() > MAX_WARNING_BINARY_FILE) {
FileTooBigDialogFragment.build(attachmentToUploadUri, fileName)
.show(supportFragmentManager, "fileTooBigFragment")
} else {
buildNewAttachment(attachmentToUploadUri, fileName)
}
}
}
}
}
}
/**
* Set up OTP (HOTP or TOTP) and add it as extra field
*/
private fun setupOTP() { private fun setupOTP() {
// Retrieve the current otpElement if exists // Retrieve the current otpElement if exists
// and open the dialog to set up the OTP // and open the dialog to set up the OTP
@@ -441,8 +589,8 @@ class EntryEditActivity : LockingActivity(),
if (!generatePasswordEducationPerformed) { if (!generatePasswordEducationPerformed) {
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field) val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
val addNewFieldEducationPerformed = mNewEntry != null val addNewFieldEducationPerformed = mNewEntry != null
&& mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty() && mNewEntry!!.allowCustomFields() && addNewFieldView != null
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE && addNewFieldView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation( && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView, addNewFieldView,
{ {
@@ -453,13 +601,27 @@ class EntryEditActivity : LockingActivity(),
} }
) )
if (!addNewFieldEducationPerformed) { if (!addNewFieldEducationPerformed) {
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp) val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment)
setupOtpView != null && setupOtpView.visibility == View.VISIBLE val addAttachmentEducationPerformed = attachmentView != null && attachmentView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation( && entryEditActivityEducation.checkAndPerformedAttachmentEducation(
setupOtpView, attachmentView,
{ {
setupOTP() mSelectFileHelper?.selectFileOnClickViewListener?.onClick(attachmentView)
}) },
{
performedNextEducation(entryEditActivityEducation)
}
)
if (!addAttachmentEducationPerformed) {
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
setupOtpView != null && setupOtpView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
setupOtpView,
{
setupOTP()
}
)
}
} }
} }
} }
@@ -485,8 +647,13 @@ class EntryEditActivity : LockingActivity(),
// Update the otp field with otpauth:// url // Update the otp field with otpauth:// url
val otpField = OtpEntryFields.buildOtpField(otpElement, val otpField = OtpEntryFields.buildOtpField(otpElement,
mEntry?.title, mEntry?.username) mEntry?.title, mEntry?.username)
entryEditContentsView?.putExtraField(otpField)
mEntry?.putExtraField(otpField.name, otpField.protectedValue) mEntry?.putExtraField(otpField.name, otpField.protectedValue)
entryEditContentsView?.apply {
putExtraField(otpField)
getExtraFieldViewPosition(otpField) { position ->
scrollView?.smoothScrollTo(0, position.toInt())
}
}
} }
override fun iconPicked(bundle: Bundle) { override fun iconPicked(bundle: Bundle) {

View File

@@ -45,7 +45,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
@@ -69,7 +69,6 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
// Views // Views
private var coordinatorLayout: CoordinatorLayout? = null private var coordinatorLayout: CoordinatorLayout? = null
private var fileManagerExplanationButton: View? = null
private var createDatabaseButtonView: View? = null private var createDatabaseButtonView: View? = null
private var openDatabaseButtonView: View? = null private var openDatabaseButtonView: View? = null
@@ -82,7 +81,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private var mDatabaseFileUri: Uri? = null private var mDatabaseFileUri: Uri? = null
private var mOpenFileHelper: OpenFileHelper? = null private var mSelectFileHelper: SelectFileHelper? = null
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
@@ -98,20 +97,15 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
toolbar.title = "" toolbar.title = ""
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
fileManagerExplanationButton = findViewById(R.id.file_manager_explanation_button)
fileManagerExplanationButton?.setOnClickListener {
UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
}
// Create database button // Create database button
createDatabaseButtonView = findViewById(R.id.create_database_button) createDatabaseButtonView = findViewById(R.id.create_database_button)
createDatabaseButtonView?.setOnClickListener { createNewFile() } createDatabaseButtonView?.setOnClickListener { createNewFile() }
// Open database button // Open database button
mOpenFileHelper = OpenFileHelper(this) mSelectFileHelper = SelectFileHelper(this)
openDatabaseButtonView = findViewById(R.id.open_keyfile_button) openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView?.apply { openDatabaseButtonView?.apply {
mOpenFileHelper?.openFileOnClickViewListener?.let { mSelectFileHelper?.selectFileOnClickViewListener?.let {
setOnClickListener(it) setOnClickListener(it)
setOnLongClickListener(it) setOnLongClickListener(it)
} }
@@ -389,7 +383,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data) AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
} }
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
if (uri != null) { if (uri != null) {
launchPasswordActivityWithPath(uri) launchPasswordActivityWithPath(uri)
} }
@@ -445,7 +439,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
openDatabaseButtonView!!, openDatabaseButtonView!!,
{tapTargetView -> {tapTargetView ->
tapTargetView?.let { tapTargetView?.let {
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it) mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it)
} }
}, },
{} {}
@@ -454,6 +448,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
}
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item) return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
} }

View File

@@ -44,8 +44,8 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
@@ -64,7 +64,9 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.MASTER_PASSWORD_KEY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.MASTER_PASSWORD_KEY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.* import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
@@ -92,7 +94,7 @@ open class PasswordActivity : SpecialModeActivity() {
private var mDatabaseKeyFileUri: Uri? = null private var mDatabaseKeyFileUri: Uri? = null
private var mRememberKeyFile: Boolean = false private var mRememberKeyFile: Boolean = false
private var mOpenFileHelper: OpenFileHelper? = null private var mSelectFileHelper: SelectFileHelper? = null
private var mPermissionAsked = false private var mPermissionAsked = false
private var readOnly: Boolean = false private var readOnly: Boolean = false
@@ -136,9 +138,9 @@ open class PasswordActivity : SpecialModeActivity() {
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState) readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this) mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
mOpenFileHelper = OpenFileHelper(this@PasswordActivity) mSelectFileHelper = SelectFileHelper(this@PasswordActivity)
keyFileSelectionView?.apply { keyFileSelectionView?.apply {
mOpenFileHelper?.openFileOnClickViewListener?.let { mSelectFileHelper?.selectFileOnClickViewListener?.let {
setOnClickListener(it) setOnClickListener(it)
setOnLongClickListener(it) setOnLongClickListener(it)
} }
@@ -747,7 +749,7 @@ open class PasswordActivity : SpecialModeActivity() {
} }
var keyFileResult = false var keyFileResult = false
mOpenFileHelper?.let { mSelectFileHelper?.let {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {

View File

@@ -25,16 +25,16 @@ import android.content.DialogInterface
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 com.google.android.material.textfield.TextInputLayout
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.View import android.view.View
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
class AssignMasterKeyDialogFragment : DialogFragment() { class AssignMasterKeyDialogFragment : DialogFragment() {
@@ -56,7 +56,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var mListener: AssignPasswordDialogListener? = null private var mListener: AssignPasswordDialogListener? = null
private var mOpenFileHelper: OpenFileHelper? = null private var mSelectFileHelper: SelectFileHelper? = null
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) {}
@@ -113,10 +113,10 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox) keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection) keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
mOpenFileHelper = OpenFileHelper(this) mSelectFileHelper = SelectFileHelper(this)
keyFileSelectionView?.apply { keyFileSelectionView?.apply {
setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener) setOnClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
setOnLongClickListener(mOpenFileHelper?.openFileOnClickViewListener) setOnLongClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
} }
val dialog = builder.create() val dialog = builder.create()
@@ -249,8 +249,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
) { uri ->
uri?.let { pathUri -> uri?.let { pathUri ->
keyFileCheckBox?.isChecked = true keyFileCheckBox?.isChecked = true
keyFileSelectionView?.uri = pathUri keyFileSelectionView?.uri = pathUri

View File

@@ -22,24 +22,31 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.Button import android.widget.Button
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.Field
class EntryCustomFieldDialogFragment: DialogFragment() { class EntryCustomFieldDialogFragment: DialogFragment() {
private var oldField: Field? = null
private var entryCustomFieldListener: EntryCustomFieldListener? = null private var entryCustomFieldListener: EntryCustomFieldListener? = null
private var customFieldLabelContainer: TextInputLayout? = null private var customFieldLabelContainer: TextInputLayout? = null
private var customFieldLabel: TextView? = null private var customFieldLabel: TextView? = null
private var customFieldDeleteButton: ImageView? = null
private var customFieldProtectionButton: CompoundButton? = null private var customFieldProtectionButton: CompoundButton? = null
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
@@ -58,17 +65,27 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
val root = activity.layoutInflater.inflate(R.layout.fragment_entry_new_field, null) val root = activity.layoutInflater.inflate(R.layout.fragment_entry_new_field, null)
customFieldLabelContainer = root?.findViewById(R.id.entry_custom_field_label_container) customFieldLabelContainer = root?.findViewById(R.id.entry_custom_field_label_container)
customFieldLabel = root?.findViewById(R.id.entry_custom_field_label) customFieldLabel = root?.findViewById(R.id.entry_custom_field_label)
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?.let { oldCustomField ->
customFieldLabel?.text = oldCustomField.name
customFieldProtectionButton?.isChecked = oldCustomField.protectedValue.isProtected
customFieldDeleteButton?.visibility = View.VISIBLE
customFieldDeleteButton?.setOnClickListener {
entryCustomFieldListener?.onDeleteCustomFieldApproved(oldCustomField)
(dialog as AlertDialog?)?.dismiss()
}
} ?: run {
customFieldDeleteButton?.visibility = View.GONE
}
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
builder.setView(root) builder.setView(root)
.setPositiveButton(android.R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
.setNegativeButton(android.R.string.cancel) { _, _ -> .setNegativeButton(android.R.string.cancel) { _, _ -> }
entryCustomFieldListener?.onNewCustomFieldCanceled(
customFieldLabel?.text.toString(),
customFieldProtectionButton?.isChecked == true
)
}
val dialogCreated = builder.create() val dialogCreated = builder.create()
customFieldLabel?.requestFocus() customFieldLabel?.requestFocus()
@@ -102,10 +119,19 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
private fun approveIfValid() { private fun approveIfValid() {
if (isValid()) { if (isValid()) {
entryCustomFieldListener?.onNewCustomFieldApproved( oldField?.let {
customFieldLabel?.text.toString(), // New property with old value
customFieldProtectionButton?.isChecked == true entryCustomFieldListener?.onEditCustomFieldApproved(it,
) Field(customFieldLabel?.text?.toString() ?: "",
ProtectedString(customFieldProtectionButton?.isChecked == true,
it.protectedValue.stringValue))
)
} ?: run {
entryCustomFieldListener?.onNewCustomFieldApproved(
Field(customFieldLabel?.text?.toString() ?: "",
ProtectedString(customFieldProtectionButton?.isChecked == true))
)
}
(dialog as AlertDialog?)?.dismiss() (dialog as AlertDialog?)?.dismiss()
} }
} }
@@ -127,13 +153,25 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
} }
interface EntryCustomFieldListener { interface EntryCustomFieldListener {
fun onNewCustomFieldApproved(label: String, protection: Boolean) fun onNewCustomFieldApproved(newField: Field)
fun onNewCustomFieldCanceled(label: String, protection: Boolean) fun onEditCustomFieldApproved(oldField: Field, newField: Field)
fun onDeleteCustomFieldApproved(oldField: Field)
} }
companion object { companion object {
private const val KEY_FIELD = "KEY_FIELD"
fun getInstance(): EntryCustomFieldDialogFragment { fun getInstance(): EntryCustomFieldDialogFragment {
return EntryCustomFieldDialogFragment() return EntryCustomFieldDialogFragment()
} }
fun getInstance(field: Field): EntryCustomFieldDialogFragment {
return EntryCustomFieldDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(KEY_FIELD, field)
}
}
}
} }
} }

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
/**
* Custom Dialog to confirm big file to upload
*/
class FileTooBigDialogFragment : DialogFragment() {
private var mActionChooseListener: ActionChooseListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
mActionChooseListener = context as ActionChooseListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + ActionChooseListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
builder.setMessage(SpannableStringBuilder().apply {
append(getString(R.string.warning_file_too_big))
append("\n\n")
append(getString(R.string.warning_sure_add_file))
})
builder.setPositiveButton(android.R.string.yes) { _, _ ->
mActionChooseListener?.onValidateUploadFileTooBig(
arguments?.getParcelable(KEY_FILE_URI),
arguments?.getString(KEY_FILE_NAME))
}
builder.setNegativeButton(android.R.string.no) { _, _ ->
dismiss()
}
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
interface ActionChooseListener {
fun onValidateUploadFileTooBig(attachmentToUploadUri: Uri?, fileName: String?)
}
companion object {
const val MAX_WARNING_BINARY_FILE = 5242880
private const val KEY_FILE_URI = "KEY_FILE_URI"
private const val KEY_FILE_NAME = "KEY_FILE_NAME"
fun build(attachmentToUploadUri: Uri,
fileName: String): FileTooBigDialogFragment {
val fragment = FileTooBigDialogFragment()
fragment.arguments = Bundle().apply {
putParcelable(KEY_FILE_URI, attachmentToUploadUri)
putString(KEY_FILE_NAME, fileName)
}
return fragment
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.text.SpannableStringBuilder
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Attachment
/**
* Custom Dialog to confirm big file to upload
*/
class ReplaceFileDialogFragment : DialogFragment() {
private var mActionChooseListener: ActionChooseListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
mActionChooseListener = context as ActionChooseListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + ActionChooseListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
builder.setMessage(SpannableStringBuilder().apply {
append(getString(R.string.warning_replace_file))
append("\n\n")
append(getString(R.string.warning_sure_add_file))
})
builder.setPositiveButton(android.R.string.yes) { _, _ ->
mActionChooseListener?.onValidateReplaceFile(
arguments?.getParcelable(KEY_FILE_URI),
arguments?.getParcelable(KEY_ENTRY_ATTACHMENT))
}
builder.setNegativeButton(android.R.string.no) { _, _ ->
dismiss()
}
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
interface ActionChooseListener {
fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?)
}
companion object {
private const val KEY_FILE_URI = "KEY_FILE_URI"
private const val KEY_ENTRY_ATTACHMENT = "KEY_ENTRY_ATTACHMENT"
fun build(attachmentToUploadUri: Uri,
attachment: Attachment): ReplaceFileDialogFragment {
val fragment = ReplaceFileDialogFragment()
fragment.arguments = Bundle().apply {
putParcelable(KEY_FILE_URI, attachmentToUploadUri)
putParcelable(KEY_ENTRY_ATTACHMENT, attachment)
}
return fragment
}
}
}

View File

@@ -28,19 +28,20 @@ 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
import android.view.MenuItem
import android.view.View import android.view.View
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
class OpenFileHelper { class SelectFileHelper {
private var activity: Activity? = null private var activity: Activity? = null
private var fragment: Fragment? = null private var fragment: Fragment? = null
val openFileOnClickViewListener: OpenFileOnClickViewListener val selectFileOnClickViewListener: SelectFileOnClickViewListener
get() = OpenFileOnClickViewListener() get() = SelectFileOnClickViewListener()
constructor(context: Activity) { constructor(context: Activity) {
this.activity = context this.activity = context
@@ -52,7 +53,10 @@ class OpenFileHelper {
this.fragment = context this.fragment = context
} }
inner class OpenFileOnClickViewListener : View.OnClickListener, View.OnLongClickListener { inner class SelectFileOnClickViewListener :
View.OnClickListener,
View.OnLongClickListener,
MenuItem.OnMenuItemClickListener {
private fun onAbstractClick(longClick: Boolean = false) { private fun onAbstractClick(longClick: Boolean = false) {
try { try {
@@ -85,6 +89,11 @@ class OpenFileHelper {
onAbstractClick(true) onAbstractClick(true)
return true return true
} }
override fun onMenuItemClick(item: MenuItem?): Boolean {
onAbstractClick()
return true
}
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")

View File

@@ -32,6 +32,18 @@ abstract class AnimatedItemsAdapter<Item, T: RecyclerView.ViewHolder>(val contex
onListSizeChangedListener?.invoke(previousSize, itemsList.size) onListSizeChangedListener?.invoke(previousSize, itemsList.size)
} }
open fun isEmpty(): Boolean {
return itemsList.isEmpty()
}
open fun contains(item: Item): Boolean {
return itemsList.contains(item)
}
open fun indexOf(item: Item): Int {
return itemsList.indexOf(item)
}
open fun putItem(item: Item) { open fun putItem(item: Item) {
val previousSize = itemsList.size val previousSize = itemsList.size
if (itemsList.contains(item)) { if (itemsList.contains(item)) {
@@ -46,13 +58,42 @@ abstract class AnimatedItemsAdapter<Item, T: RecyclerView.ViewHolder>(val contex
onListSizeChangedListener?.invoke(previousSize, itemsList.size) onListSizeChangedListener?.invoke(previousSize, itemsList.size)
} }
fun onBindDeleteButton(holder: T, deleteButton: View, item: Item, position: Int) { /**
* Only replace [oldItem] by [newItem] if [oldItem] exists
*/
open fun replaceItem(oldItem: Item, newItem: Item) {
if (itemsList.contains(oldItem)) {
val index = itemsList.indexOf(oldItem)
itemsList.removeAt(index)
itemsList.add(index, newItem)
notifyItemChanged(index)
}
}
/**
* Only remove [item] if doesn't exists
*/
open fun removeItem(item: Item) {
if (itemsList.contains(item)) {
mItemToRemove = item
notifyItemChanged(itemsList.indexOf(item))
}
}
protected fun performDeletion(holder: T, item: Item): Boolean {
val effectivelyDeletionPerformed = mItemToRemove == item
if (effectivelyDeletionPerformed) {
holder.itemView.collapse(true) {
deleteItem(item)
}
}
return effectivelyDeletionPerformed
}
protected fun onBindDeleteButton(holder: T, deleteButton: View, item: Item, position: Int) {
deleteButton.apply { deleteButton.apply {
visibility = View.VISIBLE visibility = View.VISIBLE
if (mItemToRemove == item) { if (performDeletion(holder, item)) {
holder.itemView.collapse(true) {
deleteItem(item)
}
setOnClickListener(null) setOnClickListener(null)
} else { } else {
setOnClickListener { setOnClickListener {

View File

@@ -23,36 +23,34 @@ import android.content.Context
import android.text.format.Formatter import android.text.format.Formatter
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView 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.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
class EntryAttachmentsItemsAdapter(context: Context, private val editable: Boolean) class EntryAttachmentsItemsAdapter(context: Context)
: AnimatedItemsAdapter<EntryAttachment, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) { : AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
var onItemClickListener: ((item: EntryAttachment)->Unit)? = null var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
private val mDatabase = Database.getInstance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false)) return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
} }
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) { override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
val entryAttachment = itemsList[position] val entryAttachmentState = itemsList[position]
holder.itemView.visibility = View.VISIBLE holder.itemView.visibility = View.VISIBLE
holder.binaryFileTitle.text = entryAttachment.name holder.binaryFileTitle.text = entryAttachmentState.attachment.name
holder.binaryFileSize.text = Formatter.formatFileSize(context, holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachment.binaryAttachment.length()) entryAttachmentState.attachment.binaryAttachment.length())
holder.binaryFileCompression.apply { holder.binaryFileCompression.apply {
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip if (entryAttachmentState.attachment.binaryAttachment.isCompressed) {
|| entryAttachment.binaryAttachment.isCompressed == true) {
text = CompressionAlgorithm.GZip.getName(context.resources) text = CompressionAlgorithm.GZip.getName(context.resources)
visibility = View.VISIBLE visibility = View.VISIBLE
} else { } else {
@@ -60,42 +58,60 @@ class EntryAttachmentsItemsAdapter(context: Context, private val editable: Boole
visibility = View.GONE visibility = View.GONE
} }
} }
if (editable) { when (entryAttachmentState.streamDirection) {
holder.binaryFileProgressContainer.visibility = View.GONE StreamDirection.UPLOAD -> {
holder.binaryFileDeleteButton.apply { holder.binaryFileProgressIcon.isActivated = true
visibility = View.VISIBLE when (entryAttachmentState.downloadState) {
onBindDeleteButton(holder, this, entryAttachment, position) AttachmentState.START,
} AttachmentState.IN_PROGRESS -> {
} else { holder.binaryFileProgressContainer.visibility = View.VISIBLE
holder.binaryFileProgressContainer.visibility = View.VISIBLE holder.binaryFileProgress.apply {
holder.binaryFileDeleteButton.visibility = View.GONE visibility = View.VISIBLE
holder.binaryFileProgress.apply { progress = entryAttachmentState.downloadProgression
visibility = when (entryAttachment.downloadState) { }
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE holder.binaryFileDeleteButton.apply {
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE visibility = View.GONE
setOnClickListener(null)
}
}
AttachmentState.NULL,
AttachmentState.ERROR,
AttachmentState.COMPLETE -> {
holder.binaryFileProgressContainer.visibility = View.GONE
holder.binaryFileProgress.visibility = View.GONE
holder.binaryFileDeleteButton.apply {
visibility = View.VISIBLE
onBindDeleteButton(holder, this, entryAttachmentState, position)
}
}
} }
progress = entryAttachment.downloadProgression holder.itemView.setOnClickListener(null)
} }
holder.itemView.setOnClickListener { StreamDirection.DOWNLOAD -> {
onItemClickListener?.invoke(entryAttachment) holder.binaryFileProgressIcon.isActivated = false
holder.binaryFileProgressContainer.visibility = View.VISIBLE
holder.binaryFileDeleteButton.visibility = View.GONE
holder.binaryFileProgress.apply {
visibility = when (entryAttachmentState.downloadState) {
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
}
progress = entryAttachmentState.downloadProgression
}
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryAttachmentState)
}
} }
} }
} }
fun updateProgress(entryAttachment: EntryAttachment) { class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val indexEntryAttachment = itemsList.indexOfLast { current -> current.name == entryAttachment.name }
if (indexEntryAttachment != -1) {
itemsList[indexEntryAttachment] = entryAttachment
notifyItemChanged(indexEntryAttachment)
}
}
inner class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title) var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size) var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
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 binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress) var binaryFileProgress: ProgressBar = 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

@@ -45,6 +45,8 @@ class EntryExtraFieldsItemsAdapter(context: Context)
private var mLastFocusedEditField = FocusedEditField() private var mLastFocusedEditField = FocusedEditField()
private var mLastFocusedTimestamp: Long = 0L private var mLastFocusedTimestamp: Long = 0L
var onEditButtonClickListener: ((item: Field)->Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryExtraFieldViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryExtraFieldViewHolder {
val view = EntryExtraFieldViewHolder( val view = EntryExtraFieldViewHolder(
inflater.inflate(R.layout.item_entry_edit_extra_field, parent, false) inflater.inflate(R.layout.item_entry_edit_extra_field, parent, false)
@@ -73,11 +75,10 @@ class EntryExtraFieldsItemsAdapter(context: Context)
setFocusField(extraField, selectionStart, selectionEnd) setFocusField(extraField, selectionStart, selectionEnd)
} else { } else {
// request focus on last text focused // request focus on last text focused
if (focusedTimestampNotExpired()) { if (focusedTimestampNotExpired())
requestFocusField(this, extraField, false) requestFocusField(this, extraField, false)
} else { else
removeFocusField(extraField) removeFocusField(extraField)
}
} }
} }
addOnSelectionChangedListener(object: EditTextSelectable.OnSelectionChangedListener { addOnSelectionChangedListener(object: EditTextSelectable.OnSelectionChangedListener {
@@ -95,9 +96,10 @@ class EntryExtraFieldsItemsAdapter(context: Context)
if (applyFontVisibility) if (applyFontVisibility)
applyFontVisibility() applyFontVisibility()
} }
holder.extraFieldDeleteButton.apply { holder.extraFieldEditButton.setOnClickListener {
onBindDeleteButton(holder, this, extraField, position) onEditButtonClickListener?.invoke(extraField)
} }
performDeletion(holder, extraField)
} }
fun assignItems(items: List<Field>, focusedEditField: FocusedEditField?) { fun assignItems(items: List<Field>, focusedEditField: FocusedEditField?) {
@@ -107,15 +109,6 @@ class EntryExtraFieldsItemsAdapter(context: Context)
super.assignItems(items) super.assignItems(items)
} }
override fun putItem(item: Field) {
setFocusField(mLastFocusedEditField.apply {
field = item
cursorSelectionStart = -1
cursorSelectionEnd = -1
}, true)
super.putItem(item)
}
private fun setFocusField(field: Field, private fun setFocusField(field: Field,
selectionStart: Int, selectionStart: Int,
selectionEnd: Int, selectionEnd: Int,
@@ -147,6 +140,7 @@ class EntryExtraFieldsItemsAdapter(context: Context)
setEditTextSelection(editText) setEditTextSelection(editText)
} }
requestFocus() requestFocus()
removeFocusField(field)
} }
} }
} }
@@ -176,7 +170,7 @@ class EntryExtraFieldsItemsAdapter(context: Context)
class EntryExtraFieldViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class EntryExtraFieldViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var extraFieldValueContainer: TextInputLayout = itemView.findViewById(R.id.entry_extra_field_value_container) var extraFieldValueContainer: TextInputLayout = itemView.findViewById(R.id.entry_extra_field_value_container)
var extraFieldValue: EditTextSelectable = itemView.findViewById(R.id.entry_extra_field_value) var extraFieldValue: EditTextSelectable = itemView.findViewById(R.id.entry_extra_field_value)
var extraFieldDeleteButton: View = itemView.findViewById(R.id.entry_extra_field_delete) var extraFieldEditButton: View = itemView.findViewById(R.id.entry_extra_field_edit)
} }
companion object { companion object {

View File

@@ -101,9 +101,9 @@ class FileDatabaseHistoryAdapter(context: Context)
// Modification // Modification
databaseFile.databaseLastModified?.let { databaseFile.databaseLastModified?.let {
holder.fileModification.text = it holder.fileModification.text = it
holder.fileModification.visibility = View.VISIBLE holder.fileModificationContainer.visibility = View.VISIBLE
} ?: run { } ?: run {
holder.fileModification.visibility = View.GONE holder.fileModificationContainer.visibility = View.GONE
} }
// Size // Size
@@ -234,7 +234,7 @@ class FileDatabaseHistoryAdapter(context: Context)
this.saveAliasListener = listener this.saveAliasListener = listener
} }
inner class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class FileDatabaseHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var fileContainer: ViewGroup = itemView.findViewById(R.id.file_container_basic_info) var fileContainer: ViewGroup = itemView.findViewById(R.id.file_container_basic_info)
@@ -250,6 +250,7 @@ class FileDatabaseHistoryAdapter(context: Context)
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button) var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button) var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
var filePath: TextView = itemView.findViewById(R.id.file_path) var filePath: TextView = itemView.findViewById(R.id.file_path)
var fileModificationContainer: ViewGroup = itemView.findViewById(R.id.file_modification_container)
var fileModification: TextView = itemView.findViewById(R.id.file_modification) var fileModification: TextView = itemView.findViewById(R.id.file_modification)
var fileSize: TextView = itemView.findViewById(R.id.file_size) var fileSize: TextView = itemView.findViewById(R.id.file_size)
} }

View File

@@ -338,6 +338,9 @@ class NodeAdapter (private val context: Context)
} }
} }
holder.attachmentIcon?.visibility =
if (entry.containsAttachment()) View.VISIBLE else View.GONE
mDatabase.stopManageEntry(entry) mDatabase.stopManageEntry(entry)
} }
@@ -391,6 +394,7 @@ class NodeAdapter (private val context: Context)
var text: TextView = itemView.findViewById(R.id.node_text) var text: TextView = itemView.findViewById(R.id.node_text)
var subText: TextView = itemView.findViewById(R.id.node_subtext) var subText: TextView = itemView.findViewById(R.id.node_subtext)
var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers) var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers)
var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon)
} }
companion object { companion object {

View File

@@ -119,15 +119,19 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
IOActionTask( IOActionTask(
{ {
databaseFileToAddOrUpdate.databaseUri?.let { databaseUri -> databaseFileToAddOrUpdate.databaseUri?.let { databaseUri ->
// Try to get info in database first
val fileDatabaseHistoryRetrieve = databaseFileHistoryDao.getByDatabaseUri(databaseUri.toString())
// Complete alias if not exists
val fileDatabaseHistory = FileDatabaseHistoryEntity( val fileDatabaseHistory = FileDatabaseHistoryEntity(
databaseUri.toString(), databaseUri.toString(),
databaseFileToAddOrUpdate.databaseAlias ?: "", databaseFileToAddOrUpdate.databaseAlias
?: fileDatabaseHistoryRetrieve?.databaseAlias
?: "",
databaseFileToAddOrUpdate.keyFileUri?.toString(), databaseFileToAddOrUpdate.keyFileUri?.toString(),
System.currentTimeMillis() System.currentTimeMillis()
) )
val fileDatabaseHistoryRetrieve = databaseFileHistoryDao.getByDatabaseUri(fileDatabaseHistory.databaseUri)
// Update values if history element not yet in the database // Update values if history element not yet in the database
if (fileDatabaseHistoryRetrieve == null) { if (fileDatabaseHistoryRetrieve == null) {
databaseFileHistoryDao.add(fileDatabaseHistory) databaseFileHistoryDao.add(fileDatabaseHistory)

View File

@@ -34,7 +34,7 @@ class DeleteEntryHistoryDatabaseRunnable (
override fun onStartRun() { override fun onStartRun() {
try { try {
mainEntry.removeEntryFromHistory(entryHistoryPosition) database.removeEntryHistory(mainEntry, entryHistoryPosition)
} catch (e: Exception) { } catch (e: Exception) {
setError(e) setError(e)
} }

View File

@@ -64,6 +64,10 @@ class DeleteNodesRunnable(context: Context,
} else { } else {
database.deleteEntry(currentNode) database.deleteEntry(currentNode)
} }
// Remove the oldest attachments
currentNode.getAttachments(database.binaryPool).forEach {
database.removeAttachmentIfNotUsed(it)
}
} }
} }
} }

View File

@@ -20,6 +20,7 @@
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.Attachment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
@@ -40,16 +41,34 @@ class UpdateEntryRunnable constructor(
// WARNING : Re attribute parent removed in entry edit activity to save memory // WARNING : Re attribute parent removed in entry edit activity to save memory
mNewEntry.addParentFrom(mOldEntry) mNewEntry.addParentFrom(mOldEntry)
// Build oldest attachments
val oldEntryAttachments = mOldEntry.getAttachments(database.binaryPool, true)
val newEntryAttachments = mNewEntry.getAttachments(database.binaryPool, true)
val attachmentsToRemove = ArrayList<Attachment>(oldEntryAttachments)
// Not use equals because only check name
newEntryAttachments.forEach { newAttachment ->
oldEntryAttachments.forEach { oldAttachment ->
if (oldAttachment.name == newAttachment.name
&& oldAttachment.binaryAttachment == newAttachment.binaryAttachment)
attachmentsToRemove.remove(oldAttachment)
}
}
// Update entry with new values // Update entry with new values
mOldEntry.updateWith(mNewEntry) mOldEntry.updateWith(mNewEntry)
mNewEntry.touch(modified = true, touchParents = true) mNewEntry.touch(modified = true, touchParents = true)
// Create an entry history (an entry history don't have history) // Create an entry history (an entry history don't have history)
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false)) mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
database.removeOldestEntryHistory(mOldEntry) database.removeOldestEntryHistory(mOldEntry, database.binaryPool)
// Only change data in index // Only change data in index
database.updateEntry(mOldEntry) database.updateEntry(mOldEntry)
// Remove oldest attachments
attachmentsToRemove.forEach {
database.removeAttachmentIfNotUsed(it)
}
} }
override fun nodeFinish(): ActionNodesValues { override fun nodeFinish(): ActionNodesValues {

View File

@@ -0,0 +1,69 @@
/*
* 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.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
data class Attachment(var name: String,
var binaryAttachment: BinaryAttachment) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readParcelable(BinaryAttachment::class.java.classLoader) ?: BinaryAttachment()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeParcelable(binaryAttachment, flags)
}
override fun describeContents(): Int {
return 0
}
override fun toString(): String {
return "$name at $binaryAttachment"
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Attachment) return false
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
return name.hashCode()
}
companion object CREATOR : Parcelable.Creator<Attachment> {
override fun createFromParcel(parcel: Parcel): Attachment {
return Attachment(parcel)
}
override fun newArray(size: Int): Array<Attachment?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -25,9 +25,7 @@ import android.net.Uri
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.*
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageFactory import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
@@ -52,6 +50,7 @@ import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import java.io.* import java.io.*
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class Database { class Database {
@@ -157,6 +156,17 @@ class Database {
} }
} }
fun compressionForNewEntry(): Boolean {
if (mDatabaseKDB != null)
return false
// Default compression not necessary if stored in header
mDatabaseKDBX?.let {
return it.compressionAlgorithm == CompressionAlgorithm.GZip
&& it.kdbxVersion.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()
}
return false
}
fun updateDataBinaryCompression(oldCompression: CompressionAlgorithm, fun updateDataBinaryCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm) { newCompression: CompressionAlgorithm) {
mDatabaseKDBX?.changeBinaryCompression(oldCompression, newCompression) mDatabaseKDBX?.changeBinaryCompression(oldCompression, newCompression)
@@ -268,14 +278,14 @@ class Database {
} }
/** /**
* Determine if RecycleBin is available or not for this version of database * Determine if a configurable RecycleBin is available or not for this version of database
* @return true if RecycleBin available * @return true if a configurable RecycleBin available
*/ */
val allowRecycleBin: Boolean val allowConfigurableRecycleBin: Boolean
get() = mDatabaseKDBX != null get() = mDatabaseKDBX != null
var isRecycleBinEnabled: Boolean var isRecycleBinEnabled: Boolean
// TODO #394 isRecycleBinEnabled mDatabaseKDB // Backup is always enabled in KDB database
get() = mDatabaseKDB != null || mDatabaseKDBX?.isRecycleBinEnabled ?: false get() = mDatabaseKDB != null || mDatabaseKDBX?.isRecycleBinEnabled ?: false
set(value) { set(value) {
mDatabaseKDBX?.isRecycleBinEnabled = value mDatabaseKDBX?.isRecycleBinEnabled = value
@@ -293,12 +303,12 @@ class Database {
} }
fun ensureRecycleBinExists(resources: Resources) { fun ensureRecycleBinExists(resources: Resources) {
mDatabaseKDB?.ensureRecycleBinExists() mDatabaseKDB?.ensureBackupExists()
mDatabaseKDBX?.ensureRecycleBinExists(resources) mDatabaseKDBX?.ensureRecycleBinExists(resources)
} }
fun removeRecycleBin() { fun removeRecycleBin() {
// TODO #394 delete backup mDatabaseKDB?.removeRecycleBin() // Don't allow remove backup in KDB
mDatabaseKDBX?.removeRecycleBin() mDatabaseKDBX?.removeRecycleBin()
} }
@@ -428,6 +438,37 @@ class Database {
}, omitBackup, max) }, omitBackup, max)
} }
val binaryPool: BinaryPool
get() {
return mDatabaseKDBX?.binaryPool ?: BinaryPool()
}
val allowMultipleAttachments: Boolean
get() {
if (mDatabaseKDB != null)
return false
if (mDatabaseKDBX != null)
return true
return false
}
fun buildNewBinary(cacheDirectory: File,
enableProtection: Boolean = false,
compressed: Boolean = false): BinaryAttachment? {
return mDatabaseKDB?.buildNewBinary(cacheDirectory)
?: mDatabaseKDBX?.buildNewBinary(cacheDirectory, enableProtection, compressed)
}
fun removeAttachmentIfNotUsed(attachment: Attachment) {
// No need in KDB database because unique attachment by entry
mDatabaseKDBX?.removeAttachmentIfNotUsed(attachment)
}
fun removeUnlinkedAttachments() {
// No check in database KDB because unique attachment by entry
mDatabaseKDBX?.removeUnlinkedAttachments()
}
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
fun saveData(contentResolver: ContentResolver) { fun saveData(contentResolver: ContentResolver) {
try { try {
@@ -473,7 +514,7 @@ class Database {
} else { } else {
var outputStream: OutputStream? = null var outputStream: OutputStream? = null
try { try {
outputStream = contentResolver.openOutputStream(uri) outputStream = contentResolver.openOutputStream(uri, "rwt")
outputStream?.let { definedOutputStream -> outputStream?.let { definedOutputStream ->
val databaseOutput = mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) } val databaseOutput = mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, definedOutputStream) } ?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, definedOutputStream) }
@@ -718,7 +759,7 @@ class Database {
fun canRecycle(entry: Entry): Boolean { fun canRecycle(entry: Entry): Boolean {
var canRecycle: Boolean? = null var canRecycle: Boolean? = null
entry.entryKDB?.let { entry.entryKDB?.let {
canRecycle = mDatabaseKDB?.canRecycle() canRecycle = mDatabaseKDB?.canRecycle(it)
} }
entry.entryKDBX?.let { entry.entryKDBX?.let {
canRecycle = mDatabaseKDBX?.canRecycle(it) canRecycle = mDatabaseKDBX?.canRecycle(it)
@@ -729,7 +770,7 @@ class Database {
fun canRecycle(group: Group): Boolean { fun canRecycle(group: Group): Boolean {
var canRecycle: Boolean? = null var canRecycle: Boolean? = null
group.groupKDB?.let { group.groupKDB?.let {
canRecycle = mDatabaseKDB?.canRecycle() canRecycle = mDatabaseKDB?.canRecycle(it)
} }
group.groupKDBX?.let { group.groupKDBX?.let {
canRecycle = mDatabaseKDBX?.canRecycle(it) canRecycle = mDatabaseKDBX?.canRecycle(it)
@@ -800,7 +841,7 @@ class Database {
rootGroup?.doForEachChildAndForIt( rootGroup?.doForEachChildAndForIt(
object : NodeHandler<Entry>() { object : NodeHandler<Entry>() {
override fun operate(node: Entry): Boolean { override fun operate(node: Entry): Boolean {
removeOldestEntryHistory(node) removeOldestEntryHistory(node, binaryPool)
return true return true
} }
}, },
@@ -808,34 +849,19 @@ class Database {
override fun operate(node: Group): Boolean { override fun operate(node: Group): Boolean {
return true return true
} }
}) }
} )
fun removeEachEntryHistory() {
rootGroup?.doForEachChildAndForIt(
object : NodeHandler<Entry>() {
override fun operate(node: Entry): Boolean {
node.removeAllHistory()
return true
}
},
object : NodeHandler<Group>() {
override fun operate(node: Group): Boolean {
return true
}
})
} }
/** /**
* Remove oldest history if more than max items or max memory * Remove oldest history if more than max items or max memory
*/ */
fun removeOldestEntryHistory(entry: Entry) { fun removeOldestEntryHistory(entry: Entry, binaryPool: BinaryPool) {
mDatabaseKDBX?.let { mDatabaseKDBX?.let {
val maxItems = historyMaxItems val maxItems = historyMaxItems
if (maxItems >= 0) { if (maxItems >= 0) {
while (entry.getHistory().size > maxItems) { while (entry.getHistory().size > maxItems) {
entry.removeOldestEntryFromHistory() removeOldestEntryHistory(entry)
} }
} }
@@ -844,11 +870,10 @@ class Database {
while (true) { while (true) {
var historySize: Long = 0 var historySize: Long = 0
for (entryHistory in entry.getHistory()) { for (entryHistory in entry.getHistory()) {
historySize += entryHistory.getSize() historySize += entryHistory.getSize(binaryPool)
} }
if (historySize > maxSize) { if (historySize > maxSize) {
entry.removeOldestEntryFromHistory() removeOldestEntryHistory(entry)
} else { } else {
break break
} }
@@ -857,6 +882,22 @@ class Database {
} }
} }
private fun removeOldestEntryHistory(entry: Entry) {
entry.removeOldestEntryFromHistory()?.let {
it.getAttachments(binaryPool, false).forEach { attachmentToRemove ->
removeAttachmentIfNotUsed(attachmentToRemove)
}
}
}
fun removeEntryHistory(entry: Entry, entryHistoryPosition: Int) {
entry.removeEntryFromHistory(entryHistoryPosition)?.let {
it.getAttachments(binaryPool, false).forEach { attachmentToRemove ->
removeAttachmentIfNotUsed(attachmentToRemove)
}
}
}
companion object : SingletonHolder<Database>(::Database) { companion object : SingletonHolder<Database>(::Database) {
private val TAG = Database::class.java.name private val TAG = Database::class.java.name

View File

@@ -21,6 +21,7 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
@@ -33,7 +34,6 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
@@ -317,40 +317,38 @@ class Entry : Node, EntryVersionedInterface<Group> {
} }
} }
fun startToManageFieldReferences(db: DatabaseKDBX) { fun startToManageFieldReferences(database: DatabaseKDBX) {
entryKDBX?.startToManageFieldReferences(db) entryKDBX?.startToManageFieldReferences(database)
} }
fun stopToManageFieldReferences() { fun stopToManageFieldReferences() {
entryKDBX?.stopToManageFieldReferences() entryKDBX?.stopToManageFieldReferences()
} }
fun getAttachments(): ArrayList<EntryAttachment> { fun getAttachments(binaryPool: BinaryPool, inHistory: Boolean = false): List<Attachment> {
val attachments = ArrayList<EntryAttachment>() val attachments = ArrayList<Attachment>()
entryKDB?.getAttachment()?.let {
entryKDB?.binaryData?.let { binaryKDB -> attachments.add(it)
attachments.add(EntryAttachment(entryKDB?.binaryDescription ?: "", binaryKDB))
} }
entryKDBX?.getAttachments(binaryPool, inHistory)?.let {
entryKDBX?.binaries?.let { binariesKDBX -> attachments.addAll(it)
for ((key, value) in binariesKDBX) {
attachments.add(EntryAttachment(key, value))
}
} }
return attachments return attachments
} }
fun removeAttachment(attachment: EntryAttachment) { fun containsAttachment(): Boolean {
entryKDB?.apply { return entryKDB?.containsAttachment() == true
if (binaryDescription == attachment.name || entryKDBX?.containsAttachment() == true
&& binaryData == attachment.binaryAttachment) { }
binaryDescription = ""
binaryData = null
}
}
entryKDBX?.removeProtectedBinary(attachment.name) fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
entryKDB?.putAttachment(attachment)
entryKDBX?.putAttachment(attachment, binaryPool)
}
fun removeAttachment(attachment: Attachment) {
entryKDB?.removeAttachment(attachment)
entryKDBX?.removeAttachment(attachment)
} }
fun getHistory(): ArrayList<Entry> { fun getHistory(): ArrayList<Entry> {
@@ -368,20 +366,22 @@ class Entry : Node, EntryVersionedInterface<Group> {
} }
} }
fun removeEntryFromHistory(position: Int) { fun removeEntryFromHistory(position: Int): Entry? {
entryKDBX?.removeEntryFromHistory(position) entryKDBX?.removeEntryFromHistory(position)?.let {
return Entry(it)
}
return null
} }
fun removeAllHistory() { fun removeOldestEntryFromHistory(): Entry? {
entryKDBX?.removeAllHistory() entryKDBX?.removeOldestEntryFromHistory()?.let {
return Entry(it)
}
return null
} }
fun removeOldestEntryFromHistory() { fun getSize(binaryPool: BinaryPool): Long {
entryKDBX?.removeOldestEntryFromHistory() return entryKDBX?.getSize(binaryPool) ?: 0L
}
fun getSize(): Long {
return entryKDBX?.size ?: 0L
} }
fun containsCustomData(): Boolean { fun containsCustomData(): Boolean {

View File

@@ -17,10 +17,8 @@
* 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.element.security package com.kunzisoft.keepass.database.element.database
import android.content.ContentResolver
import android.net.Uri
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.stream.readBytes import com.kunzisoft.keepass.stream.readBytes
@@ -30,7 +28,7 @@ import java.util.zip.GZIPOutputStream
class BinaryAttachment : Parcelable { class BinaryAttachment : Parcelable {
var isCompressed: Boolean? = null var isCompressed: Boolean = false
private set private set
var isProtected: Boolean = false var isProtected: Boolean = false
private set private set
@@ -46,12 +44,12 @@ class BinaryAttachment : Parcelable {
* Empty protected binary * Empty protected binary
*/ */
constructor() { constructor() {
this.isCompressed = null this.isCompressed = false
this.isProtected = false this.isProtected = false
this.dataFile = null this.dataFile = null
} }
constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean? = null) { constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean = false) {
this.isCompressed = compressed this.isCompressed = compressed
this.isProtected = enableProtection this.isProtected = enableProtection
this.dataFile = dataFile this.dataFile = dataFile
@@ -59,7 +57,7 @@ class BinaryAttachment : Parcelable {
private constructor(parcel: Parcel) { private constructor(parcel: Parcel) {
val compressedByte = parcel.readByte().toInt() val compressedByte = parcel.readByte().toInt()
isCompressed = if (compressedByte == 2) null else compressedByte != 0 isCompressed = compressedByte != 0
isProtected = parcel.readByte().toInt() != 0 isProtected = parcel.readByte().toInt() != 0
parcel.readString()?.let { parcel.readString()?.let {
dataFile = File(it) dataFile = File(it)
@@ -74,32 +72,51 @@ class BinaryAttachment : Parcelable {
} }
} }
@Throws(IOException::class)
fun getUnGzipInputDataStream(): InputStream {
return if (isCompressed)
GZIPInputStream(getInputDataStream())
else
getInputDataStream()
}
@Throws(IOException::class)
fun getOutputDataStream(): OutputStream {
return when {
dataFile != null -> FileOutputStream(dataFile!!)
else -> throw IOException("Unable to write in an unknown file")
}
}
@Throws(IOException::class)
fun getGzipOutputDataStream(): OutputStream {
return if (isCompressed) {
GZIPOutputStream(getOutputDataStream())
} else {
getOutputDataStream()
}
}
@Throws(IOException::class) @Throws(IOException::class)
fun compress(bufferSize: Int = DEFAULT_BUFFER_SIZE) { fun compress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
dataFile?.let { concreteDataFile -> dataFile?.let { concreteDataFile ->
// To compress, create a new binary with file // To compress, create a new binary with file
if (isCompressed != true) { if (!isCompressed) {
val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
var outputStream: GZIPOutputStream? = null GZIPOutputStream(FileOutputStream(fileBinaryCompress)).use { outputStream ->
var inputStream: InputStream? = null getInputDataStream().use { inputStream ->
try { inputStream.readBytes(bufferSize) { buffer ->
outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress)) outputStream.write(buffer)
inputStream = getInputDataStream()
inputStream.readBytes(bufferSize) { buffer ->
outputStream.write(buffer)
}
} finally {
inputStream?.close()
outputStream?.close()
// Remove unGzip file
if (concreteDataFile.delete()) {
if (fileBinaryCompress.renameTo(concreteDataFile)) {
// Harmonize with database compression
isCompressed = true
} }
} }
} }
// Remove unGzip file
if (concreteDataFile.delete()) {
if (fileBinaryCompress.renameTo(concreteDataFile)) {
// Harmonize with database compression
isCompressed = true
}
}
} }
} }
} }
@@ -107,52 +124,20 @@ class BinaryAttachment : Parcelable {
@Throws(IOException::class) @Throws(IOException::class)
fun decompress(bufferSize: Int = DEFAULT_BUFFER_SIZE) { fun decompress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
dataFile?.let { concreteDataFile -> dataFile?.let { concreteDataFile ->
if (isCompressed != false) { if (isCompressed) {
val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
var outputStream: FileOutputStream? = null FileOutputStream(fileBinaryDecompress).use { outputStream ->
var inputStream: GZIPInputStream? = null getUnGzipInputDataStream().use { inputStream ->
try { inputStream.readBytes(bufferSize) { buffer ->
outputStream = FileOutputStream(fileBinaryDecompress) outputStream.write(buffer)
inputStream = GZIPInputStream(getInputDataStream())
inputStream.readBytes(bufferSize) { buffer ->
outputStream.write(buffer)
}
} finally {
inputStream?.close()
outputStream?.close()
// Remove gzip file
if (concreteDataFile.delete()) {
if (fileBinaryDecompress.renameTo(concreteDataFile)) {
// Harmonize with database compression
isCompressed = false
} }
} }
} }
} // Remove gzip file
} if (concreteDataFile.delete()) {
} if (fileBinaryDecompress.renameTo(concreteDataFile)) {
// Harmonize with database compression
fun download(createdFileUri: Uri, isCompressed = false
contentResolver: ContentResolver,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
update: ((percent: Int)->Unit)? = null) {
var dataDownloaded = 0
contentResolver.openOutputStream(createdFileUri).use { outputStream ->
outputStream?.let { fileOutputStream ->
if (isCompressed == true) {
GZIPInputStream(getInputDataStream())
} else {
getInputDataStream()
}.use { inputStream ->
inputStream.readBytes(bufferSize) { buffer ->
fileOutputStream.write(buffer)
dataDownloaded += buffer.size
try {
val percentDownload = (100 * dataDownloaded / length()).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {}
} }
} }
} }
@@ -185,18 +170,22 @@ class BinaryAttachment : Parcelable {
override fun hashCode(): Int { override fun hashCode(): Int {
var result = 0 var result = 0
result = 31 * result + if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0 result = 31 * result + if (isCompressed) 1 else 0
result = 31 * result + if (isProtected) 1 else 0 result = 31 * result + if (isProtected) 1 else 0
result = 31 * result + dataFile!!.hashCode() result = 31 * result + dataFile!!.hashCode()
return result return result
} }
override fun toString(): String {
return dataFile.toString()
}
override fun describeContents(): Int { override fun describeContents(): Int {
return 0 return 0
} }
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0).toByte()) dest.writeByte((if (isCompressed) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte()) dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeString(dataFile?.absolutePath) dest.writeString(dataFile?.absolutePath)
} }

View File

@@ -19,52 +19,126 @@
*/ */
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.database
import android.util.SparseArray
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import java.io.IOException import java.io.IOException
class BinaryPool { class BinaryPool {
private val pool = SparseArray<BinaryAttachment>() private val pool = LinkedHashMap<Int, BinaryAttachment>()
/**
* To get a binary by the pool key (ref attribute in entry)
*/
operator fun get(key: Int): BinaryAttachment? { operator fun get(key: Int): BinaryAttachment? {
return pool[key] return pool[key]
} }
fun put(key: Int, value: BinaryAttachment) { /**
pool.put(key, value) * To linked a binary with a pool key, if the pool key doesn't exists, create an unused one
*/
fun put(key: Int?, value: BinaryAttachment) {
if (key == null)
put(value)
else
pool[key] = value
} }
fun doForEachBinary(action: (key: Int, binary: BinaryAttachment) -> Unit) { /**
for (i in 0 until pool.size()) { * To put a [binaryAttachment] in the pool,
action.invoke(i, pool.get(pool.keyAt(i))) * if already exists, replace the current one,
* else add it with a new key
*/
fun put(binaryAttachment: BinaryAttachment): Int {
var key = findKey(binaryAttachment)
if (key == null) {
key = findUnusedKey()
} }
pool[key] = binaryAttachment
return key
} }
/**
* Remove a binary from the pool, the file is not deleted
*/
@Throws(IOException::class) @Throws(IOException::class)
fun clear() { fun remove(binaryAttachment: BinaryAttachment) {
doForEachBinary { _, binary -> findKey(binaryAttachment)?.let {
binary.clear() pool.remove(it)
} }
pool.clear() // Don't clear attachment here because a file can be used in many BinaryAttachment
} }
fun add(fileBinary: BinaryAttachment) { /**
if (findKey(fileBinary) == null) { * Utility method to find an unused key in the pool
pool.put(findUnusedKey(), fileBinary) */
} private fun findUnusedKey(): Int {
} var unusedKey = 0
while (pool[unusedKey] != null)
fun findUnusedKey(): Int {
var unusedKey = pool.size()
while (get(unusedKey) != null)
unusedKey++ unusedKey++
return unusedKey return unusedKey
} }
fun findKey(pb: BinaryAttachment): Int? { /**
for (i in 0 until pool.size()) { * Return key of [binaryAttachmentToRetrieve] or null if not found
if (pool.get(pool.keyAt(i)) == pb) return i */
private fun findKey(binaryAttachmentToRetrieve: BinaryAttachment): Int? {
val contains = pool.containsValue(binaryAttachmentToRetrieve)
return if (!contains)
null
else {
for ((key, binary) in pool) {
if (binary == binaryAttachmentToRetrieve) {
return key
}
}
return null
} }
return null
} }
/**
* Utility method to order binaries and solve index problem in database v4
*/
private fun orderedBinaries(): List<KeyBinary> {
val keyBinaryList = ArrayList<KeyBinary>()
for ((key, binary) in pool) {
keyBinaryList.add(KeyBinary(key, binary))
}
return keyBinaryList
}
/**
* To register a binary with a ref corresponding to an ordered index
*/
fun getBinaryIndexFromKey(key: Int): Int? {
val index = orderedBinaries().indexOfFirst { it.key == key }
return if (index < 0)
null
else
index
}
/**
* Different from doForEach, provide an ordered index to each binary
*/
fun doForEachOrderedBinary(action: (index: Int, keyBinary: KeyBinary) -> Unit) {
orderedBinaries().forEachIndexed(action)
}
/**
* To do an action on each binary in the pool
*/
fun doForEachBinary(action: (binary: BinaryAttachment) -> Unit) {
pool.values.forEach { action.invoke(it) }
}
@Throws(IOException::class)
fun clear() {
doForEachBinary {
it.clear()
}
pool.clear()
}
/**
* Utility data class to order binaries
*/
data class KeyBinary(val key: Int, val binary: BinaryAttachment)
} }

View File

@@ -26,8 +26,10 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.DigestOutputStream import java.security.DigestOutputStream
@@ -38,7 +40,7 @@ import kotlin.collections.ArrayList
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() { class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID private var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
private var kdfListV3: MutableList<KdfEngine> = ArrayList() private var kdfListV3: MutableList<KdfEngine> = ArrayList()
@@ -57,7 +59,14 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
// Retrieve backup group in index // Retrieve backup group in index
val backupGroup: GroupKDB? val backupGroup: GroupKDB?
get() = if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID) null else getGroupById(backupGroupId) get() {
if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
ensureBackupExists()
return if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
null
else
getGroupById(backupGroupId)
}
override val kdfEngine: KdfEngine? override val kdfEngine: KdfEngine?
get() = kdfListV3[0] get() = kdfListV3[0]
@@ -192,10 +201,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
} }
/** /**
* Ensure that the recycle bin tree exists, if enabled and create it * Ensure that the backup tree exists if enabled, and create it
* if it doesn't exist * if it doesn't exist
*/ */
fun ensureRecycleBinExists() { fun ensureBackupExists() {
rootGroups.forEach { currentGroup -> rootGroups.forEach { currentGroup ->
if (currentGroup.level == 0 if (currentGroup.level == 0
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) { && currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
@@ -219,21 +228,25 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
* @param node Node to remove * @param node Node to remove
* @return true if node can be recycle, false elsewhere * @return true if node can be recycle, false elsewhere
*/ */
// TODO #394 Backup KDB fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
// fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean { if (node == backupGroup)
fun canRecycle(): Boolean { return false
backupGroup?.let {
if (node.isContainedIn(it))
return false
}
return true return true
} }
fun recycle(group: GroupKDB) { fun recycle(group: GroupKDB) {
ensureRecycleBinExists() ensureBackupExists()
removeGroupFrom(group, group.parent) removeGroupFrom(group, group.parent)
addGroupTo(group, backupGroup) addGroupTo(group, backupGroup)
group.afterAssignNewParent() group.afterAssignNewParent()
} }
fun recycle(entry: EntryKDB) { fun recycle(entry: EntryKDB) {
ensureRecycleBinExists() ensureBackupExists()
removeEntryFrom(entry, entry.parent) removeEntryFrom(entry, entry.parent)
addEntryTo(entry, backupGroup) addEntryTo(entry, backupGroup)
entry.afterAssignNewParent() entry.afterAssignNewParent()
@@ -249,6 +262,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
addEntryTo(entry, origParent) addEntryTo(entry, origParent)
} }
fun buildNewBinary(cacheDirectory: File): BinaryAttachment {
// Generate an unique new file with timestamp
val fileInCache = File(cacheDirectory, System.currentTimeMillis().toString())
return BinaryAttachment(fileInCache)
}
companion object { companion object {
val TYPE = DatabaseKDB::class.java val TYPE = DatabaseKDB::class.java

View File

@@ -29,8 +29,10 @@ import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
@@ -40,12 +42,14 @@ import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.utils.VariantDictionary
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.Text import org.w3c.dom.Text
import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
@@ -173,33 +177,51 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
fun changeBinaryCompression(oldCompression: CompressionAlgorithm, fun changeBinaryCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm) { newCompression: CompressionAlgorithm) {
binaryPool.doForEachBinary { key, binary -> when (oldCompression) {
CompressionAlgorithm.None -> {
try { when (newCompression) {
when (oldCompression) { CompressionAlgorithm.None -> {}
CompressionAlgorithm.None -> {
when (newCompression) {
CompressionAlgorithm.None -> {
}
CompressionAlgorithm.GZip -> {
// To compress, create a new binary with file
binary.compress(BUFFER_SIZE_BYTES)
}
}
}
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
when (newCompression) { // Only in databaseV3.1, in databaseV4 the header is zipped during the save
CompressionAlgorithm.None -> { if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
// To decompress, create a new binary with file compressAllBinaries()
binary.decompress(BUFFER_SIZE_BYTES)
}
CompressionAlgorithm.GZip -> {
}
} }
} }
} }
}
CompressionAlgorithm.GZip -> {
// In databaseV4 the header is zipped during the save, so not necessary here
if (kdbxVersion.toKotlinLong() >= FILE_VERSION_32_4.toKotlinLong()) {
decompressAllBinaries()
} else {
when (newCompression) {
CompressionAlgorithm.None -> {
decompressAllBinaries()
}
CompressionAlgorithm.GZip -> {}
}
}
}
}
}
private fun compressAllBinaries() {
binaryPool.doForEachBinary { binary ->
try {
// To compress, create a new binary with file
binary.compress(BUFFER_SIZE_BYTES)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to change compression for $key") Log.e(TAG, "Unable to compress $binary", e)
}
}
}
private fun decompressAllBinaries() {
binaryPool.doForEachBinary { binary ->
try {
binary.decompress(BUFFER_SIZE_BYTES)
} catch (e: Exception) {
Log.e(TAG, "Unable to decompress $binary", e)
} }
} }
} }
@@ -536,6 +558,52 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return publicCustomData.size() > 0 return publicCustomData.size() > 0
} }
fun buildNewBinary(cacheDirectory: File,
protection: Boolean,
compression: Boolean,
binaryPoolId: Int? = null): BinaryAttachment {
// New file with current time
val fileInCache = File(cacheDirectory, System.currentTimeMillis().toString())
val binaryAttachment = BinaryAttachment(fileInCache, protection, compression)
// add attachment to pool
binaryPool.put(binaryPoolId, binaryAttachment)
return binaryAttachment
}
fun removeAttachmentIfNotUsed(attachment: Attachment) {
// Remove attachment from pool
removeUnlinkedAttachments(attachment.binaryAttachment)
}
fun removeUnlinkedAttachments(vararg binaries: BinaryAttachment) {
// Build binaries to remove with all binaries known
val binariesToRemove = ArrayList<BinaryAttachment>()
if (binaries.isEmpty()) {
binaryPool.doForEachBinary { binary ->
binariesToRemove.add(binary)
}
} else {
binariesToRemove.addAll(binaries)
}
// Remove binaries from the list
rootGroup?.doForEachChild(object : NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean {
node.getAttachments(binaryPool, true).forEach {
binariesToRemove.remove(it.binaryAttachment)
}
return binariesToRemove.isNotEmpty()
}
}, null)
// Effective removing
binariesToRemove.forEach {
try {
binaryPool.remove(it)
} catch (e: Exception) {
Log.w(TAG, "Unable to clean binaries", e)
}
}
}
override fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean { override fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
if (password == null) if (password == null)
return true return true

View File

@@ -26,8 +26,10 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.Attachment
import java.util.* import java.util.*
import kotlin.collections.ArrayList
/** /**
* Structure containing information about one entry. * Structure containing information about one entry.
@@ -135,6 +137,29 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override val type: Type override val type: Type
get() = Type.ENTRY get() = Type.ENTRY
fun getAttachment(): Attachment? {
val binary = binaryData
return if (binary != null)
Attachment(binaryDescription, binary)
else null
}
fun containsAttachment(): Boolean {
return binaryData != null
}
fun putAttachment(attachment: Attachment) {
this.binaryDescription = attachment.name
this.binaryData = attachment.binaryAttachment
}
fun removeAttachment(attachment: Attachment) {
if (this.binaryDescription == attachment.name) {
this.binaryDescription = ""
this.binaryData = null
}
}
companion object { companion object {
/** Size of byte buffer needed to hold this struct. */ /** Size of byte buffer needed to hold this struct. */

View File

@@ -21,7 +21,9 @@ package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.database.BinaryPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
@@ -31,11 +33,12 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedLong import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.* import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
import kotlin.collections.LinkedHashMap import kotlin.collections.LinkedHashMap
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface { class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
@@ -60,8 +63,9 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
} }
var iconCustom = IconImageCustom.UNKNOWN_ICON var iconCustom = IconImageCustom.UNKNOWN_ICON
private var customData = LinkedHashMap<String, String>() private var customData = LinkedHashMap<String, String>()
// TODO Private
var fields = LinkedHashMap<String, ProtectedString>() var fields = LinkedHashMap<String, ProtectedString>()
var binaries = LinkedHashMap<String, BinaryAttachment>() var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId>
var foregroundColor = "" var foregroundColor = ""
var backgroundColor = "" var backgroundColor = ""
var overrideURL = "" var overrideURL = ""
@@ -70,36 +74,32 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
var additional = "" var additional = ""
var tags = "" var tags = ""
val size: Long fun getSize(binaryPool: BinaryPool): Long {
get() { var size = FIXED_LENGTH_SIZE
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) { for (entry in fields.entries) {
size += entry.key.length.toLong() size += entry.key.length.toLong()
size += entry.value.length().toLong() size += entry.value.length().toLong()
}
for ((key, value) in binaries) {
size += key.length.toLong()
size += value.length()
}
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.size
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
return size
} }
size += getAttachmentsSize(binaryPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.getSize(binaryPool)
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
return size
}
override var expires: Boolean = false override var expires: Boolean = false
constructor() : super() constructor() : super()
@@ -110,7 +110,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel) customData = ParcelableUtil.readStringParcelableMap(parcel)
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java) fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
binaries = ParcelableUtil.readStringParcelableMap(parcel, BinaryAttachment::class.java) binaries = ParcelableUtil.readStringIntMap(parcel)
foregroundColor = parcel.readString() ?: foregroundColor foregroundColor = parcel.readString() ?: foregroundColor
backgroundColor = parcel.readString() ?: backgroundColor backgroundColor = parcel.readString() ?: backgroundColor
overrideURL = parcel.readString() ?: overrideURL overrideURL = parcel.readString() ?: overrideURL
@@ -128,7 +128,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
dest.writeParcelable(locationChanged, flags) dest.writeParcelable(locationChanged, flags)
ParcelableUtil.writeStringParcelableMap(dest, customData) ParcelableUtil.writeStringParcelableMap(dest, customData)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields) ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
ParcelableUtil.writeStringParcelableMap(dest, flags, binaries) ParcelableUtil.writeStringIntMap(dest, binaries)
dest.writeString(foregroundColor) dest.writeString(foregroundColor)
dest.writeString(backgroundColor) dest.writeString(backgroundColor)
dest.writeString(overrideURL) dest.writeString(overrideURL)
@@ -167,8 +167,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
tags = source.tags tags = source.tags
} }
fun startToManageFieldReferences(db: DatabaseKDBX) { fun startToManageFieldReferences(database: DatabaseKDBX) {
this.mDatabase = db this.mDatabase = database
this.mDecodeRef = true this.mDecodeRef = true
} }
@@ -284,14 +284,46 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
fields[label] = value fields[label] = value
} }
fun putProtectedBinary(key: String, value: BinaryAttachment) { /**
binaries[key] = value * It's a list because history labels can be defined multiple times
*/
fun getAttachments(binaryPool: BinaryPool, inHistory: Boolean = false): List<Attachment> {
val entryAttachmentList = ArrayList<Attachment>()
for ((label, poolId) in binaries) {
binaryPool[poolId]?.let { binary ->
entryAttachmentList.add(Attachment(label, binary))
}
}
if (inHistory) {
history.forEach {
entryAttachmentList.addAll(it.getAttachments(binaryPool, false))
}
}
return entryAttachmentList
} }
fun removeProtectedBinary(name: String) { fun containsAttachment(): Boolean {
binaries.remove(name) return binaries.isNotEmpty()
} }
fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
binaries[attachment.name] = binaryPool.put(attachment.binaryAttachment)
}
fun removeAttachment(attachment: Attachment) {
binaries.remove(attachment.name)
}
private fun getAttachmentsSize(binaryPool: BinaryPool): Long {
var size = 0L
for ((label, poolId) in binaries) {
size += label.length.toLong()
size += binaryPool[poolId]?.length() ?: 0
}
return size
}
// TODO Remove ?
fun sizeOfHistory(): Int { fun sizeOfHistory(): Int {
return history.size return history.size
} }
@@ -308,15 +340,11 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
history.add(entry) history.add(entry)
} }
fun removeEntryFromHistory(position: Int) { fun removeEntryFromHistory(position: Int): EntryKDBX? {
history.removeAt(position) return history.removeAt(position)
} }
fun removeAllHistory() { fun removeOldestEntryFromHistory(): EntryKDBX? {
history.clear()
}
fun removeOldestEntryFromHistory() {
var min: Date? = null var min: Date? = null
var index = -1 var index = -1
@@ -329,9 +357,9 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
} }
} }
if (index != -1) { return if (index != -1) {
history.removeAt(index) history.removeAt(index)
} } else null
} }
override fun touch(modified: Boolean, touchParents: Boolean) { override fun touch(modified: Boolean, touchParents: Boolean) {

View File

@@ -192,10 +192,11 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
} }
} }
if (fieldID == PwDbHeaderV4Fields.EndOfHeader)
return true
if (fieldData != null) if (fieldData != null)
when (fieldID) { when (fieldID) {
PwDbHeaderV4Fields.EndOfHeader -> return true
PwDbHeaderV4Fields.CipherID -> setCipher(fieldData) PwDbHeaderV4Fields.CipherID -> setCipher(fieldData)
PwDbHeaderV4Fields.CompressionFlags -> setCompressionFlags(fieldData) PwDbHeaderV4Fields.CompressionFlags -> setCompressionFlags(fieldData)

View File

@@ -27,14 +27,12 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import org.joda.time.Instant
import java.io.* import java.io.*
import java.security.* import java.security.*
import java.util.* import java.util.*
@@ -282,11 +280,9 @@ class DatabaseInputKDB(cacheDirectory: File,
0x000E -> { 0x000E -> {
newEntry?.let { entry -> newEntry?.let { entry ->
if (fieldSize > 0) { if (fieldSize > 0) {
// Generate an unique new file with timestamp val binaryAttachment = mDatabaseToOpen.buildNewBinary(cacheDirectory)
val binaryFile = File(cacheDirectory, entry.binaryData = binaryAttachment
Instant.now().millis.toString()) BufferedOutputStream(binaryAttachment.getOutputDataStream()).use { outputStream ->
entry.binaryData = BinaryAttachment(binaryFile)
BufferedOutputStream(FileOutputStream(binaryFile)).use { outputStream ->
cipherInputStream.readBytes(fieldSize, cipherInputStream.readBytes(fieldSize,
DatabaseKDB.BUFFER_SIZE_BYTES) { buffer -> DatabaseKDB.BUFFER_SIZE_BYTES) { buffer ->
outputStream.write(buffer) outputStream.write(buffer)

View File

@@ -26,6 +26,7 @@ import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -35,7 +36,7 @@ import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
@@ -49,12 +50,14 @@ import org.bouncycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory import org.xmlpull.v1.XmlPullParserFactory
import java.io.* import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset import java.nio.charset.Charset
import java.text.ParseException import java.text.ParseException
import java.util.* import java.util.*
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherInputStream import javax.crypto.CipherInputStream
import kotlin.math.min import kotlin.math.min
@@ -68,9 +71,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
private var hashOfHeader: ByteArray? = null private var hashOfHeader: ByteArray? = null
private val unusedCacheFileName: String
get() = mDatabase.binaryPool.findUnusedKey().toString()
private var readNextNode = true private var readNextNode = true
private val ctxGroups = Stack<GroupKDBX>() private val ctxGroups = Stack<GroupKDBX>()
private var ctxGroup: GroupKDBX? = null private var ctxGroup: GroupKDBX? = null
@@ -233,8 +233,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
var data = ByteArray(0) var data = ByteArray(0)
if (size > 0) { if (size > 0) {
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) {
// TODO OOM here
data = dataInputStream.readBytes(size) data = dataInputStream.readBytes(size)
}
} }
var result = true var result = true
@@ -249,18 +251,16 @@ class DatabaseInputKDBX(cacheDirectory: File,
header.innerRandomStreamKey = data header.innerRandomStreamKey = data
} }
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
val flag = dataInputStream.readBytes(1)[0].toInt() != 0
val protectedFlag = flag && DatabaseHeaderKDBX.KdbxBinaryFlags.Protected.toInt() != DatabaseHeaderKDBX.KdbxBinaryFlags.None.toInt()
val byteLength = size - 1
// Read in a file // Read in a file
val file = File(cacheDirectory, unusedCacheFileName) val protectedFlag = dataInputStream.readBytes(1)[0].toInt() != 0
FileOutputStream(file).use { outputStream -> val byteLength = size - 1
// No compression at this level
val protectedBinary = mDatabase.buildNewBinary(cacheDirectory, protectedFlag, false)
protectedBinary.getOutputDataStream().use { outputStream ->
dataInputStream.readBytes(byteLength, DatabaseKDBX.BUFFER_SIZE_BYTES) { buffer -> dataInputStream.readBytes(byteLength, DatabaseKDBX.BUFFER_SIZE_BYTES) { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
} }
} }
val protectedBinary = BinaryAttachment(file, protectedFlag)
mDatabase.binaryPool.add(protectedBinary)
} }
} }
@@ -443,14 +443,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
} }
KdbContext.Binaries -> if (name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) { KdbContext.Binaries -> if (name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
val key = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrId) readBinary(xpp)
if (key != null) {
val pbData = readBinary(xpp)
val id = Integer.parseInt(key)
mDatabase.binaryPool.put(id, pbData!!)
} else {
readUnknown(xpp)
}
} else { } else {
readUnknown(xpp) readUnknown(xpp)
} }
@@ -766,8 +759,9 @@ class DatabaseInputKDBX(cacheDirectory: File,
return KdbContext.Entry return KdbContext.Entry
} else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) { } else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
if (ctxBinaryName != null && ctxBinaryValue != null) if (ctxBinaryName != null && ctxBinaryValue != null) {
ctxEntry?.putProtectedBinary(ctxBinaryName!!, ctxBinaryValue!!) ctxEntry?.putAttachment(Attachment(ctxBinaryName!!, ctxBinaryValue!!), mDatabase.binaryPool)
}
ctxBinaryName = null ctxBinaryName = null
ctxBinaryValue = null ctxBinaryValue = null
@@ -947,50 +941,56 @@ class DatabaseInputKDBX(cacheDirectory: File,
// Reference Id to a binary already present in binary pool // Reference Id to a binary already present in binary pool
val ref = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrRef) val ref = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrRef)
if (ref != null) { // New id to a binary
xpp.next() // Consume end tag val key = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrId)
val id = Integer.parseInt(ref) return when {
return mDatabase.binaryPool[id] ref != null -> {
} xpp.next() // Consume end tag
val id = Integer.parseInt(ref)
// New binary to retrieve // A ref is not necessarily an index in Database V3.1
else { mDatabase.binaryPool[id]
var compressed = false
var protected = false
if (xpp.attributeCount > 0) {
val compress = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrCompressed)
if (compress != null) {
compressed = compress.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)
}
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
if (protect != null) {
protected = protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)
}
} }
key != null -> {
val base64 = readString(xpp) createBinary(key.toIntOrNull(), xpp)
if (base64.isEmpty()) }
return BinaryAttachment() else -> {
val data = Base64.decode(base64, BASE_64_FLAG) // New binary to retrieve
createBinary(null, xpp)
val file = File(cacheDirectory, unusedCacheFileName)
return FileOutputStream(file).use { outputStream ->
// Force compression in this specific case
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
&& !compressed) {
GZIPOutputStream(outputStream).write(data)
BinaryAttachment(file, protected, true)
} else {
outputStream.write(data)
BinaryAttachment(file, protected, compressed)
}
} }
} }
} }
@Throws(IOException::class, XmlPullParserException::class)
private fun createBinary(binaryId: Int?, xpp: XmlPullParser): BinaryAttachment? {
var compressed = false
var protected = false
if (xpp.attributeCount > 0) {
val compress = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrCompressed)
if (compress != null) {
compressed = compress.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)
}
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
if (protect != null) {
protected = protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)
}
}
val base64 = readString(xpp)
if (base64.isEmpty())
return null
val data = Base64.decode(base64, BASE_64_FLAG)
// Build the new binary and compress
val binaryAttachment = mDatabase.buildNewBinary(cacheDirectory, protected, compressed, binaryId)
binaryAttachment.getOutputDataStream().use { outputStream ->
outputStream.write(data)
}
return binaryAttachment
}
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
private fun readString(xpp: XmlPullParser): String { private fun readString(xpp: XmlPullParser): String {
val buf = readProtectedBase64String(xpp) val buf = readProtectedBase64String(xpp)

View File

@@ -47,18 +47,25 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
dataOutputStream.writeInt(streamKeySize) dataOutputStream.writeInt(streamKeySize)
dataOutputStream.write(header.innerRandomStreamKey) dataOutputStream.write(header.innerRandomStreamKey)
database.binaryPool.doForEachBinary { _, protectedBinary -> database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
val protectedBinary = keyBinary.binary
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (protectedBinary.isProtected) { if (protectedBinary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
} }
// Force decompression to add binary in header
protectedBinary.decompress()
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary.toInt()) dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary.toInt())
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify dataOutputStream.writeInt(protectedBinary.length().toInt() + 1)
dataOutputStream.write(flag.toInt()) dataOutputStream.write(flag.toInt())
protectedBinary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer -> // if was compressed in cache, uncompress it
dataOutputStream.write(buffer) protectedBinary.getInputDataStream().use { inputStream ->
inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
dataOutputStream.write(buffer)
}
} }
} }

View File

@@ -38,7 +38,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
@@ -55,7 +55,6 @@ import java.io.OutputStream
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherOutputStream import javax.crypto.CipherOutputStream
@@ -422,7 +421,6 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
private fun writeBinary(binary : BinaryAttachment) { private fun writeBinary(binary : BinaryAttachment) {
val binaryLength = binary.length() val binaryLength = binary.length()
if (binaryLength > 0) { if (binaryLength > 0) {
if (binary.isProtected) { if (binary.isProtected) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue) xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
@@ -433,21 +431,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.text(charArray, 0, charArray.size) xml.text(charArray, 0, charArray.size)
} }
} else { } else {
// Force binary compression from database (compression was harmonized during import) if (binary.isCompressed) {
if (mDatabaseKDBX.compressionAlgorithm === CompressionAlgorithm.GZip) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue) xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
} }
// Force decompression in this specific case
val binaryInputStream = if (mDatabaseKDBX.compressionAlgorithm == CompressionAlgorithm.None
&& binary.isCompressed == true) {
GZIPInputStream(binary.getInputDataStream())
} else {
binary.getInputDataStream()
}
// Write the XML // Write the XML
binaryInputStream.readBytes(BUFFER_SIZE_BYTES) { buffer -> binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray() val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size) xml.text(charArray, 0, charArray.size)
} }
@@ -459,10 +447,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
private fun writeMetaBinaries() { private fun writeMetaBinaries() {
xml.startTag(null, DatabaseKDBXXML.ElemBinaries) xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
mDatabaseKDBX.binaryPool.doForEachBinary { key, binary -> // Use indexes because necessarily in DatabaseV4 (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary) xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, key.toString()) xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
writeBinary(binary) writeBinary(keyBinary.binary)
xml.endTag(null, DatabaseKDBXXML.ElemBinary) xml.endTag(null, DatabaseKDBXXML.ElemBinary)
} }
@@ -559,23 +548,22 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeEntryBinaries(binaries: Map<String, BinaryAttachment>) { private fun writeEntryBinaries(binaries: LinkedHashMap<String, Int>) {
for ((key, binary) in binaries) { for ((label, poolId) in binaries) {
xml.startTag(null, DatabaseKDBXXML.ElemBinary) // Retrieve the right index with the poolId, don't use ref because of header in DatabaseV4
xml.startTag(null, DatabaseKDBXXML.ElemKey) mDatabaseKDBX.binaryPool.getBinaryIndexFromKey(poolId)?.toString()?.let { indexString ->
xml.text(safeXmlString(key)) xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.endTag(null, DatabaseKDBXXML.ElemKey) xml.startTag(null, DatabaseKDBXXML.ElemKey)
xml.text(safeXmlString(label))
xml.endTag(null, DatabaseKDBXXML.ElemKey)
xml.startTag(null, DatabaseKDBXXML.ElemValue) xml.startTag(null, DatabaseKDBXXML.ElemValue)
val ref = mDatabaseKDBX.binaryPool.findKey(binary) // Use only pool data in Meta to save binaries
if (ref != null) { xml.attribute(null, DatabaseKDBXXML.AttrRef, indexString)
xml.attribute(null, DatabaseKDBXXML.AttrRef, ref.toString()) xml.endTag(null, DatabaseKDBXXML.ElemValue)
} else {
writeBinary(binary) xml.endTag(null, DatabaseKDBXXML.ElemBinary)
} }
xml.endTag(null, DatabaseKDBXXML.ElemValue)
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
} }
} }

View File

@@ -95,9 +95,9 @@ open class Education(val activity: Activity) {
R.string.education_entry_edit_key, R.string.education_entry_edit_key,
R.string.education_password_generator_key, R.string.education_password_generator_key,
R.string.education_entry_new_field_key, R.string.education_entry_new_field_key,
R.string.education_add_attachment_key,
R.string.education_setup_OTP_key) R.string.education_setup_OTP_key)
/** /**
* Get preferences bundle for education * Get preferences bundle for education
*/ */
@@ -272,6 +272,18 @@ open class Education(val activity: Activity) {
context.resources.getBoolean(R.bool.education_entry_new_field_default)) context.resources.getBoolean(R.bool.education_entry_new_field_default))
} }
/**
* Determines whether the explanatory view of the new attachment button in an entry has already been displayed.
*
* @param context The context to open the SharedPreferences
* @return boolean value of education_add_attachment_key key
*/
fun isEducationAddAttachmentPerformed(context: Context): Boolean {
val prefs = getEducationSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.education_add_attachment_key),
context.resources.getBoolean(R.bool.education_add_attachment_default))
}
/** /**
* Determines whether the explanatory view to setup OTP has already been displayed. * Determines whether the explanatory view to setup OTP has already been displayed.
* *

View File

@@ -29,6 +29,10 @@ import com.kunzisoft.keepass.R
class EntryEditActivityEducation(activity: Activity) class EntryEditActivityEducation(activity: Activity)
: Education(activity) { : Education(activity) {
/**
* Check and display learning views
* Displays the explanation for the password generator
*/
fun checkAndPerformedGeneratePasswordEducation(educationView: View, fun checkAndPerformedGeneratePasswordEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null, onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean { onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
@@ -56,7 +60,7 @@ class EntryEditActivityEducation(activity: Activity)
/** /**
* Check and display learning views * Check and display learning views
* Displays the explanation for the icon selection, the password generator and for a new field * Displays the explanation to create a new field
*/ */
fun checkAndPerformedEntryNewFieldEducation(educationView: View, fun checkAndPerformedEntryNewFieldEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null, onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
@@ -83,6 +87,35 @@ class EntryEditActivityEducation(activity: Activity)
R.string.education_entry_new_field_key) R.string.education_entry_new_field_key)
} }
/**
* Check and display learning views
* Displays the explanation for to upload attachment
*/
fun checkAndPerformedAttachmentEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
return checkAndPerformedEducation(!isEducationAddAttachmentPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_add_attachment_title),
activity.getString(R.string.education_add_attachment_summary))
.textColorInt(Color.WHITE)
.tintTarget(true)
.cancelable(true),
object : TapTargetView.Listener() {
override fun onTargetClick(view: TapTargetView) {
super.onTargetClick(view)
onEducationViewClick?.invoke(view)
}
override fun onOuterCircleClick(view: TapTargetView?) {
super.onOuterCircleClick(view)
view?.dismiss(false)
onOuterViewClick?.invoke(view)
}
},
R.string.education_add_attachment_key)
}
/** /**
* Check and display learning views * Check and display learning views
* Displays the explanation to setup OTP * Displays the explanation to setup OTP

View File

@@ -21,24 +21,25 @@ package com.kunzisoft.keepass.model
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.utils.readEnum import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum import com.kunzisoft.keepass.utils.writeEnum
data class EntryAttachment(var name: String, data class EntryAttachmentState(var attachment: Attachment,
var binaryAttachment: BinaryAttachment, var streamDirection: StreamDirection,
var downloadState: AttachmentState = AttachmentState.NULL, var downloadState: AttachmentState = AttachmentState.NULL,
var downloadProgression: Int = 0) : Parcelable { var downloadProgression: Int = 0) : Parcelable {
constructor(parcel: Parcel) : this( constructor(parcel: Parcel) : this(
parcel.readString() ?: "", parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryAttachment()),
parcel.readParcelable(BinaryAttachment::class.java.classLoader) ?: BinaryAttachment(), parcel.readEnum<StreamDirection>() ?: StreamDirection.DOWNLOAD,
parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL, parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL,
parcel.readInt()) parcel.readInt())
override fun writeToParcel(parcel: Parcel, flags: Int) { override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name) parcel.writeParcelable(attachment, flags)
parcel.writeParcelable(binaryAttachment, flags) parcel.writeEnum(streamDirection)
parcel.writeEnum(downloadState) parcel.writeEnum(downloadState)
parcel.writeInt(downloadProgression) parcel.writeInt(downloadProgression)
} }
@@ -49,26 +50,23 @@ data class EntryAttachment(var name: String,
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is EntryAttachment) return false if (other !is EntryAttachmentState) return false
if (name != other.name) return false if (attachment != other.attachment) return false
if (binaryAttachment != other.binaryAttachment) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = name.hashCode() return attachment.hashCode()
result = 31 * result + binaryAttachment.hashCode()
return result
} }
companion object CREATOR : Parcelable.Creator<EntryAttachment> { companion object CREATOR : Parcelable.Creator<EntryAttachmentState> {
override fun createFromParcel(parcel: Parcel): EntryAttachment { override fun createFromParcel(parcel: Parcel): EntryAttachmentState {
return EntryAttachment(parcel) return EntryAttachmentState(parcel)
} }
override fun newArray(size: Int): Array<EntryAttachment?> { override fun newArray(size: Int): Array<EntryAttachmentState?> {
return arrayOfNulls(size) return arrayOfNulls(size)
} }
} }

View File

@@ -0,0 +1,5 @@
package com.kunzisoft.keepass.model
enum class StreamDirection {
UPLOAD, DOWNLOAD
}

View File

@@ -28,17 +28,24 @@ import android.os.IBinder
import android.util.Log import android.util.Log
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.stream.readBytes
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.BufferedInputStream
import java.util.* import java.util.*
import kotlin.collections.HashMap import java.util.concurrent.CopyOnWriteArrayList
class AttachmentFileNotificationService: LockNotificationService() { class AttachmentFileNotificationService: LockNotificationService() {
override val notificationId: Int = 10000 override val notificationId: Int = 10000
private val attachmentNotificationList = CopyOnWriteArrayList<AttachmentNotification>()
private var mActionTaskBinder = ActionTaskBinder() private var mActionTaskBinder = ActionTaskBinder()
private var mActionTaskListeners = LinkedList<ActionTaskListener>() private var mActionTaskListeners = LinkedList<ActionTaskListener>()
@@ -51,33 +58,31 @@ class AttachmentFileNotificationService: LockNotificationService() {
fun addActionTaskListener(actionTaskListener: ActionTaskListener) { fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.add(actionTaskListener) mActionTaskListeners.add(actionTaskListener)
attachmentNotificationList.forEach {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit { it.attachmentFileAction?.listener = attachmentFileActionListener
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) { }
entry.value.attachmentTask?.onUpdate = { uri, attachment, notificationIdAttach ->
newNotification(uri, attachment, notificationIdAttach)
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentProgress(entry.key, attachment)
}
}
}
})
} }
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) { fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit { attachmentNotificationList.forEach {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) { it.attachmentFileAction?.listener = null
entry.value.attachmentTask?.onUpdate = null }
}
})
mActionTaskListeners.remove(actionTaskListener) mActionTaskListeners.remove(actionTaskListener)
} }
} }
private val attachmentFileActionListener = object: AttachmentFileAction.AttachmentFileActionListener {
override fun onUpdate(attachmentNotification: AttachmentNotification) {
newNotification(attachmentNotification)
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentAction(attachmentNotification.uri,
attachmentNotification.entryAttachmentState)
}
}
}
interface ActionTaskListener { interface ActionTaskListener {
fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState)
} }
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
@@ -87,49 +92,29 @@ class AttachmentFileNotificationService: LockNotificationService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
val downloadFileUri: Uri? = if (intent?.hasExtra(DOWNLOAD_FILE_URI_KEY) == true) { val downloadFileUri: Uri? = if (intent?.hasExtra(FILE_URI_KEY) == true) {
intent.getParcelableExtra(DOWNLOAD_FILE_URI_KEY) intent.getParcelableExtra(FILE_URI_KEY)
} else null } else null
when(intent?.action) { when(intent?.action) {
ACTION_ATTACHMENT_FILE_START_UPLOAD -> {
actionUploadOrDownload(downloadFileUri,
intent,
StreamDirection.UPLOAD)
}
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> { ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
if (downloadFileUri != null actionUploadOrDownload(downloadFileUri,
&& intent.hasExtra(ATTACHMENT_KEY)) { intent,
StreamDirection.DOWNLOAD)
val nextNotificationId = (downloadFileUris.values.maxByOrNull { it.notificationId }
?.notificationId ?: notificationId) + 1
try {
intent.getParcelableExtra<EntryAttachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
val attachmentNotification = AttachmentNotification(nextNotificationId, entryAttachment)
downloadFileUris[downloadFileUri] = attachmentNotification
mainScope.launch {
AttachmentFileActionClass(downloadFileUri,
attachmentNotification,
contentResolver).apply {
onUpdate = { uri, attachment, notificationIdAttach ->
newNotification(uri, attachment, notificationIdAttach)
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentProgress(downloadFileUri, attachment)
}
}
}.executeAction()
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to download $downloadFileUri", e)
}
}
} }
else -> { else -> {
if (downloadFileUri != null) { if (downloadFileUri != null) {
downloadFileUris[downloadFileUri]?.notificationId?.let { attachmentNotificationList.firstOrNull { it.uri == downloadFileUri }?.let { elementToRemove ->
notificationManager?.cancel(it) notificationManager?.cancel(elementToRemove.notificationId)
downloadFileUris.remove(downloadFileUri) attachmentNotificationList.remove(elementToRemove)
} }
} }
if (downloadFileUris.isEmpty()) { if (attachmentNotificationList.isEmpty()) {
stopSelf() stopSelf()
} }
} }
@@ -138,25 +123,35 @@ class AttachmentFileNotificationService: LockNotificationService() {
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
@Synchronized
fun checkCurrentAttachmentProgress() { fun checkCurrentAttachmentProgress() {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit { attachmentNotificationList.forEach { attachmentNotification ->
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) { mActionTaskListeners.forEach { actionListener ->
mActionTaskListeners.forEach { actionListener -> actionListener.onAttachmentAction(
actionListener.onAttachmentProgress(entry.key, entry.value.entryAttachment) attachmentNotification.uri,
} attachmentNotification.entryAttachmentState
)
} }
}) }
} }
private fun newNotification(downloadFileUri: Uri, @Synchronized
entryAttachment: EntryAttachment, fun removeAttachmentAction(entryAttachment: EntryAttachmentState) {
notificationIdAttachment: Int) { attachmentNotificationList.firstOrNull {
it.entryAttachmentState == entryAttachment
}?.let {
attachmentNotificationList.remove(it)
}
}
private fun newNotification(attachmentNotification: AttachmentNotification) {
val pendingContentIntent = PendingIntent.getActivity(this, val pendingContentIntent = PendingIntent.getActivity(this,
0, 0,
Intent().apply { Intent().apply {
action = Intent.ACTION_VIEW action = Intent.ACTION_VIEW
setDataAndType(downloadFileUri, contentResolver.getType(downloadFileUri)) setDataAndType(attachmentNotification.uri,
contentResolver.getType(attachmentNotification.uri))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}, PendingIntent.FLAG_CANCEL_CURRENT) }, PendingIntent.FLAG_CANCEL_CURRENT)
@@ -164,54 +159,84 @@ class AttachmentFileNotificationService: LockNotificationService() {
0, 0,
Intent(this, AttachmentFileNotificationService::class.java).apply { Intent(this, AttachmentFileNotificationService::class.java).apply {
// No action to delete the service // No action to delete the service
putExtra(DOWNLOAD_FILE_URI_KEY, downloadFileUri) putExtra(FILE_URI_KEY, attachmentNotification.uri)
}, PendingIntent.FLAG_CANCEL_CURRENT) }, PendingIntent.FLAG_CANCEL_CURRENT)
val fileName = DocumentFile.fromSingleUri(this, downloadFileUri)?.name ?: "" val fileName = DocumentFile.fromSingleUri(this, attachmentNotification.uri)?.name ?: ""
val builder = buildNewNotification().apply { val builder = buildNewNotification().apply {
setSmallIcon(R.drawable.ic_file_download_white_24dp) when (attachmentNotification.entryAttachmentState.streamDirection) {
setContentTitle(getString(R.string.download_attachment, fileName)) StreamDirection.UPLOAD -> {
setSmallIcon(R.drawable.ic_file_upload_white_24dp)
setContentTitle(getString(R.string.upload_attachment, fileName))
}
StreamDirection.DOWNLOAD -> {
setSmallIcon(R.drawable.ic_file_download_white_24dp)
setContentTitle(getString(R.string.download_attachment, fileName))
}
}
setAutoCancel(false) setAutoCancel(false)
when (entryAttachment.downloadState) { when (attachmentNotification.entryAttachmentState.downloadState) {
AttachmentState.NULL, AttachmentState.START -> { AttachmentState.NULL, AttachmentState.START -> {
setContentText(getString(R.string.download_initialization)) setContentText(getString(R.string.download_initialization))
setOngoing(true) setOngoing(true)
} }
AttachmentState.IN_PROGRESS -> { AttachmentState.IN_PROGRESS -> {
if (entryAttachment.downloadProgression > 100) { if (attachmentNotification.entryAttachmentState.downloadProgression > 100) {
setContentText(getString(R.string.download_finalization)) setContentText(getString(R.string.download_finalization))
} else { } else {
setProgress(100, entryAttachment.downloadProgression, false) setProgress(100,
setContentText(getString(R.string.download_progression, entryAttachment.downloadProgression)) attachmentNotification.entryAttachmentState.downloadProgression,
false)
setContentText(getString(R.string.download_progression,
attachmentNotification.entryAttachmentState.downloadProgression))
} }
setOngoing(true) setOngoing(true)
} }
AttachmentState.COMPLETE, AttachmentState.ERROR -> { AttachmentState.COMPLETE -> {
setContentText(getString(R.string.download_complete)) setContentText(getString(R.string.download_complete))
setContentIntent(pendingContentIntent) when (attachmentNotification.entryAttachmentState.streamDirection) {
StreamDirection.UPLOAD -> {
}
StreamDirection.DOWNLOAD -> {
setContentIntent(pendingContentIntent)
}
}
setDeleteIntent(pendingDeleteIntent) setDeleteIntent(pendingDeleteIntent)
setOngoing(false) setOngoing(false)
} }
AttachmentState.ERROR -> {
setContentText(getString(R.string.error_file_not_create))
setOngoing(false)
}
}
}
when (attachmentNotification.entryAttachmentState.downloadState) {
AttachmentState.ERROR,
AttachmentState.COMPLETE -> {
stopForeground(false)
notificationManager?.notify(attachmentNotification.notificationId, builder.build())
} else -> {
startForeground(attachmentNotification.notificationId, builder.build())
} }
} }
notificationManager?.notify(notificationIdAttachment, builder.build())
} }
override fun onDestroy() { override fun onDestroy() {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit { attachmentNotificationList.forEach { attachmentNotification ->
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) { attachmentNotification.attachmentFileAction?.listener = null
entry.value.attachmentTask?.onUpdate = null notificationManager?.cancel(attachmentNotification.notificationId)
notificationManager?.cancel(entry.value.notificationId) }
} attachmentNotificationList.clear()
})
super.onDestroy() super.onDestroy()
} }
private data class AttachmentNotification(var notificationId: Int, private data class AttachmentNotification(var uri: Uri,
var entryAttachment: EntryAttachment, var notificationId: Int,
var attachmentTask: AttachmentFileActionClass? = null) { var entryAttachmentState: EntryAttachmentState,
var attachmentFileAction: AttachmentFileAction? = null) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
@@ -228,52 +253,85 @@ class AttachmentFileNotificationService: LockNotificationService() {
} }
} }
private class AttachmentFileActionClass( private fun actionUploadOrDownload(downloadFileUri: Uri?,
private val fileUri: Uri, intent: Intent,
streamDirection: StreamDirection) {
if (downloadFileUri != null
&& intent.hasExtra(ATTACHMENT_KEY)) {
try {
intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
val nextNotificationId = (attachmentNotificationList.maxByOrNull { it.notificationId }
?.notificationId ?: notificationId) + 1
val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection)
val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState)
attachmentNotificationList.add(attachmentNotification)
mainScope.launch {
AttachmentFileAction(attachmentNotification,
contentResolver).apply {
listener = attachmentFileActionListener
}.executeAction()
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to upload/download $downloadFileUri", e)
}
}
}
private class AttachmentFileAction(
private val attachmentNotification: AttachmentNotification, private val attachmentNotification: AttachmentNotification,
private val contentResolver: ContentResolver) { private val contentResolver: ContentResolver) {
private val updateMinFrequency = 1000 private val updateMinFrequency = 1000
private var previousSaveTime = System.currentTimeMillis() private var previousSaveTime = System.currentTimeMillis()
var onUpdate: ((Uri, EntryAttachment, Int)->Unit)? = null var listener: AttachmentFileActionListener? = null
interface AttachmentFileActionListener {
fun onUpdate(attachmentNotification: AttachmentNotification)
}
suspend fun executeAction() { suspend fun executeAction() {
TimeoutHelper.temporarilyDisableTimeout() TimeoutHelper.temporarilyDisableTimeout()
// on pre execute // on pre execute
attachmentNotification.attachmentTask = this attachmentNotification.attachmentFileAction = this
attachmentNotification.entryAttachment.apply { attachmentNotification.entryAttachmentState.apply {
downloadState = AttachmentState.START downloadState = AttachmentState.START
downloadProgression = 0 downloadProgression = 0
} }
onUpdate?.invoke(fileUri, listener?.onUpdate(attachmentNotification)
attachmentNotification.entryAttachment,
attachmentNotification.notificationId)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
// on Progress with thread // on Progress with thread
val asyncResult: Deferred<Boolean> = async { val asyncResult: Deferred<Boolean> = async {
var progressResult = true var progressResult = true
try { try {
attachmentNotification.entryAttachment.apply { attachmentNotification.entryAttachmentState.apply {
downloadState = AttachmentState.IN_PROGRESS downloadState = AttachmentState.IN_PROGRESS
binaryAttachment.download(fileUri, contentResolver, 1024) { percent ->
// Publish progress when (streamDirection) {
val currentTime = System.currentTimeMillis() StreamDirection.UPLOAD -> {
if (previousSaveTime + updateMinFrequency < currentTime) { uploadToDatabase(
attachmentNotification.entryAttachment.apply { attachmentNotification.uri,
downloadState = AttachmentState.IN_PROGRESS attachment.binaryAttachment,
downloadProgression = percent contentResolver, 1024) { percent ->
publishProgress(percent)
}
}
StreamDirection.DOWNLOAD -> {
downloadFromDatabase(
attachmentNotification.uri,
attachment.binaryAttachment,
contentResolver, 1024) { percent ->
publishProgress(percent)
} }
onUpdate?.invoke(fileUri,
attachmentNotification.entryAttachment,
attachmentNotification.notificationId)
Log.d(TAG, "Download file $fileUri : $percent%")
previousSaveTime = currentTime
} }
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to upload or download file", e)
progressResult = false progressResult = false
} }
progressResult progressResult
@@ -282,33 +340,95 @@ class AttachmentFileNotificationService: LockNotificationService() {
// on post execute // on post execute
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val result = asyncResult.await() val result = asyncResult.await()
attachmentNotification.attachmentTask = null attachmentNotification.attachmentFileAction = null
attachmentNotification.entryAttachment.apply { attachmentNotification.entryAttachmentState.apply {
downloadState = if (result) AttachmentState.COMPLETE else AttachmentState.ERROR downloadState = if (result) AttachmentState.COMPLETE else AttachmentState.ERROR
downloadProgression = 100 downloadProgression = 100
} }
onUpdate?.invoke(fileUri, listener?.onUpdate(attachmentNotification)
attachmentNotification.entryAttachment, TimeoutHelper.releaseTemporarilyDisableTimeout()
attachmentNotification.notificationId)
} }
} }
} }
fun downloadFromDatabase(attachmentToUploadUri: Uri,
binaryAttachment: BinaryAttachment,
contentResolver: ContentResolver,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
update: ((percent: Int)->Unit)? = null) {
var dataDownloaded = 0L
val fileSize = binaryAttachment.length()
UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream ->
binaryAttachment.getUnGzipInputDataStream().use { inputStream ->
inputStream.readBytes(bufferSize) { buffer ->
outputStream.write(buffer)
dataDownloaded += buffer.size
try {
val percentDownload = (100 * dataDownloaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.e(TAG, "", e)
}
}
}
}
}
fun uploadToDatabase(attachmentFromDownloadUri: Uri,
binaryAttachment: BinaryAttachment,
contentResolver: ContentResolver,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
update: ((percent: Int)->Unit)? = null) {
var dataUploaded = 0L
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.let { inputStream ->
binaryAttachment.getGzipOutputDataStream().use { outputStream ->
BufferedInputStream(inputStream).use { attachmentBufferedInputStream ->
attachmentBufferedInputStream.readBytes(bufferSize) { buffer ->
outputStream.write(buffer)
dataUploaded += buffer.size
try {
val percentDownload = (100 * dataUploaded / fileSize).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {
Log.e(TAG, "", e)
}
}
}
}
}
}
private fun publishProgress(percent: Int) {
// Publish progress
val currentTime = System.currentTimeMillis()
if (previousSaveTime + updateMinFrequency < currentTime) {
attachmentNotification.entryAttachmentState.apply {
downloadState = AttachmentState.IN_PROGRESS
downloadProgression = percent
}
CoroutineScope(Dispatchers.Main).launch {
listener?.onUpdate(attachmentNotification)
Log.d(TAG, "Download file ${attachmentNotification.uri} : $percent%")
}
previousSaveTime = currentTime
}
}
companion object { companion object {
private val TAG = AttachmentFileActionClass::class.java.name private val TAG = AttachmentFileAction::class.java.name
} }
} }
companion object { companion object {
private val TAG = AttachmentFileNotificationService::javaClass.name private val TAG = AttachmentFileNotificationService::javaClass.name
const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD"
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD" const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
const val DOWNLOAD_FILE_URI_KEY = "DOWNLOAD_FILE_URI_KEY" const val FILE_URI_KEY = "FILE_URI_KEY"
const val ATTACHMENT_KEY = "ATTACHMENT_KEY" const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
private val downloadFileUris = HashMap<Uri, AttachmentNotification>()
} }
} }

View File

@@ -150,7 +150,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key)) recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key))
// Recycle bin // Recycle bin
if (mDatabase.allowRecycleBin) { if (mDatabase.allowConfigurableRecycleBin) {
val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key)) val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key))
recycleBinEnablePref?.apply { recycleBinEnablePref?.apply {
isChecked = mDatabase.isRecycleBinEnabled isChecked = mDatabase.isRecycleBinEnabled

View File

@@ -231,12 +231,6 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.biometric_auto_open_prompt_default)) context.resources.getBoolean(R.bool.biometric_auto_open_prompt_default))
} }
fun isFullFilePathEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.full_file_path_enable_key),
context.resources.getBoolean(R.bool.full_file_path_enable_default))
}
fun getListSort(context: Context): SortNodeEnum { fun getListSort(context: Context): SortNodeEnum {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.getString(context.getString(R.string.sort_node_key), prefs.getString(context.getString(R.string.sort_node_key),

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2017 Brian Pellin, 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.stream
import java.io.IOException
import java.io.OutputStream
import java.io.RandomAccessFile
class RandomFileOutputStream internal constructor(private val mFile: RandomAccessFile) : OutputStream() {
@Throws(IOException::class)
override fun close() {
super.close()
mFile.close()
}
@Throws(IOException::class)
override fun write(buffer: ByteArray, offset: Int, count: Int) {
super.write(buffer, offset, count)
mFile.write(buffer, offset, count)
}
@Throws(IOException::class)
override fun write(buffer: ByteArray) {
super.write(buffer)
mFile.write(buffer)
}
@Throws(IOException::class)
override fun write(oneByte: Int) {
mFile.write(oneByte)
}
@Throws(IOException::class)
fun seek(pos: Long) {
mFile.seek(pos)
}
}

View File

@@ -28,9 +28,12 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.model.EntryAttachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
class AttachmentFileBinderManager(private val activity: FragmentActivity) { class AttachmentFileBinderManager(private val activity: FragmentActivity) {
@@ -43,8 +46,18 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
private var mServiceConnection: ServiceConnection? = null private var mServiceConnection: ServiceConnection? = null
private val mActionTaskListener = object: AttachmentFileNotificationService.ActionTaskListener { private val mActionTaskListener = object: AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) { override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
onActionTaskListener?.onAttachmentProgress(fileUri, attachment) onActionTaskListener?.let {
it.onAttachmentAction(fileUri, entryAttachmentState)
when (entryAttachmentState.downloadState) {
AttachmentState.COMPLETE,
AttachmentState.ERROR -> {
// Finish the action when capture by activity
consummeAttachmentAction(entryAttachmentState)
}
else -> {}
}
}
} }
} }
@@ -85,22 +98,32 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
mServiceConnection = null mServiceConnection = null
} }
@Synchronized
fun consummeAttachmentAction(attachment: EntryAttachmentState) {
mBinder?.getService()?.removeAttachmentAction(attachment)
}
@Synchronized @Synchronized
private fun start(bundle: Bundle? = null, actionTask: String) { private fun start(bundle: Bundle? = null, actionTask: String) {
activity.stopService(mIntentTask)
if (bundle != null) if (bundle != null)
mIntentTask.putExtras(bundle) mIntentTask.putExtras(bundle)
activity.runOnUiThread { mIntentTask.action = actionTask
mIntentTask.action = actionTask activity.startService(mIntentTask)
activity.startService(mIntentTask) }
}
fun startUploadAttachment(uploadFileUri: Uri,
attachment: Attachment) {
start(Bundle().apply {
putParcelable(AttachmentFileNotificationService.FILE_URI_KEY, uploadFileUri)
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
}, ACTION_ATTACHMENT_FILE_START_UPLOAD)
} }
fun startDownloadAttachment(downloadFileUri: Uri, fun startDownloadAttachment(downloadFileUri: Uri,
entryAttachment: EntryAttachment) { attachment: Attachment) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(AttachmentFileNotificationService.DOWNLOAD_FILE_URI_KEY, downloadFileUri) putParcelable(AttachmentFileNotificationService.FILE_URI_KEY, downloadFileUri)
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, entryAttachment) putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
}, ACTION_ATTACHMENT_FILE_START_DOWNLOAD) }, ACTION_ATTACHMENT_FILE_START_DOWNLOAD)
} }
} }

View File

@@ -60,6 +60,15 @@ object ParcelableUtil {
} }
} }
// For writing map with string key and Int value to a Parcel
fun writeStringIntMap(parcel: Parcel, map: LinkedHashMap<String, Int>) {
parcel.writeInt(map.size)
for ((key, value) in map) {
parcel.writeString(key)
parcel.writeInt(value)
}
}
// For reading map with string key from a Parcel // For reading map with string key from a Parcel
fun <V : Parcelable> readStringParcelableMap( fun <V : Parcelable> readStringParcelableMap(
parcel: Parcel, vClass: Class<V>): LinkedHashMap<String, V> { parcel: Parcel, vClass: Class<V>): LinkedHashMap<String, V> {
@@ -74,6 +83,19 @@ object ParcelableUtil {
return map return map
} }
// For reading map with string key and Int value from a Parcel
fun readStringIntMap(parcel: Parcel): LinkedHashMap<String, Int> {
val size = parcel.readInt()
val map = LinkedHashMap<String, Int>(size)
for (i in 0 until size) {
val key: String? = parcel.readString()
val value: Int? = parcel.readInt()
if (key != null && value != null)
map[key] = value
}
return map
}
// For writing map with string key and string value to a Parcel // For writing map with string key and string value to a Parcel
fun writeStringParcelableMap(dest: Parcel, map: LinkedHashMap<String, String>) { fun writeStringParcelableMap(dest: Parcel, map: LinkedHashMap<String, String>) {

View File

@@ -27,10 +27,7 @@ import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import java.io.File import java.io.*
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.InputStream
import java.util.* import java.util.*
@@ -52,6 +49,17 @@ object UriUtil {
} }
} }
@Throws(FileNotFoundException::class)
fun getUriOutputStream(contentResolver: ContentResolver, fileUri: Uri?): OutputStream? {
if (fileUri == null)
return null
return when {
isFileScheme(fileUri) -> fileUri.path?.let { FileOutputStream(it) }
isContentScheme(fileUri) -> contentResolver.openOutputStream(fileUri, "rwt")
else -> null
}
}
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class)
fun getUriInputStream(contentResolver: ContentResolver, fileUri: Uri?): InputStream? { fun getUriInputStream(contentResolver: ContentResolver, fileUri: Uri?): InputStream? {
if (fileUri == null) if (fileUri == null)

View File

@@ -19,7 +19,6 @@
package com.kunzisoft.keepass.view package com.kunzisoft.keepass.view
import android.content.Context import android.content.Context
import android.text.util.Linkify
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -28,7 +27,6 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
@@ -37,9 +35,11 @@ import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.search.UuidUtil import com.kunzisoft.keepass.database.search.UuidUtil
import com.kunzisoft.keepass.model.EntryAttachment import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
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 java.util.* import java.util.*
@@ -52,27 +52,14 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
private var fontInVisibility: Boolean = false private var fontInVisibility: Boolean = false
private val userNameContainerView: View private val userNameFieldView: EntryField
private val userNameView: TextView private val passwordFieldView: EntryField
private val userNameActionView: ImageView private val otpFieldView: EntryField
private val urlFieldView: EntryField
private val passwordContainerView: View private val notesFieldView: EntryField
private val passwordView: TextView
private val passwordActionView: ImageView
private val otpContainerView: View
private val otpLabelView: TextView
private val otpView: TextView
private val otpActionView: ImageView
private var otpRunnable: Runnable? = null private var otpRunnable: Runnable? = null
private val urlContainerView: View
private val urlView: TextView
private val notesContainerView: View
private val notesView: TextView
private val extraFieldsContainerView: View private val extraFieldsContainerView: View
private val extraFieldsListView: ViewGroup private val extraFieldsListView: ViewGroup
@@ -84,7 +71,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
private val attachmentsContainerView: View private val attachmentsContainerView: View
private val attachmentsListView: RecyclerView private val attachmentsListView: RecyclerView
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context, false) private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
private val historyContainerView: View private val historyContainerView: View
private val historyListView: RecyclerView private val historyListView: RecyclerView
@@ -93,34 +80,26 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
private val uuidView: TextView private val uuidView: TextView
private val uuidReferenceView: TextView private val uuidReferenceView: TextView
val isUserNamePresent: Boolean
get() = userNameContainerView.visibility == View.VISIBLE
val isPasswordPresent: Boolean
get() = passwordContainerView.visibility == View.VISIBLE
init { init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater? val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.view_entry_contents, this) inflater?.inflate(R.layout.view_entry_contents, this)
userNameContainerView = findViewById(R.id.entry_user_name_container) userNameFieldView = findViewById(R.id.entry_user_name_field)
userNameView = findViewById(R.id.entry_user_name) userNameFieldView.setLabel(R.string.entry_user_name)
userNameActionView = findViewById(R.id.entry_user_name_action_image)
passwordContainerView = findViewById(R.id.entry_password_container) passwordFieldView = findViewById(R.id.entry_password_field)
passwordView = findViewById(R.id.entry_password) passwordFieldView.setLabel(R.string.entry_password)
passwordActionView = findViewById(R.id.entry_password_action_image)
otpContainerView = findViewById(R.id.entry_otp_container) otpFieldView = findViewById(R.id.entry_otp_field)
otpLabelView = findViewById(R.id.entry_otp_label) otpFieldView.setLabel(R.string.entry_otp)
otpView = findViewById(R.id.entry_otp)
otpActionView = findViewById(R.id.entry_otp_action_image)
urlContainerView = findViewById(R.id.entry_url_container) urlFieldView = findViewById(R.id.entry_url_field)
urlView = findViewById(R.id.entry_url) urlFieldView.setLabel(R.string.entry_url)
urlFieldView.setValueAutoLink(true)
notesContainerView = findViewById(R.id.entry_notes_container) notesFieldView = findViewById(R.id.entry_notes_field)
notesView = findViewById(R.id.entry_notes) notesFieldView.setLabel(R.string.entry_notes)
notesFieldView.setValueAutoLink(true)
extraFieldsContainerView = findViewById(R.id.extra_fields_container) extraFieldsContainerView = findViewById(R.id.extra_fields_container)
extraFieldsListView = findViewById(R.id.extra_fields_list) extraFieldsListView = findViewById(R.id.extra_fields_list)
@@ -128,7 +107,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
attachmentsContainerView = findViewById(R.id.entry_attachments_container) attachmentsContainerView = findViewById(R.id.entry_attachments_container)
attachmentsListView = findViewById(R.id.entry_attachments_list) attachmentsListView = findViewById(R.id.entry_attachments_list)
attachmentsListView?.apply { attachmentsListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
} }
@@ -154,86 +133,52 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
this.fontInVisibility = fontInVisibility this.fontInVisibility = fontInVisibility
} }
fun assignUserName(userName: String?) { fun assignUserName(userName: String?,
if (userName != null && userName.isNotEmpty()) { onClickListener: OnClickListener?) {
userNameContainerView.visibility = View.VISIBLE userNameFieldView.apply {
userNameView.apply { if (userName != null && userName.isNotEmpty()) {
text = userName visibility = View.VISIBLE
if (fontInVisibility) setValue(userName)
applyFontVisibility() applyFontVisibility(fontInVisibility)
} } else {
} else {
userNameContainerView.visibility = View.GONE
}
}
fun assignUserNameCopyListener(onClickListener: OnClickListener) {
userNameActionView.setOnClickListener(onClickListener)
}
fun assignPassword(password: String?, allowCopyPassword: Boolean) {
if (password != null && password.isNotEmpty()) {
passwordContainerView.visibility = View.VISIBLE
passwordView.apply {
text = password
if (fontInVisibility)
applyFontVisibility()
}
passwordActionView.isActivated = !allowCopyPassword
} else {
passwordContainerView.visibility = View.GONE
}
}
fun assignPasswordCopyListener(onClickListener: OnClickListener?) {
passwordActionView.apply {
setOnClickListener(onClickListener)
if (onClickListener == null) {
visibility = View.GONE visibility = View.GONE
} }
assignCopyButtonClickListener(onClickListener)
} }
} }
fun atLeastOneFieldProtectedPresent(): Boolean { fun assignPassword(password: String?,
extraFieldsListView.let { allowCopyPassword: Boolean,
for (i in 0 until it.childCount) { onClickListener: OnClickListener?) {
val childCustomView = it.getChildAt(i) passwordFieldView.apply {
if (childCustomView is EntryExtraField) if (password != null && password.isNotEmpty()) {
if (childCustomView.isProtected) visibility = View.VISIBLE
return true setValue(password, true)
} applyFontVisibility(fontInVisibility)
} activateCopyButton(allowCopyPassword)
return false }else {
} visibility = View.GONE
fun setHiddenPasswordStyle(hiddenStyle: Boolean) {
passwordView.applyHiddenStyle(hiddenStyle)
// Hidden style for custom fields
extraFieldsListView.let {
for (i in 0 until it.childCount) {
val childCustomView = it.getChildAt(i)
if (childCustomView is EntryExtraField)
childCustomView.setHiddenPasswordStyle(hiddenStyle)
} }
assignCopyButtonClickListener(onClickListener)
} }
} }
fun assignOtp(otpElement: OtpElement?, fun assignOtp(otpElement: OtpElement?,
otpProgressView: ProgressBar?, otpProgressView: ProgressBar?,
onClickListener: OnClickListener) { onClickListener: OnClickListener) {
otpContainerView.removeCallbacks(otpRunnable) otpFieldView.removeCallbacks(otpRunnable)
if (otpElement != null) { if (otpElement != null) {
otpContainerView.visibility = View.VISIBLE otpFieldView.visibility = View.VISIBLE
if (otpElement.token.isEmpty()) { if (otpElement.token.isEmpty()) {
otpView.text = context.getString(R.string.error_invalid_OTP) otpFieldView.setValue(R.string.error_invalid_OTP)
otpActionView.setColorFilter(ContextCompat.getColor(context, R.color.grey_dark)) otpFieldView.activateCopyButton(false)
assignOtpCopyListener(null) otpFieldView.assignCopyButtonClickListener(null)
} else { } else {
assignOtpCopyListener(onClickListener) otpFieldView.setLabel(otpElement.type.name)
otpView.text = otpElement.token otpFieldView.setValue(otpElement.token)
otpLabelView.text = otpElement.type.name otpFieldView.assignCopyButtonClickListener(onClickListener)
when (otpElement.type) { when (otpElement.type) {
// Only add token if HOTP // Only add token if HOTP
@@ -249,47 +194,41 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
} }
otpRunnable = Runnable { otpRunnable = Runnable {
if (otpElement.shouldRefreshToken()) { if (otpElement.shouldRefreshToken()) {
otpView.text = otpElement.token otpFieldView.setValue(otpElement.token)
} }
otpProgressView?.progress = otpElement.secondsRemaining otpProgressView?.progress = otpElement.secondsRemaining
otpContainerView.postDelayed(otpRunnable, 1000) otpFieldView.postDelayed(otpRunnable, 1000)
} }
otpContainerView.post(otpRunnable) otpFieldView.post(otpRunnable)
} }
} }
} }
} else { } else {
otpContainerView.visibility = View.GONE otpFieldView.visibility = View.GONE
otpProgressView?.visibility = View.GONE otpProgressView?.visibility = View.GONE
} }
} }
fun assignOtpCopyListener(onClickListener: OnClickListener?) {
otpActionView.setOnClickListener(onClickListener)
}
fun assignURL(url: String?) { fun assignURL(url: String?) {
if (url != null && url.isNotEmpty()) { urlFieldView.apply {
urlContainerView.visibility = View.VISIBLE if (url != null && url.isNotEmpty()) {
urlView.text = url visibility = View.VISIBLE
} else { setValue(url)
urlContainerView.visibility = View.GONE } else {
visibility = View.GONE
}
} }
} }
fun assignComment(comment: String?) { fun assignNotes(notes: String?) {
if (comment != null && comment.isNotEmpty()) { notesFieldView.apply {
notesContainerView.visibility = View.VISIBLE if (notes != null && notes.isNotEmpty()) {
notesView.apply { visibility = View.VISIBLE
text = comment setValue(notes)
if (fontInVisibility) applyFontVisibility(fontInVisibility)
applyFontVisibility() } else {
visibility = View.GONE
} }
try {
Linkify.addLinks(notesView, Linkify.ALL)
} catch (e: Exception) {}
} else {
notesContainerView.visibility = View.GONE
} }
} }
@@ -322,6 +261,19 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
uuidReferenceView.text = UuidUtil.toHexString(uuid) uuidReferenceView.text = UuidUtil.toHexString(uuid)
} }
fun setHiddenProtectedValue(hiddenProtectedValue: Boolean) {
passwordFieldView.hiddenProtectedValue = hiddenProtectedValue
// Hidden style for custom fields
extraFieldsListView.let {
for (i in 0 until it.childCount) {
val childCustomView = it.getChildAt(i)
if (childCustomView is EntryField)
childCustomView.hiddenProtectedValue = hiddenProtectedValue
}
}
}
/* ------------- /* -------------
* Extra Fields * Extra Fields
* ------------- * -------------
@@ -334,14 +286,15 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
fun addExtraField(title: String, fun addExtraField(title: String,
value: ProtectedString, value: ProtectedString,
allowCopy: Boolean, allowCopy: Boolean,
onActionClickListener: OnClickListener?) { onCopyButtonClickListener: OnClickListener?) {
val entryCustomField: EntryExtraField? = EntryExtraField(context, attrs, defStyle) val entryCustomField: EntryField? = EntryField(context, attrs, defStyle)
entryCustomField?.apply { entryCustomField?.apply {
setLabel(title) setLabel(title)
setValue(value.toString(), value.isProtected) setValue(value.toString(), value.isProtected)
activateActionButton(allowCopy) setValueAutoLink(true)
assignActionButtonClickListener(onActionClickListener) activateCopyButton(allowCopy)
assignCopyButtonClickListener(onCopyButtonClickListener)
applyFontVisibility(fontInVisibility) applyFontVisibility(fontInVisibility)
} }
entryCustomField?.let { entryCustomField?.let {
@@ -364,17 +317,18 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE
} }
fun assignAttachments(attachments: ArrayList<EntryAttachment>, fun assignAttachments(attachments: Set<Attachment>,
onAttachmentClicked: (attachment: EntryAttachment)->Unit) { streamDirection: StreamDirection,
onAttachmentClicked: (attachment: Attachment)->Unit) {
showAttachments(attachments.isNotEmpty()) showAttachments(attachments.isNotEmpty())
attachmentsAdapter.assignItems(attachments) attachmentsAdapter.assignItems(attachments.map { EntryAttachmentState(it, streamDirection) })
attachmentsAdapter.onItemClickListener = { item -> attachmentsAdapter.onItemClickListener = { item ->
onAttachmentClicked.invoke(item) onAttachmentClicked.invoke(item.attachment)
} }
} }
fun updateAttachmentDownloadProgress(attachmentToDownload: EntryAttachment) { fun putAttachment(attachmentToDownload: EntryAttachmentState) {
attachmentsAdapter.updateProgress(attachmentToDownload) attachmentsAdapter.putItem(attachmentToDownload)
} }
/* ------------- /* -------------

View File

@@ -33,14 +33,16 @@ import com.google.android.material.textfield.TextInputLayout
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.adapters.EntryExtraFieldsItemsAdapter import com.kunzisoft.keepass.adapters.EntryExtraFieldsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment
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.icons.IconDrawableFactory import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.icons.assignDefaultDatabaseIcon import com.kunzisoft.keepass.icons.assignDefaultDatabaseIcon
import com.kunzisoft.keepass.model.EntryAttachment import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.model.FocusedEditField import com.kunzisoft.keepass.model.FocusedEditField
import com.kunzisoft.keepass.model.StreamDirection
import org.joda.time.Duration import org.joda.time.Duration
import org.joda.time.Instant import org.joda.time.Instant
@@ -68,7 +70,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
private val attachmentsListView: RecyclerView private val attachmentsListView: RecyclerView
private val extraFieldsAdapter = EntryExtraFieldsItemsAdapter(context) private val extraFieldsAdapter = EntryExtraFieldsItemsAdapter(context)
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context, true) private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
private var iconColor: Int = 0 private var iconColor: Int = 0
private var expiresInstant: DateInstant = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate()) private var expiresInstant: DateInstant = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
@@ -124,7 +126,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
} }
} }
attachmentsListView?.apply { attachmentsListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
} }
@@ -242,7 +244,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
* ------------- * -------------
*/ */
fun getExtraField(): MutableList<Field> { fun getExtraFields(): List<Field> {
return extraFieldsAdapter.itemsList return extraFieldsAdapter.itemsList
} }
@@ -254,10 +256,13 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
/** /**
* Remove all children and add new views for each field * Remove all children and add new views for each field
*/ */
fun assignExtraFields(fields: List<Field>, focusedExtraField: FocusedEditField? = null) { fun assignExtraFields(fields: List<Field>,
onEditButtonClickListener: ((item: Field)->Unit)?,
focusedExtraField: FocusedEditField? = null) {
extraFieldsContainerView.visibility = if (fields.isEmpty()) View.GONE else View.VISIBLE extraFieldsContainerView.visibility = if (fields.isEmpty()) View.GONE else View.VISIBLE
// Reinit focused field // Reinit focused field
extraFieldsAdapter.assignItems(fields, focusedExtraField) extraFieldsAdapter.assignItems(fields, focusedExtraField)
extraFieldsAdapter.onEditButtonClickListener = onEditButtonClickListener
} }
/** /**
@@ -273,20 +278,74 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
extraFieldsAdapter.putItem(extraField) extraFieldsAdapter.putItem(extraField)
} }
fun replaceExtraField(oldExtraField: Field, newExtraField: Field) {
extraFieldsContainerView.visibility = View.VISIBLE
extraFieldsAdapter.replaceItem(oldExtraField, newExtraField)
}
fun removeExtraField(oldExtraField: Field) {
extraFieldsAdapter.removeItem(oldExtraField)
}
fun getExtraFieldViewPosition(field: Field, position: (Float) -> Unit) {
extraFieldsListView.post {
position.invoke(extraFieldsListView.y
+ (extraFieldsListView.getChildAt(extraFieldsAdapter.indexOf(field))?.y
?: 0F)
)
}
}
/* ------------- /* -------------
* Attachments * Attachments
* ------------- * -------------
*/ */
fun assignAttachments(attachments: ArrayList<EntryAttachment>, fun getAttachments(): List<Attachment> {
onDeleteItem: (attachment: EntryAttachment)->Unit) { return attachmentsAdapter.itemsList.map { it.attachment }
}
fun assignAttachments(attachments: Set<Attachment>,
streamDirection: StreamDirection,
onDeleteItem: (attachment: Attachment)->Unit) {
attachmentsContainerView.visibility = if (attachments.isEmpty()) View.GONE else View.VISIBLE attachmentsContainerView.visibility = if (attachments.isEmpty()) View.GONE else View.VISIBLE
attachmentsAdapter.assignItems(attachments) attachmentsAdapter.assignItems(attachments.map { EntryAttachmentState(it, streamDirection) })
attachmentsAdapter.onDeleteButtonClickListener = { item -> attachmentsAdapter.onDeleteButtonClickListener = { item ->
onDeleteItem.invoke(item) onDeleteItem.invoke(item.attachment)
} }
} }
fun containsAttachment(): Boolean {
return !attachmentsAdapter.isEmpty()
}
fun containsAttachment(attachment: EntryAttachmentState): Boolean {
return attachmentsAdapter.contains(attachment)
}
fun putAttachment(attachment: EntryAttachmentState) {
attachmentsContainerView.visibility = View.VISIBLE
attachmentsAdapter.putItem(attachment)
}
fun removeAttachment(attachment: EntryAttachmentState) {
attachmentsAdapter.removeItem(attachment)
}
fun clearAttachments() {
attachmentsAdapter.clear()
}
fun getAttachmentViewPosition(attachment: EntryAttachmentState, position: (Float) -> Unit) {
attachmentsListView.postDelayed({
position.invoke(attachmentsContainerView.y
+ attachmentsListView.y
+ (attachmentsListView.getChildAt(attachmentsAdapter.indexOf(attachment))?.y
?: 0F)
)
}, 250)
}
/** /**
* Validate or not the entry form * Validate or not the entry form
* *

View File

@@ -1,77 +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.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.kunzisoft.keepass.R
class EntryExtraField @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: LinearLayout(context, attrs, defStyle) {
private val labelView: TextView
private val valueView: TextView
private val actionImageView: ImageView
var isProtected = false
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.item_entry_extra_field, this)
labelView = findViewById(R.id.title)
valueView = findViewById(R.id.value)
actionImageView = findViewById(R.id.action_image)
}
fun applyFontVisibility(fontInVisibility: Boolean) {
if (fontInVisibility)
valueView.applyFontVisibility()
}
fun setLabel(label: String?) {
labelView.text = label ?: ""
}
fun setValue(value: String?, isProtected: Boolean = false) {
valueView.text = value ?: ""
this.isProtected = isProtected
}
fun setHiddenPasswordStyle(hiddenStyle: Boolean) {
valueView.applyHiddenStyle(isProtected && hiddenStyle)
}
fun activateActionButton(enable: Boolean) {
// Reverse because isActivated show custom color and allow click
actionImageView.isActivated = !enable
}
fun assignActionButtonClickListener(onClickActionListener: OnClickListener?) {
actionImageView.setOnClickListener(onClickActionListener)
actionImageView.visibility = if (onClickActionListener == null) GONE else VISIBLE
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.view
import android.content.Context
import android.text.util.Linkify
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.core.text.util.LinkifyCompat
import com.kunzisoft.keepass.R
class EntryField @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: LinearLayout(context, attrs, defStyle) {
private val labelView: TextView
private val valueView: TextView
private val showButtonView: ImageView
private val copyButtonView: ImageView
private var isProtected = false
var hiddenProtectedValue: Boolean
get() {
return showButtonView.isSelected
}
set(value) {
showButtonView.isSelected = !value
changeProtectedValueParameters()
}
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.item_entry_field, this)
labelView = findViewById(R.id.entry_field_label)
valueView = findViewById(R.id.entry_field_value)
showButtonView = findViewById(R.id.entry_field_show)
copyButtonView = findViewById(R.id.entry_field_copy)
copyButtonView.visibility = View.GONE
}
fun applyFontVisibility(fontInVisibility: Boolean) {
if (fontInVisibility)
valueView.applyFontVisibility()
}
fun setLabel(label: String?) {
labelView.text = label ?: ""
}
fun setLabel(@StringRes labelId: Int) {
labelView.setText(labelId)
}
fun setValue(value: String?,
isProtected: Boolean = false) {
valueView.text = value ?: ""
this.isProtected = isProtected
showButtonView.visibility = if (isProtected) View.VISIBLE else View.GONE
showButtonView.setOnClickListener {
showButtonView.isSelected = !showButtonView.isSelected
changeProtectedValueParameters()
}
changeProtectedValueParameters()
}
fun setValue(@StringRes valueId: Int,
isProtected: Boolean = false) {
setValue(resources.getString(valueId), isProtected)
}
private fun changeProtectedValueParameters() {
valueView.apply {
if (isProtected) {
isFocusable = false
} else {
setTextIsSelectable(true)
}
applyHiddenStyle(isProtected && !showButtonView.isSelected)
if (valueView.autoLinkMask == Linkify.ALL) {
try {
LinkifyCompat.addLinks(this, Linkify.ALL)
} catch (e: Exception) {}
}
}
}
fun setValueAutoLink(autoLink: Boolean) {
valueView.autoLinkMask = if (autoLink && !isProtected) Linkify.ALL else 0
changeProtectedValueParameters()
}
fun activateCopyButton(enable: Boolean) {
// Reverse because isActivated show custom color and allow click
copyButtonView.isActivated = !enable
}
fun assignCopyButtonClickListener(onClickActionListener: OnClickListener?) {
copyButtonView.setOnClickListener(onClickActionListener)
copyButtonView.visibility = if (onClickActionListener == null) GONE else VISIBLE
}
}

View File

@@ -44,13 +44,15 @@ fun TextView.applyFontVisibility() {
typeface = typeFace typeface = typeFace
} }
fun TextView.applyHiddenStyle(hide: Boolean) { fun TextView.applyHiddenStyle(hide: Boolean, changeMaxLines: Boolean = true) {
if (hide) { if (hide) {
transformationMethod = PasswordTransformationMethod.getInstance() transformationMethod = PasswordTransformationMethod.getInstance()
maxLines = 1 if (changeMaxLines)
maxLines = 1
} else { } else {
transformationMethod = null transformationMethod = null
maxLines = 800 if (changeMaxLines)
maxLines = 800
} }
} }

View File

@@ -60,8 +60,12 @@ class FileDatabaseInfo : Serializable {
fun getModificationString(): String? { fun getModificationString(): String? {
return documentFile?.lastModified()?.let { return documentFile?.lastModified()?.let {
DateFormat.getDateTimeInstance() if (it != 0L) {
.format(Date(it)) DateFormat.getDateTimeInstance()
.format(Date(it))
} else {
null
}
} }
} }
@@ -74,7 +78,6 @@ class FileDatabaseInfo : Serializable {
fun retrieveDatabaseAlias(alias: String): String? { fun retrieveDatabaseAlias(alias: String): String? {
return when { return when {
alias.isNotEmpty() -> alias alias.isNotEmpty() -> alias
PreferencesUtil.isFullFilePathEnable(context) -> fileUri?.path
else -> if (exists) documentFile?.name else fileUri?.path else -> if (exists) documentFile?.name else fileUri?.path
} }
} }

View File

@@ -1,9 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:drawable="@drawable/ic_arrow_up_white_24dp"
android:height="24dp" android:fromDegrees="180"
android:viewportHeight="24" android:toDegrees="180"
android:viewportWidth="24"> android:visible="true" />
<path
android:fillColor="#FFFFFF"
android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
</vector>

View File

@@ -1,9 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:drawable="@drawable/ic_arrow_left_white_24dp"
android:height="24dp" android:fromDegrees="180"
android:viewportWidth="24.0" android:toDegrees="180"
android:viewportHeight="24.0"> android:visible="true" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/>
</vector>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="true"
android:drawable="@drawable/ic_file_upload_white_24dp" />
<item android:state_activated="false"
android:drawable="@drawable/ic_file_download_white_24dp" />
</selector>

View File

@@ -0,0 +1,5 @@
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_file_download_white_24dp"
android:fromDegrees="180"
android:toDegrees="180"
android:visible="true" />

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/background_button_color_secondary"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.97,0.89 1.66,0.89L22,21c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM9,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM14,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19,13.5c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true"
android:drawable="@drawable/ic_visibility_white_24dp" />
<item android:state_selected="false"
android:drawable="@drawable/ic_visibility_off_white_24dp" />
</selector>

View File

@@ -69,9 +69,6 @@
android:id="@+id/entry_edit_bottom_bar" android:id="@+id/entry_edit_bottom_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:theme="@style/Widget.MaterialComponents.BottomAppBar"
android:backgroundTint="?attr/colorAccent"
app:backgroundTint="?attr/colorAccent"
app:fabAlignmentMode="center" app:fabAlignmentMode="center"
app:hideOnScroll="false" app:hideOnScroll="false"
app:layout_scrollFlags="scroll|enterAlways" app:layout_scrollFlags="scroll|enterAlways"

View File

@@ -124,26 +124,13 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:theme="?attr/toolbarAppearance"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_info_white_24dp"
app:navigationContentDescription="@string/about"
android:elevation="4dp" android:elevation="4dp"
app:layout_collapseMode="pin" app:layout_collapseMode="pin"
android:theme="?attr/toolbarHomeAppearance"
android:popupTheme="?attr/toolbarPopupAppearance" /> android:popupTheme="?attr/toolbarPopupAppearance" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
android:layout_gravity="top|start"
app:layout_collapseMode="pin">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/file_manager_explanation_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_info_white_24dp"
android:contentDescription="@string/about"
style="@style/KeepassDXStyle.ImageButton.Simple.Secondary"/>
</FrameLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@@ -156,8 +156,7 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:theme="?attr/actionToolbarAppearance" style="?attr/actionToolbarAppearance" />
android:background="?attr/colorAccent" />
<include <include
layout="@layout/view_button_lock" layout="@layout/view_button_lock"

View File

@@ -34,9 +34,11 @@
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/entry_custom_field_label_container" android:id="@+id/entry_custom_field_label_container"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_custom_field_delete">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/entry_custom_field_label" android:id="@+id/entry_custom_field_label"
@@ -49,6 +51,16 @@
android:maxLines="1" /> android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/entry_custom_field_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_content_delete_white_24dp"
android:contentDescription="@string/menu_delete"
style="@style/KeepassDXStyle.ImageButton.Simple" />
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/entry_custom_field_protection" android:id="@+id/entry_custom_field_protection"
style="@style/KeepassDXStyle.TextAppearance.Small" style="@style/KeepassDXStyle.TextAppearance.Small"

View File

@@ -47,5 +47,7 @@
android:scrollbars="vertical" android:scrollbars="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:attr/windowBackground" /> android:background="?android:attr/windowBackground"
android:paddingBottom="?attr/actionBarSize"
android:clipToPadding="false" />
</FrameLayout> </FrameLayout>

View File

@@ -23,6 +23,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
tools:targetApi="p" tools:targetApi="p"
android:id="@+id/item_attachment_container" android:id="@+id/item_attachment_container"
android:focusable="false"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@@ -31,7 +32,6 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container" app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
@@ -70,40 +70,44 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="48dp" android:layout_width="wrap_content"
android:layout_height="48dp"> android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/item_attachment_delete_button" android:id="@+id/item_attachment_delete_button"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_gravity="center"
android:contentDescription="@string/content_description_remove_field" android:contentDescription="@string/content_description_remove_field"
android:focusable="true"
android:src="@drawable/ic_content_delete_white_24dp" android:src="@drawable/ic_content_delete_white_24dp"
style="@style/KeepassDXStyle.ImageButton.Simple" /> style="@style/KeepassDXStyle.ImageButton.Simple" />
<FrameLayout <FrameLayout
android:id="@+id/item_attachment_progress_container" android:id="@+id/item_attachment_progress_container"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent"> android:layout_height="wrap_content"
android:focusable="false"
android:layout_gravity="center">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/item_attachment_icon" android:id="@+id/item_attachment_icon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:padding="6dp" android:layout_gravity="center"
android:src="@drawable/ic_file_download_white_24dp" android:src="@drawable/ic_file_stream_white_24dp"
android:contentDescription="@string/download" android:contentDescription="@string/download"
android:tint="?attr/colorAccent" /> style="@style/KeepassDXStyle.ImageButton.Simple" />
<ProgressBar <ProgressBar
android:id="@+id/item_attachment_progress" android:id="@+id/item_attachment_progress"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
style="@style/KeepassDXStyle.ProgressBar.Circle" style="@style/KeepassDXStyle.ProgressBar.Circle"
android:layout_width="match_parent" android:layout_width="36dp"
android:layout_height="match_parent" android:layout_height="36dp"
android:max="100" android:max="100"
android:progress="60" /> android:progress="60" />
</FrameLayout> </FrameLayout>

View File

@@ -32,7 +32,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_delete"> app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_edit">
<com.kunzisoft.keepass.view.EditTextSelectable <com.kunzisoft.keepass.view.EditTextSelectable
android:id="@+id/entry_extra_field_value" android:id="@+id/entry_extra_field_value"
@@ -46,14 +46,15 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/entry_extra_field_delete" android:id="@+id/entry_extra_field_edit"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_content_delete_white_24dp" android:src="@drawable/ic_more_white_24dp"
android:contentDescription="@string/menu_delete" android:contentDescription="@string/menu_edit"
style="@style/KeepassDXStyle.ImageButton.Simple"/> style="@style/KeepassDXStyle.ImageButton.Simple"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -24,26 +24,34 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView <TextView
android:id="@+id/title" android:id="@+id/entry_field_label"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/action_image" app:layout_constraintEnd_toStartOf="@+id/entry_field_show"
tools:text="title" tools:text="title"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" /> style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<TextView <TextView
android:id="@+id/value" android:id="@+id/entry_field_value"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/title" app:layout_constraintTop_toBottomOf="@+id/entry_field_label"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/action_image" app:layout_constraintEnd_toStartOf="@+id/entry_field_show"
android:textIsSelectable="true"
tools:text="value" tools:text="value"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" /> style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_image" android:id="@+id/entry_field_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_field_copy"
android:src="@drawable/ic_visibility_state"
android:contentDescription="@string/menu_showpass"
style="@style/KeepassDXStyle.ImageButton.Simple"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/entry_field_copy"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"

View File

@@ -176,38 +176,49 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/file_precise_info_container" android:id="@+id/file_precise_info_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/file_delete_button"> app:layout_constraintEnd_toStartOf="@+id/file_delete_button">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_modification" <LinearLayout
android:id="@+id/file_modification_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
tools:text="3:40:14 PM"
android:textColor="?android:attr/textColorHintInverse"
android:paddingStart="@dimen/default_margin" android:paddingStart="@dimen/default_margin"
android:paddingLeft="@dimen/default_margin" android:paddingLeft="@dimen/default_margin"
android:paddingEnd="@dimen/default_margin" android:paddingEnd="@dimen/default_margin"
android:paddingRight="@dimen/default_margin" android:paddingRight="@dimen/default_margin"
android:paddingBottom="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/file_size" app:layout_constraintEnd_toStartOf="@+id/file_size"
android:gravity="start|bottom"/> android:orientation="vertical"
android:gravity="start|center_vertical" >
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_modification_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/entry_modified"
android:textColor="?android:attr/textColorHintInverse"
android:textSize="12sp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_modification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Aug 21, 2020 3:40:14 PM"
android:textColor="?android:attr/textColorHintInverse"/>
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_size" android:id="@+id/file_size"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:gravity="end|bottom" android:gravity="end|center_vertical"
android:textColor="?android:attr/textColorHintInverse" android:textColor="?android:attr/textColorHintInverse"
android:paddingBottom="12dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/file_modification" app:layout_constraintStart_toEndOf="@+id/file_modification_container"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="@dimen/default_margin" android:layout_marginStart="@dimen/default_margin"
android:layout_marginEnd="@dimen/default_margin" android:layout_marginEnd="@dimen/default_margin"

View File

@@ -25,7 +25,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/KeepassDXStyle.Selectable.Item"> style="@style/KeepassDXStyle.Selectable.Item">
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
@@ -46,20 +46,26 @@
android:layout_marginEnd="@dimen/image_list_margin" android:layout_marginEnd="@dimen/image_list_margin"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/ic_blank_32dp" android:src="@drawable/ic_blank_32dp"
android:layout_centerVertical="true" app:layout_constraintTop_toTopOf="parent"
android:layout_alignParentLeft="true" app:layout_constraintBottom_toBottomOf="parent"
android:layout_alignParentStart="true" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="2dp" android:paddingTop="2dp"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:layout_centerVertical="true" android:layout_marginLeft="@dimen/image_list_margin"
android:layout_alignParentRight="true" android:layout_marginStart="@dimen/image_list_margin"
android:layout_alignParentEnd="true" android:layout_marginRight="@dimen/image_list_margin"
android:layout_toRightOf="@+id/node_icon" android:layout_marginEnd="@dimen/image_list_margin"
android:layout_toEndOf="@+id/node_icon"> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/node_icon"
app:layout_constraintLeft_toRightOf="@+id/node_icon"
app:layout_constraintEnd_toStartOf="@+id/node_attachment_icon"
app:layout_constraintRight_toLeftOf="@+id/node_attachment_icon">
<TextView <TextView
android:id="@+id/node_text" android:id="@+id/node_text"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -78,5 +84,19 @@
android:singleLine="true" android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle" /> style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle" />
</LinearLayout> </LinearLayout>
</RelativeLayout> <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/node_attachment_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/image_list_margin"
android:layout_marginStart="@dimen/image_list_margin"
android:layout_marginRight="@dimen/image_list_margin"
android:layout_marginEnd="@dimen/image_list_margin"
android:src="@drawable/ic_attach_file_white_24dp"
style="@style/KeepassDXStyle.TextAppearance.Entry.Icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/edit_text_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/edit_text_show">
<com.kunzisoft.keepass.view.EditTextSelectable
android:id="@+id/edit_text_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:focusable="true"
android:focusableInTouchMode="true"/>
</com.google.android.material.textfield.TextInputLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/edit_text_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_visibility_state"
android:contentDescription="@string/menu_showpass"
style="@style/KeepassDXStyle.ImageButton.Simple"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -40,153 +40,41 @@
android:orientation="vertical"> android:orientation="vertical">
<!-- Username --> <!-- Username -->
<androidx.constraintlayout.widget.ConstraintLayout <com.kunzisoft.keepass.view.EntryField
android:id="@+id/entry_user_name_container" android:id="@+id/entry_user_name_field"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"> android:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_user_name_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_user_name_action_image"
android:text="@string/entry_user_name"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/entry_user_name_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_user_name_action_image"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/entry_user_name_action_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_content_copy_white_24dp"
android:contentDescription="@string/menu_copy"
style="@style/KeepassDXStyle.ImageButton.Simple" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Password --> <!-- Password -->
<androidx.constraintlayout.widget.ConstraintLayout <com.kunzisoft.keepass.view.EntryField
android:id="@+id/entry_password_container" android:id="@+id/entry_password_field"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"> android:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_password_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_password_action_image"
android:text="@string/entry_password"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_password"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/entry_password_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_password_action_image"
android:focusable="false"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/entry_password_action_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_content_copy_white_24dp"
android:contentDescription="@string/menu_copy"
style="@style/KeepassDXStyle.ImageButton.Simple" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- OTP --> <!-- OTP -->
<androidx.constraintlayout.widget.ConstraintLayout <com.kunzisoft.keepass.view.EntryField
android:id="@+id/entry_otp_container" android:id="@+id/entry_otp_field"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"> android:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_otp_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_otp_action_image"
android:text="@string/entry_otp"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_otp"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/entry_otp_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_otp_action_image"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/entry_otp_action_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_content_copy_white_24dp"
android:contentDescription="@string/menu_copy"
style="@style/KeepassDXStyle.ImageButton.Simple" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- URL --> <!-- URL -->
<LinearLayout <com.kunzisoft.keepass.view.EntryField
android:id="@+id/entry_url_container" android:id="@+id/entry_url_field"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone"> android:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_url_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_url"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="all"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
</LinearLayout>
<!-- Comment --> <!-- Notes -->
<LinearLayout <com.kunzisoft.keepass.view.EntryField
android:id="@+id/entry_notes_container" android:id="@+id/entry_notes_field"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone"> android:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_notes_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_notes"
android:autoLink="all"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_notes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View File

@@ -190,7 +190,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="start" android:gravity="start"
android:maxLines="20"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:importantForAutofill="no" android:importantForAutofill="no"
android:inputType="textMultiLine" android:inputType="textMultiLine"
@@ -214,6 +213,7 @@
android:id="@+id/extra_fields_list" android:id="@+id/extra_fields_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:layout_margin="@dimen/card_view_padding" android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical"> android:orientation="vertical">
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
@@ -247,7 +247,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/entry_attachments_list" android:id="@+id/entry_attachments_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View File

@@ -19,11 +19,6 @@
--> -->
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_toggle_pass"
android:icon="@drawable/ic_visibility_white_24dp"
android:title="@string/menu_showpass"
android:orderInCategory="21"
app:showAsAction="always" />
<item android:id="@+id/menu_edit" <item android:id="@+id/menu_edit"
android:icon="@drawable/ic_mode_edit_white_24dp" android:icon="@drawable/ic_mode_edit_white_24dp"
android:title="@string/menu_edit" android:title="@string/menu_edit"

View File

@@ -26,13 +26,11 @@
android:title="@string/entry_add_field" android:title="@string/entry_add_field"
android:orderInCategory="92" android:orderInCategory="92"
app:showAsAction="always" /> app:showAsAction="always" />
<!--
<item android:id="@+id/menu_add_attachment" <item android:id="@+id/menu_add_attachment"
android:icon="@drawable/ic_attach_file_white_24dp" android:icon="@drawable/ic_attach_file_white_24dp"
android:title="@string/entry_add_attachment" android:title="@string/entry_add_attachment"
android:orderInCategory="93" android:orderInCategory="93"
app:showAsAction="always" /> app:showAsAction="always" />
-->
<item android:id="@+id/menu_add_otp" <item android:id="@+id/menu_add_otp"
android:icon="@drawable/ic_otp_white_24dp" android:icon="@drawable/ic_otp_white_24dp"
android:title="@string/entry_setup_otp" android:title="@string/entry_setup_otp"

View File

@@ -43,7 +43,7 @@
<string name="entry_title">العنوان</string> <string name="entry_title">العنوان</string>
<string name="entry_url">رابط</string> <string name="entry_url">رابط</string>
<string name="entry_user_name">اسم المستخدم</string> <string name="entry_user_name">اسم المستخدم</string>
<string name="error_file_not_create">تعذر إنشاء الملف :</string> <string name="error_file_not_create">تعذر إنشاء الملف</string>
<string name="error_invalid_path">تأكد أن المسار صحيح.</string> <string name="error_invalid_path">تأكد أن المسار صحيح.</string>
<string name="error_no_name">ادخل اسمًا.</string> <string name="error_no_name">ادخل اسمًا.</string>
<string name="error_pass_match">كلمتا السر غير متطابقتين.</string> <string name="error_pass_match">كلمتا السر غير متطابقتين.</string>
@@ -189,8 +189,6 @@
<string name="unavailable_feature_version">نسخة الاندرويد %1$s لا تحقق ادنى متطلبات السنخة %2$s.</string> <string name="unavailable_feature_version">نسخة الاندرويد %1$s لا تحقق ادنى متطلبات السنخة %2$s.</string>
<string name="file_name">اسم الملف</string> <string name="file_name">اسم الملف</string>
<string name="path">مسار</string> <string name="path">مسار</string>
<string name="full_file_path_enable_title">مسار الملف</string>
<string name="full_file_path_enable_summary">عرض المسار الكامل للملف</string>
<string name="configure_biometric">فحص البصمة مدعوم لكنه ليس معد.</string> <string name="configure_biometric">فحص البصمة مدعوم لكنه ليس معد.</string>
<string name="open_biometric_prompt_unlock_database">فحص البصمة</string> <string name="open_biometric_prompt_unlock_database">فحص البصمة</string>
<string name="biometric_invalid_key">لا يمكن قراءة مفتاح البصمة. <string name="biometric_invalid_key">لا يمكن قراءة مفتاح البصمة.

View File

@@ -57,7 +57,7 @@
<string name="entry_user_name">Usuari</string> <string name="entry_user_name">Usuari</string>
<string name="error_arc4">L\'encriptació Arcfour no està suportada.</string> <string name="error_arc4">L\'encriptació Arcfour no està suportada.</string>
<string name="error_can_not_handle_uri">KeePassDX no pot manejar aquesta URI.</string> <string name="error_can_not_handle_uri">KeePassDX no pot manejar aquesta URI.</string>
<string name="error_file_not_create">No s\'ha pogut crear l\'arxiu:</string> <string name="error_file_not_create">No s\'ha pogut crear l\'arxiu</string>
<string name="error_invalid_db">No s\'ha pogut llegir la base de dades.</string> <string name="error_invalid_db">No s\'ha pogut llegir la base de dades.</string>
<string name="error_invalid_path">Assegureu-vos que el camí eś correcte.</string> <string name="error_invalid_path">Assegureu-vos que el camí eś correcte.</string>
<string name="error_no_name">Introduïu-hi un nom.</string> <string name="error_no_name">Introduïu-hi un nom.</string>
@@ -166,7 +166,6 @@
<string name="recycle_bin_title">Ús de la paperera</string> <string name="recycle_bin_title">Ús de la paperera</string>
<string name="database_data_compression_summary">La compressió de les dades redueix la mida de la base de dades.</string> <string name="database_data_compression_summary">La compressió de les dades redueix la mida de la base de dades.</string>
<string name="database_data_compression_title">Compressió de les dades</string> <string name="database_data_compression_title">Compressió de les dades</string>
<string name="full_file_path_enable_title">Ruta del fitxer</string>
<string name="assign_master_key">Assigna una contrasenya mestra</string> <string name="assign_master_key">Assigna una contrasenya mestra</string>
<string name="path">Camí</string> <string name="path">Camí</string>
<string name="file_name">Nom de fitxer</string> <string name="file_name">Nom de fitxer</string>

View File

@@ -59,8 +59,8 @@
<string name="entry_user_name">Uživatelské jméno</string> <string name="entry_user_name">Uživatelské jméno</string>
<string name="error_arc4">Arcfour proudová šifra není podporována.</string> <string name="error_arc4">Arcfour proudová šifra není podporována.</string>
<string name="error_can_not_handle_uri">KeePassDX nemůže zpracovat toto URI.</string> <string name="error_can_not_handle_uri">KeePassDX nemůže zpracovat toto URI.</string>
<string name="error_file_not_create">Soubor se nedaří vytvořit:</string> <string name="error_file_not_create">Soubor se nedaří vytvořit</string>
<string name="error_invalid_db">Nelze přečíst databázi.</string> <string name="error_invalid_db">Databázi nelze číst.</string>
<string name="error_invalid_path">Neplatná cesta.</string> <string name="error_invalid_path">Neplatná cesta.</string>
<string name="error_no_name">Vložte jméno.</string> <string name="error_no_name">Vložte jméno.</string>
<string name="error_nokeyfile">Vyberte soubor s klíčem.</string> <string name="error_nokeyfile">Vyberte soubor s klíčem.</string>
@@ -169,7 +169,7 @@
<string name="menu_move">Přesunout</string> <string name="menu_move">Přesunout</string>
<string name="menu_paste">Vložit</string> <string name="menu_paste">Vložit</string>
<string name="menu_cancel">Storno</string> <string name="menu_cancel">Storno</string>
<string name="menu_biometric_remove_key">Smaž uložený otisk prstu</string> <string name="menu_biometric_remove_key">Smazat uložený biometrický klíč</string>
<string name="menu_file_selection_read_only">Chráněno před zápisem</string> <string name="menu_file_selection_read_only">Chráněno před zápisem</string>
<string name="menu_open_file_read_and_write">Čtení a zápis</string> <string name="menu_open_file_read_and_write">Čtení a zápis</string>
<string name="read_only">Chráněno před zápisem</string> <string name="read_only">Chráněno před zápisem</string>
@@ -214,7 +214,7 @@
<string name="list_password_generator_options_summary">Nastavit povolené znaky pro generátor hesel</string> <string name="list_password_generator_options_summary">Nastavit povolené znaky pro generátor hesel</string>
<string name="clipboard">Schránka</string> <string name="clipboard">Schránka</string>
<string name="clipboard_notifications_title">Oznamování schránky</string> <string name="clipboard_notifications_title">Oznamování schránky</string>
<string name="clipboard_notifications_summary">Zapnout oznamování schránky o kopírování pole při prohlížení záznamu</string> <string name="clipboard_notifications_summary">Ukázat oznamení schránky o kopírování pole při prohlížení záznamu</string>
<string name="clipboard_warning">Vymazat historii schránky manuálně, pokud automatické vymazání schránky selže.</string> <string name="clipboard_warning">Vymazat historii schránky manuálně, pokud automatické vymazání schránky selže.</string>
<string name="lock">Zamknout</string> <string name="lock">Zamknout</string>
<string name="lock_database_screen_off_title">Zámek obrazovky</string> <string name="lock_database_screen_off_title">Zámek obrazovky</string>
@@ -226,14 +226,12 @@
<string name="biometric_delete_all_key_summary">Smazat všechny šifrovací klíče související s biometrickým rozlišením</string> <string name="biometric_delete_all_key_summary">Smazat všechny šifrovací klíče související s biometrickým rozlišením</string>
<string name="biometric_delete_all_key_warning">Smazat všechny šifrovací klíče související s biometrickým rozlišením\?</string> <string name="biometric_delete_all_key_warning">Smazat všechny šifrovací klíče související s biometrickým rozlišením\?</string>
<string name="unavailable_feature_text">Tuto funkci se nedaří spustit.</string> <string name="unavailable_feature_text">Tuto funkci se nedaří spustit.</string>
<string name="unavailable_feature_version">Verze %1$s vámi používaného systému Android nevyhovuje minimální verzi %2$s.</string> <string name="unavailable_feature_version">V zařízení je instalován Android %1$s, ale potřebná je verze %2$s a novější.</string>
<string name="unavailable_feature_hardware">Hardware nebyl rozpoznán.</string> <string name="unavailable_feature_hardware">Hardware nebyl rozpoznán.</string>
<string name="file_name">Název souboru</string> <string name="file_name">Název souboru</string>
<string name="path">Cesta</string> <string name="path">Cesta</string>
<string name="assign_master_key">Přiřadit hlavní klíč</string> <string name="assign_master_key">Přiřadit hlavní klíč</string>
<string name="create_keepass_file">Vytvořit novou databázi</string> <string name="create_keepass_file">Vytvořit novou databázi</string>
<string name="full_file_path_enable_title">Cesta k souboru</string>
<string name="full_file_path_enable_summary">Zobrazit úplnou cestu k souboru</string>
<string name="recycle_bin_title">Využití koše</string> <string name="recycle_bin_title">Využití koše</string>
<string name="recycle_bin_summary">Před smazáním přesune skupiny a položky do skupiny „Koš“</string> <string name="recycle_bin_summary">Před smazáním přesune skupiny a položky do skupiny „Koš“</string>
<string name="monospace_font_fields_enable_title">Písmo položek</string> <string name="monospace_font_fields_enable_title">Písmo položek</string>
@@ -251,7 +249,7 @@
<string name="magic_keyboard_title">Magikeyboard</string> <string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_explanation_summary">Aktivovat vlastní klávesnici, která snadno vyplní hesla a další položky identity</string> <string name="magic_keyboard_explanation_summary">Aktivovat vlastní klávesnici, která snadno vyplní hesla a další položky identity</string>
<string name="allow_no_password_title">Umožnit bez hlavního klíče</string> <string name="allow_no_password_title">Umožnit bez hlavního klíče</string>
<string name="allow_no_password_summary">Povolit tlačítko \"Otevřít\", i když není vybráno žádné heslo</string> <string name="allow_no_password_summary">Povolit klepnutí na \"Otevřít\", i když není vybráno žádné heslo</string>
<string name="enable_read_only_title">Chráněno před zápisem</string> <string name="enable_read_only_title">Chráněno před zápisem</string>
<string name="enable_read_only_summary">Ve výchozím stavu otevřít databázi pouze pro čtení</string> <string name="enable_read_only_summary">Ve výchozím stavu otevřít databázi pouze pro čtení</string>
<string name="enable_education_screens_title">Vzdělávací nápovědy</string> <string name="enable_education_screens_title">Vzdělávací nápovědy</string>
@@ -269,8 +267,8 @@
\nSkupiny (ekvivalent složek) organizují záznamy v databázi.</string> \nSkupiny (ekvivalent složek) organizují záznamy v databázi.</string>
<string name="education_search_title">Hledejte v položkách</string> <string name="education_search_title">Hledejte v položkách</string>
<string name="education_search_summary">Zadejte název, uživatelské jméno nebo jiné položky k nalezení svých hesel.</string> <string name="education_search_summary">Zadejte název, uživatelské jméno nebo jiné položky k nalezení svých hesel.</string>
<string name="education_biometric_title">Odemykání databáze otiskem prstu</string> <string name="education_biometric_title">Odemknutí databáze biometricky</string>
<string name="education_biometric_summary">Propojte své heslo a otisk prstu pro rychlé odemykání databáze.</string> <string name="education_biometric_summary">Propojte své heslo s načtenou biometrikou pro rychlé odemknutí databáze.</string>
<string name="education_entry_edit_title">Upravit položku</string> <string name="education_entry_edit_title">Upravit položku</string>
<string name="education_entry_edit_summary">Přidejte ke své položce vlastní kolonky. Společná data mohou být sdílena mezi více různými kolonkami.</string> <string name="education_entry_edit_summary">Přidejte ke své položce vlastní kolonky. Společná data mohou být sdílena mezi více různými kolonkami.</string>
<string name="education_generate_password_title">Vytvořit silné heslo</string> <string name="education_generate_password_title">Vytvořit silné heslo</string>
@@ -419,7 +417,7 @@
<string name="database_custom_color_title">Vlastní barva databáze</string> <string name="database_custom_color_title">Vlastní barva databáze</string>
<string name="compression">Komprese</string> <string name="compression">Komprese</string>
<string name="compression_none">Žádná</string> <string name="compression_none">Žádná</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Nastavení klávesnice zařízení</string> <string name="device_keyboard_setting_title">Nastavení klávesnice zařízení</string>
<string name="error_save_database">Nebylo možno uložit databázi.</string> <string name="error_save_database">Nebylo možno uložit databázi.</string>
<string name="menu_save_database">Uložit databázi</string> <string name="menu_save_database">Uložit databázi</string>
@@ -440,18 +438,18 @@
<string name="download_initialization">Zahajuji…</string> <string name="download_initialization">Zahajuji…</string>
<string name="download_progression">Probíhá: %1$d%%</string> <string name="download_progression">Probíhá: %1$d%%</string>
<string name="download_finalization">Dokončuji…</string> <string name="download_finalization">Dokončuji…</string>
<string name="download_complete">Ukončeno! Klepnout pro otevření souboru.</string> <string name="download_complete">Kompletní!</string>
<string name="hide_expired_entries_title">Skrýt propadlé záznamy</string> <string name="hide_expired_entries_title">Skrýt propadlé záznamy</string>
<string name="hide_expired_entries_summary">Propadlé záznamy jsou skryty</string> <string name="hide_expired_entries_summary">Propadlé záznamy nejsou ukázány</string>
<string name="contact">Kontakt</string> <string name="contact">Kontakt</string>
<string name="contribution">Příspěvky</string> <string name="contribution">Příspěvky</string>
<string name="feedback">Feedback</string> <string name="feedback">Feedback</string>
<string name="auto_focus_search_title">Snadné hledání</string> <string name="auto_focus_search_title">Snadné hledání</string>
<string name="auto_focus_search_summary">Při otevření databáze žádat hledání</string> <string name="auto_focus_search_summary">Při otevření databáze žádat hledání</string>
<string name="remember_database_locations_title">Uložit umístění databází</string> <string name="remember_database_locations_title">Pamatovat si umístění databází</string>
<string name="remember_database_locations_summary">Pamatovat si umístění databází</string> <string name="remember_database_locations_summary">Uchová informaci o tom, kde jsou databáze uloženy</string>
<string name="remember_keyfile_locations_title">Uložit umístění souborů s klíči</string> <string name="remember_keyfile_locations_title">Pamatovat si umístění souborů s klíči</string>
<string name="remember_keyfile_locations_summary">Pamatovat si umístění souborů s klíči</string> <string name="remember_keyfile_locations_summary">Uchová informaci o tom, kde jsou uloženy soubory s klíči</string>
<string name="show_recent_files_title">Ukázat nedávné soubory</string> <string name="show_recent_files_title">Ukázat nedávné soubory</string>
<string name="show_recent_files_summary">Ukázat umístění nedávných databází</string> <string name="show_recent_files_summary">Ukázat umístění nedávných databází</string>
<string name="hide_broken_locations_title">Skrýt špatné odkazy na databáze</string> <string name="hide_broken_locations_title">Skrýt špatné odkazy na databáze</string>

View File

@@ -58,7 +58,7 @@
<string name="entry_user_name">Brugernavn</string> <string name="entry_user_name">Brugernavn</string>
<string name="error_arc4">Arcfour stream cipher er ikke understøttet.</string> <string name="error_arc4">Arcfour stream cipher er ikke understøttet.</string>
<string name="error_can_not_handle_uri">Kunne ikke håndtere URI i KeePassDX.</string> <string name="error_can_not_handle_uri">Kunne ikke håndtere URI i KeePassDX.</string>
<string name="error_file_not_create">Kunne ikke oprette fil:</string> <string name="error_file_not_create">Kunne ikke oprette fil</string>
<string name="error_invalid_db">Kunne ikke læse databasen.</string> <string name="error_invalid_db">Kunne ikke læse databasen.</string>
<string name="error_invalid_path">Sørg for, at stien er korrekt.</string> <string name="error_invalid_path">Sørg for, at stien er korrekt.</string>
<string name="error_no_name">Indtast et navn.</string> <string name="error_no_name">Indtast et navn.</string>
@@ -231,8 +231,6 @@
<string name="path">Sti</string> <string name="path">Sti</string>
<string name="assign_master_key">Tildel en hovednøgle</string> <string name="assign_master_key">Tildel en hovednøgle</string>
<string name="create_keepass_file">Opret en ny database</string> <string name="create_keepass_file">Opret en ny database</string>
<string name="full_file_path_enable_title">Filsti</string>
<string name="full_file_path_enable_summary">Se den fulde filsti</string>
<string name="recycle_bin_title">Brug af papirkurven</string> <string name="recycle_bin_title">Brug af papirkurven</string>
<string name="recycle_bin_summary">Flyt grupper og poster til gruppen \"Papirkurven\" før den slettes</string> <string name="recycle_bin_summary">Flyt grupper og poster til gruppen \"Papirkurven\" før den slettes</string>
<string name="monospace_font_fields_enable_title">Feltskrifttype</string> <string name="monospace_font_fields_enable_title">Feltskrifttype</string>
@@ -268,7 +266,7 @@
\nGrupper (~mapper) organiserer poster i databasen.</string> \nGrupper (~mapper) organiserer poster i databasen.</string>
<string name="education_search_title">Søg i poster</string> <string name="education_search_title">Søg i poster</string>
<string name="education_search_summary">Indtast titel, brugernavn eller indhold af andre felter for at hente adgangskoder.</string> <string name="education_search_summary">Indtast titel, brugernavn eller indhold af andre felter for at hente adgangskoder.</string>
<string name="education_biometric_title">Oplåsning af database ved hjælp af biometrisk</string> <string name="education_biometric_title">Biometrisk oplåsning af databasen</string>
<string name="education_biometric_summary">Knyt adgangskoden til det scannede biometri for hurtigt at låse databasen op.</string> <string name="education_biometric_summary">Knyt adgangskoden til det scannede biometri for hurtigt at låse databasen op.</string>
<string name="education_entry_edit_title">Rediger posten</string> <string name="education_entry_edit_title">Rediger posten</string>
<string name="education_entry_edit_summary">Rediger post med brugerdefinerede felter. Pool data kan refereres mellem forskellige indtastningsfelter.</string> <string name="education_entry_edit_summary">Rediger post med brugerdefinerede felter. Pool data kan refereres mellem forskellige indtastningsfelter.</string>
@@ -419,7 +417,7 @@
<string name="database_custom_color_title">Brugerdefineret databasefarve</string> <string name="database_custom_color_title">Brugerdefineret databasefarve</string>
<string name="compression">Komprimering</string> <string name="compression">Komprimering</string>
<string name="compression_none">Ingen</string> <string name="compression_none">Ingen</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Indstillinger for enhedens tastatur</string> <string name="device_keyboard_setting_title">Indstillinger for enhedens tastatur</string>
<string name="error_save_database">Databasen kunne ikke gemmes.</string> <string name="error_save_database">Databasen kunne ikke gemmes.</string>
<string name="menu_save_database">Gem database</string> <string name="menu_save_database">Gem database</string>
@@ -440,9 +438,9 @@
<string name="download_initialization">Initialiserer…</string> <string name="download_initialization">Initialiserer…</string>
<string name="download_progression">I gang: %1$d%%</string> <string name="download_progression">I gang: %1$d%%</string>
<string name="download_finalization">Færdiggørelse…</string> <string name="download_finalization">Færdiggørelse…</string>
<string name="download_complete">Komplet! Tryk for at åbne filen.</string> <string name="download_complete">Komplet!</string>
<string name="hide_expired_entries_title">Skjul udløbne poster</string> <string name="hide_expired_entries_title">Skjul udløbne poster</string>
<string name="hide_expired_entries_summary">Udløbne poster er skjult</string> <string name="hide_expired_entries_summary">Udløbne poster vises ikke</string>
<string name="contact">Kontakt</string> <string name="contact">Kontakt</string>
<string name="contribution">Bidrag</string> <string name="contribution">Bidrag</string>
<string name="feedback">Tilbagemelding</string> <string name="feedback">Tilbagemelding</string>

View File

@@ -67,7 +67,7 @@
<string name="entry_user_name">Benutzername</string> <string name="entry_user_name">Benutzername</string>
<string name="error_arc4">Die RC4/Arcfour-Stromverschlüsselung wird nicht unterstützt.</string> <string name="error_arc4">Die RC4/Arcfour-Stromverschlüsselung wird nicht unterstützt.</string>
<string name="error_can_not_handle_uri">KeePassDX kann diese URI-Adresse nicht verarbeiten.</string> <string name="error_can_not_handle_uri">KeePassDX kann diese URI-Adresse nicht verarbeiten.</string>
<string name="error_file_not_create">Konnte Datei nicht erstellen:</string> <string name="error_file_not_create">Konnte Datei nicht erstellen</string>
<string name="error_invalid_db">Datenbank nicht lesbar.</string> <string name="error_invalid_db">Datenbank nicht lesbar.</string>
<string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string> <string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string>
<string name="error_no_name">Namen eingeben.</string> <string name="error_no_name">Namen eingeben.</string>
@@ -170,8 +170,8 @@
<string name="menu_appearance_settings">Aussehen</string> <string name="menu_appearance_settings">Aussehen</string>
<string name="password_size_title">Generierte Passwortlänge</string> <string name="password_size_title">Generierte Passwortlänge</string>
<string name="password_size_summary">Legt die Standardlänge des generierten Passworts fest</string> <string name="password_size_summary">Legt die Standardlänge des generierten Passworts fest</string>
<string name="clipboard_notifications_title">Zwischenablagenbenachrichtigungen</string> <string name="clipboard_notifications_title">Zwischenablage-Benachrichtigung</string>
<string name="clipboard_notifications_summary">Benachrichtigungen für die Zwischenablage einschalten, um beim Anzeigen von Eingabefeldern diese kopieren zu können</string> <string name="clipboard_notifications_summary">Benachrichtigungen zur Zwischenablage anzeigen, um beim Betrachten eines Eintrags Felder kopieren zu können</string>
<string name="lock_database_screen_off_title">Bildschirmsperre</string> <string name="lock_database_screen_off_title">Bildschirmsperre</string>
<string name="lock_database_screen_off_summary">Datenbank sperren, wenn der Bildschirm ausgeschaltet wird</string> <string name="lock_database_screen_off_summary">Datenbank sperren, wenn der Bildschirm ausgeschaltet wird</string>
<string name="create_keepass_file">Neue Datenbank erstellen</string> <string name="create_keepass_file">Neue Datenbank erstellen</string>
@@ -198,7 +198,7 @@
<string name="key_derivation_function">Schlüsselableitungsfunktion</string> <string name="key_derivation_function">Schlüsselableitungsfunktion</string>
<string name="extended_ASCII">Erweiterte ASCII</string> <string name="extended_ASCII">Erweiterte ASCII</string>
<string name="allow">Erlauben</string> <string name="allow">Erlauben</string>
<string name="error_autofill_enable_service">Autofill-Dienst kann nicht aktiviert werden.</string> <string name="error_autofill_enable_service">Dienst für automatisches Ausfüllen kann nicht aktiviert werden.</string>
<string name="copy_field">Kopie von %1$s</string> <string name="copy_field">Kopie von %1$s</string>
<string name="menu_form_filling_settings">Formularausfüllung</string> <string name="menu_form_filling_settings">Formularausfüllung</string>
<string name="menu_biometric_remove_key">Gespeicherten biometrischen Schlüssel löschen</string> <string name="menu_biometric_remove_key">Gespeicherten biometrischen Schlüssel löschen</string>
@@ -218,19 +218,17 @@
<string name="sort_last_modify_time">Änderungsdatum</string> <string name="sort_last_modify_time">Änderungsdatum</string>
<string name="sort_last_access_time">Zugriffsdatum</string> <string name="sort_last_access_time">Zugriffsdatum</string>
<string name="biometric_not_recognized">Biometrische Daten nicht erkannt</string> <string name="biometric_not_recognized">Biometrische Daten nicht erkannt</string>
<string name="autofill">Autofill</string> <string name="autofill">Automatisches Ausfüllen</string>
<string name="autofill_service_name">KeePassDX autom. Formularausfüllung</string> <string name="autofill_service_name">KeePassDX autom. Formularausfüllung</string>
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string> <string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string>
<string name="set_autofill_service_title">Autofill-Dienstvorgabe festlegen</string> <string name="set_autofill_service_title">Standarddienst für automatisches Ausfüllen festlegen</string>
<string name="autofill_explanation_summary">Autofill aktivieren, um automatisch Eingabefelder in anderen Apps auszufüllen</string> <string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen</string>
<string name="clipboard">Zwischenablage</string> <string name="clipboard">Zwischenablage</string>
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string> <string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
<string name="biometric_delete_all_key_summary">Alle mit der biometrischen Erkennung verbundenen Verschlüsselungsschlüssel löschen.</string> <string name="biometric_delete_all_key_summary">Alle mit der biometrischen Erkennung verbundenen Verschlüsselungsschlüssel löschen.</string>
<string name="biometric_delete_all_key_warning">Sind Sie sicher, dass Sie alle mit der biometrischen Erkennung verknüpften Schlüssel löschen möchten\?</string> <string name="biometric_delete_all_key_warning">Sind Sie sicher, dass Sie alle mit der biometrischen Erkennung verknüpften Schlüssel löschen möchten\?</string>
<string name="unavailable_feature_version">Die Android-Version, %1$s, erfüllt nicht die Mindestanforderung für Version %2$s.</string> <string name="unavailable_feature_version">Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.</string>
<string name="unavailable_feature_hardware">Keine entsprechende Hardware.</string> <string name="unavailable_feature_hardware">Keine entsprechende Hardware.</string>
<string name="full_file_path_enable_title">Dateipfad</string>
<string name="full_file_path_enable_summary">Vollständigen Dateipfad anzeigen</string>
<string name="recycle_bin_title">Papierkorb-Nutzung</string> <string name="recycle_bin_title">Papierkorb-Nutzung</string>
<string name="recycle_bin_summary">Verschiebt Gruppen oder Einträge in den Papierkorb, bevor sie gelöscht werden.</string> <string name="recycle_bin_summary">Verschiebt Gruppen oder Einträge in den Papierkorb, bevor sie gelöscht werden.</string>
<string name="monospace_font_fields_enable_title">Feldschriftart</string> <string name="monospace_font_fields_enable_title">Feldschriftart</string>
@@ -259,7 +257,7 @@
\nGruppen (wie Ordner) helfen, Einträge in der Datenbank zu ordnen.</string> \nGruppen (wie Ordner) helfen, Einträge in der Datenbank zu ordnen.</string>
<string name="education_search_title">Einträge durchsuchen</string> <string name="education_search_title">Einträge durchsuchen</string>
<string name="education_search_summary">Titel, Benutzernamen oder Inhalte anderer Feldern eingeben, um die Passwörter wiederzufinden.</string> <string name="education_search_summary">Titel, Benutzernamen oder Inhalte anderer Feldern eingeben, um die Passwörter wiederzufinden.</string>
<string name="education_biometric_title">Datenbank-Entsperrung durch Biometrie</string> <string name="education_biometric_title">Biometrische Datenbank-Entsperrung</string>
<string name="education_biometric_summary">Verknüpft Ihr Passwort mit Ihrer gescannten Biometrie, um Ihre Datenbank schnell zu entsperren.</string> <string name="education_biometric_summary">Verknüpft Ihr Passwort mit Ihrer gescannten Biometrie, um Ihre Datenbank schnell zu entsperren.</string>
<string name="education_entry_edit_title">Eintrag bearbeiten</string> <string name="education_entry_edit_title">Eintrag bearbeiten</string>
<string name="education_entry_edit_summary">Bearbeiten Sie Ihren Eintrag mit persönlichen Feldern. Persönliche Felder können mit Querverweisen aus anderen Einträgen ergänzt werden.</string> <string name="education_entry_edit_summary">Bearbeiten Sie Ihren Eintrag mit persönlichen Feldern. Persönliche Felder können mit Querverweisen aus anderen Einträgen ergänzt werden.</string>
@@ -304,7 +302,7 @@
<string name="clipboard_warning">Wenn das automatische Löschen der Zwischenablage fehlschlägt, bitte den Verlauf manuell löschen.</string> <string name="clipboard_warning">Wenn das automatische Löschen der Zwischenablage fehlschlägt, bitte den Verlauf manuell löschen.</string>
<string name="allow_copy_password_warning">WARNUNG: Alle Apps teilen sich die Zwischenablage. Wenn sensible Daten kopiert werden, kann andere Software darauf zugreifen.</string> <string name="allow_copy_password_warning">WARNUNG: Alle Apps teilen sich die Zwischenablage. Wenn sensible Daten kopiert werden, kann andere Software darauf zugreifen.</string>
<string name="allow_no_password_title">Entsperren ohne Hauptschlüssel</string> <string name="allow_no_password_title">Entsperren ohne Hauptschlüssel</string>
<string name="allow_no_password_summary">„Öffnen“-Taste aktivieren, wenn keine Passwort-Identifikation festgelegt ist</string> <string name="allow_no_password_summary">Erlaubt das Antippen der „Öffnen“-Taste, wenn keine Anmeldeinformationen festgelegt sind</string>
<string name="enable_education_screens_title">Hilfe-Anzeige</string> <string name="enable_education_screens_title">Hilfe-Anzeige</string>
<string name="enable_education_screens_summary">Bedienelemente hervorheben, um die Funktionsweise der App zu lernen</string> <string name="enable_education_screens_summary">Bedienelemente hervorheben, um die Funktionsweise der App zu lernen</string>
<string name="menu_open_file_read_and_write">Änderbar</string> <string name="menu_open_file_read_and_write">Änderbar</string>
@@ -341,10 +339,10 @@
<string name="keyboard_key_vibrate_title">Vibrierende Tastendrücke</string> <string name="keyboard_key_vibrate_title">Vibrierende Tastendrücke</string>
<string name="keyboard_key_sound_title">Hörbare Tastendrücke</string> <string name="keyboard_key_sound_title">Hörbare Tastendrücke</string>
<string name="selection_mode">Auswahlmodus</string> <string name="selection_mode">Auswahlmodus</string>
<string name="remember_database_locations_title">Speicherort der Datenbanken</string> <string name="remember_database_locations_title">Datenbank-Speicherorte merken</string>
<string name="remember_database_locations_summary">Speicherort der Datenbanken merken</string> <string name="remember_database_locations_summary">Verfolgt, wo Datenbanken gespeichert sind</string>
<string name="remember_keyfile_locations_title">Speicherort der Schlüsseldateien</string> <string name="remember_keyfile_locations_title">Schlüsseldatei-Speicherorte merken</string>
<string name="remember_keyfile_locations_summary">Speicherort der Schlüssel zu Datenbanken merken</string> <string name="remember_keyfile_locations_summary">Verfolgt, wo Schlüsseldateien gespeichert sind</string>
<string name="show_recent_files_title">Zuletzt verwendete Dateien anzeigen</string> <string name="show_recent_files_title">Zuletzt verwendete Dateien anzeigen</string>
<string name="show_recent_files_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</string> <string name="show_recent_files_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</string>
<string name="hide_broken_locations_title">Defekte Datenbankverknüpfungen ausblenden</string> <string name="hide_broken_locations_title">Defekte Datenbankverknüpfungen ausblenden</string>
@@ -360,7 +358,7 @@
<string name="content_description_open_file">Datei öffnen</string> <string name="content_description_open_file">Datei öffnen</string>
<string name="content_description_add_entry">Eintrag hinzufügen</string> <string name="content_description_add_entry">Eintrag hinzufügen</string>
<string name="content_description_add_group">Gruppe hinzufügen</string> <string name="content_description_add_group">Gruppe hinzufügen</string>
<string name="content_description_file_information">Datei-Informationen</string> <string name="content_description_file_information">Datei-Info</string>
<string name="content_description_entry_icon">Symbol für den Eintrag</string> <string name="content_description_entry_icon">Symbol für den Eintrag</string>
<string name="entry_password_generator">Passwort-Generator</string> <string name="entry_password_generator">Passwort-Generator</string>
<string name="content_description_password_length">Passwortlänge</string> <string name="content_description_password_length">Passwortlänge</string>
@@ -370,7 +368,7 @@
<string name="list_groups_show_number_entries_title">Anzahl der Einträge anzeigen</string> <string name="list_groups_show_number_entries_title">Anzahl der Einträge anzeigen</string>
<string name="list_groups_show_number_entries_summary">Anzahl der Einträge in einer Gruppe anzeigen</string> <string name="list_groups_show_number_entries_summary">Anzahl der Einträge in einer Gruppe anzeigen</string>
<string name="content_description_add_node">Knoten hinzufügen</string> <string name="content_description_add_node">Knoten hinzufügen</string>
<string name="lock_database_back_root_title">\"Zurück\" drücken, um zu sperren</string> <string name="lock_database_back_root_title">Zurück drücken, um zu sperren</string>
<string name="clear_clipboard_notification_summary">Die Datenbank sperren, wenn die Dauer der Zwischenablage abläuft oder die Benachrichtigung geschlossen wird, nachdem Sie sie zu benutzen begonnen haben</string> <string name="clear_clipboard_notification_summary">Die Datenbank sperren, wenn die Dauer der Zwischenablage abläuft oder die Benachrichtigung geschlossen wird, nachdem Sie sie zu benutzen begonnen haben</string>
<string name="content_description_node_children">Untergeordneter Knotenpunkt</string> <string name="content_description_node_children">Untergeordneter Knotenpunkt</string>
<string name="content_description_keyfile_checkbox">Schlüsseldatei-Kontrollkästchen</string> <string name="content_description_keyfile_checkbox">Schlüsseldatei-Kontrollkästchen</string>
@@ -435,7 +433,7 @@
<string name="database_custom_color_title">Benutzerdefinierte Datenbankfarbe</string> <string name="database_custom_color_title">Benutzerdefinierte Datenbankfarbe</string>
<string name="compression">Kompression</string> <string name="compression">Kompression</string>
<string name="compression_none">Keine</string> <string name="compression_none">Keine</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Gerätetastatur-Einstellungen</string> <string name="device_keyboard_setting_title">Gerätetastatur-Einstellungen</string>
<string name="error_save_database">Die Datenbank konnte nicht gespeichert werden.</string> <string name="error_save_database">Die Datenbank konnte nicht gespeichert werden.</string>
<string name="menu_save_database">Datenbank speichern</string> <string name="menu_save_database">Datenbank speichern</string>
@@ -456,9 +454,9 @@
<string name="download_initialization">Initialisieren…</string> <string name="download_initialization">Initialisieren…</string>
<string name="download_progression">Fortschritt: %1$d%%</string> <string name="download_progression">Fortschritt: %1$d%%</string>
<string name="download_finalization">Fertigstellen…</string> <string name="download_finalization">Fertigstellen…</string>
<string name="download_complete">Vollständig! Tippen Sie, um die Datei zu öffnen.</string> <string name="download_complete">Vollständig!</string>
<string name="hide_expired_entries_title">Abgelaufene Einträge ausblenden</string> <string name="hide_expired_entries_title">Abgelaufene Einträge ausblenden</string>
<string name="hide_expired_entries_summary">Abgelaufene Einträge werden ausgeblendet</string> <string name="hide_expired_entries_summary">Abgelaufene Einträge werden nicht angezeigt</string>
<string name="style_choose_title">App-Design</string> <string name="style_choose_title">App-Design</string>
<string name="style_choose_summary">App-Design, das in der App genutzt wird</string> <string name="style_choose_summary">App-Design, das in der App genutzt wird</string>
<string-array name="list_style_names"> <string-array name="list_style_names">
@@ -472,27 +470,27 @@
</string-array> </string-array>
<string name="warning_database_read_only">Datei Schreibrechte gewähren, um Datenbankänderungen zu speichern</string> <string name="warning_database_read_only">Datei Schreibrechte gewähren, um Datenbankänderungen zu speichern</string>
<string name="education_setup_OTP_summary">Einrichten einer Einmal-Passwortverwaltung (HOTP / TOTP), um ein Token zu generieren, das für die Zwei-Faktor-Authentifizierung (2FA) angefordert wird.</string> <string name="education_setup_OTP_summary">Einrichten einer Einmal-Passwortverwaltung (HOTP / TOTP), um ein Token zu generieren, das für die Zwei-Faktor-Authentifizierung (2FA) angefordert wird.</string>
<string name="education_setup_OTP_title">Einrichten von OTP</string> <string name="education_setup_OTP_title">OTP einrichten</string>
<string name="error_create_database">Es ist nicht möglich, eine Datenbankdatei zu erstellen.</string> <string name="error_create_database">Es ist nicht möglich, eine Datenbankdatei zu erstellen.</string>
<string name="entry_add_attachment">Anhang hinzufügen</string> <string name="entry_add_attachment">Anhang hinzufügen</string>
<string name="discard">Verwerfen</string> <string name="discard">Verwerfen</string>
<string name="discard_changes">Änderungen verwerfen\?</string> <string name="discard_changes">Änderungen verwerfen\?</string>
<string name="validate">Validieren</string> <string name="validate">Validieren</string>
<string name="autofill_auto_search_summary">Suchergebnisse aus der Web-Domain oder Anwendungs-ID automatisch vorschlagen</string> <string name="autofill_auto_search_summary">Automatisch Suchergebnisse nach Web-Domain oder Anwendungs-ID vorschlagen</string>
<string name="autofill_auto_search_title">Automatische Suche</string> <string name="autofill_auto_search_title">Automatische Suche</string>
<string name="lock_database_show_button_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string> <string name="lock_database_show_button_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string>
<string name="lock_database_show_button_title">Sperrtaste anzeigen</string> <string name="lock_database_show_button_title">Sperrtaste anzeigen</string>
<string name="autofill_preference_title">Autofill-Einstellungen</string> <string name="autofill_preference_title">Einstellungen für automatisches Ausfüllen</string>
<string name="warning_database_link_revoked">Zugriff auf die Datei durch den Dateimanager widerrufen</string> <string name="warning_database_link_revoked">Zugriff auf die Datei durch den Dateimanager widerrufen</string>
<string name="error_label_exists">Diese Bezeichnung existiert bereits.</string> <string name="error_label_exists">Diese Bezeichnung existiert bereits.</string>
<string name="keyboard_search_share_summary">Automatische Suche nach gemeinsam genutzten Informationen zur Belegung der Tastatur</string> <string name="keyboard_search_share_summary">Automatische Suche nach gemeinsam genutzten Informationen zur Belegung der Tastatur</string>
<string name="keyboard_search_share_title">Gemeinsame Infos durchsuchen</string> <string name="keyboard_search_share_title">Gemeinsame Infos durchsuchen</string>
<string name="autofill_block_restart">Starten Sie die Anwendung, die das Formular enthält, neu, um die Sperrung zu aktivieren.</string> <string name="autofill_block_restart">Starten Sie die Anwendung, die das Formular enthält, neu, um die Sperrung zu aktivieren.</string>
<string name="autofill_block">Automatisches Füllen blockieren</string> <string name="autofill_block">Automatisches Ausfüllen sperren</string>
<string name="autofill_web_domain_blocklist_summary">Sperrliste, die das automatische Einsetzen von Web-Domains verhindert</string> <string name="autofill_web_domain_blocklist_summary">Liste der Domains, auf denen ein automatisches Ausfüllen unterbunden wird</string>
<string name="autofill_web_domain_blocklist_title">Web-Domain-Sperrliste</string> <string name="autofill_web_domain_blocklist_title">Liste gesperrter Web-Domains</string>
<string name="autofill_application_id_blocklist_summary">Sperrliste, die das automatische Füllen von Apps verhindert</string> <string name="autofill_application_id_blocklist_summary">Liste der Apps, in denen ein automatisches Ausfüllen unterbunden wird</string>
<string name="autofill_application_id_blocklist_title">Anwendungs-Sperrliste</string> <string name="autofill_application_id_blocklist_title">Liste gesperrter Anwendungen</string>
<string name="subdomain_search_summary">Suche Web-Domains mit Subdomain-Beschränkungen</string> <string name="subdomain_search_summary">Suche Web-Domains mit Subdomain-Beschränkungen</string>
<string name="subdomain_search_title">Subdomain-Suche</string> <string name="subdomain_search_title">Subdomain-Suche</string>
<string name="error_string_type">Dieser Text stimmt nicht mit dem angeforderten Element überein.</string> <string name="error_string_type">Dieser Text stimmt nicht mit dem angeforderten Element überein.</string>

View File

@@ -61,7 +61,7 @@
<string name="entry_user_name">Όνομα Χρήστη</string> <string name="entry_user_name">Όνομα Χρήστη</string>
<string name="error_arc4">Η ροή κρυπτογράφησης Arcfour δεν υποστηρίζεται.</string> <string name="error_arc4">Η ροή κρυπτογράφησης Arcfour δεν υποστηρίζεται.</string>
<string name="error_can_not_handle_uri">Το KeePassDX δε μπορεί να χειριστεί αυτή τη διεύθυνση URI.</string> <string name="error_can_not_handle_uri">Το KeePassDX δε μπορεί να χειριστεί αυτή τη διεύθυνση URI.</string>
<string name="error_file_not_create">Δεν ήταν δυνατή η δημιουργία αρχείου:</string> <string name="error_file_not_create">Δεν ήταν δυνατή η δημιουργία αρχείου</string>
<string name="error_invalid_db">Δεν ήταν δυνατή η ανάγνωση της βάσης δεδομένων.</string> <string name="error_invalid_db">Δεν ήταν δυνατή η ανάγνωση της βάσης δεδομένων.</string>
<string name="error_invalid_path">Βεβαιωθείτε ότι η διαδρομή είναι σωστή.</string> <string name="error_invalid_path">Βεβαιωθείτε ότι η διαδρομή είναι σωστή.</string>
<string name="error_no_name">Εισαγάγετε ένα όνομα.</string> <string name="error_no_name">Εισαγάγετε ένα όνομα.</string>
@@ -203,8 +203,6 @@
<string name="path">Διαδρομή</string> <string name="path">Διαδρομή</string>
<string name="assign_master_key">Ορίστε ένα κύριο κλειδί</string> <string name="assign_master_key">Ορίστε ένα κύριο κλειδί</string>
<string name="create_keepass_file">Δημιουργία νέας βάσης δεδομένων</string> <string name="create_keepass_file">Δημιουργία νέας βάσης δεδομένων</string>
<string name="full_file_path_enable_title">Διαδρομή αρχείου</string>
<string name="full_file_path_enable_summary">Προβολή ολόκληρης της διαδρομής αρχείου</string>
<string name="recycle_bin_title">Χρήση Κάδου ανακύκλωσης</string> <string name="recycle_bin_title">Χρήση Κάδου ανακύκλωσης</string>
<string name="recycle_bin_summary">Μετακίνηση ομάδων και καταχωρίσεων στην ομάδα \"Κάδο ανακύκλωσης\" πριν την διαγραφή</string> <string name="recycle_bin_summary">Μετακίνηση ομάδων και καταχωρίσεων στην ομάδα \"Κάδο ανακύκλωσης\" πριν την διαγραφή</string>
<string name="monospace_font_fields_enable_title">Γραμματοσειρά πεδίου</string> <string name="monospace_font_fields_enable_title">Γραμματοσειρά πεδίου</string>
@@ -418,7 +416,7 @@
<string name="database_custom_color_title">Προσαρμοσμένο χρώμα βάσης δεδομένων</string> <string name="database_custom_color_title">Προσαρμοσμένο χρώμα βάσης δεδομένων</string>
<string name="compression">Συμπίεση</string> <string name="compression">Συμπίεση</string>
<string name="compression_none">Καμιά</string> <string name="compression_none">Καμιά</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="magic_keyboard_explanation_summary">Ενεργοποιώντας ένα προσαρμοσμένο πληκτρολόγιο συγκεντρώνει τους κωδικούς πρόσβασής σας και όλα τα πεδία ταυτότητας</string> <string name="magic_keyboard_explanation_summary">Ενεργοποιώντας ένα προσαρμοσμένο πληκτρολόγιο συγκεντρώνει τους κωδικούς πρόσβασής σας και όλα τα πεδία ταυτότητας</string>
<string name="device_keyboard_setting_title">Ρυθμίσεις πληκτρολογίου συσκευής</string> <string name="device_keyboard_setting_title">Ρυθμίσεις πληκτρολογίου συσκευής</string>
<string name="education_biometric_title">Ξεκλείδωμα Βάσης Δεδομένων με βιομετρικά στοιχεία</string> <string name="education_biometric_title">Ξεκλείδωμα Βάσης Δεδομένων με βιομετρικά στοιχεία</string>
@@ -442,7 +440,7 @@
<string name="download_initialization">Αρχικοποίηση…</string> <string name="download_initialization">Αρχικοποίηση…</string>
<string name="download_progression">Σε εξέλιξη: %1$d%%</string> <string name="download_progression">Σε εξέλιξη: %1$d%%</string>
<string name="download_finalization">Ολοκλήρωση…</string> <string name="download_finalization">Ολοκλήρωση…</string>
<string name="download_complete">Ολοκληρώθηκε! Πατήστε για να ανοίξετε το αρχείο.</string> <string name="download_complete">Ολοκληρώθηκε!</string>
<string name="hide_expired_entries_title">Απόκρυψη καταχωρίσεων που έχουν λήξει</string> <string name="hide_expired_entries_title">Απόκρυψη καταχωρίσεων που έχουν λήξει</string>
<string name="hide_expired_entries_summary">Οι καταχωρίσεις που έχουν λήξει είναι κρυμμένες</string> <string name="hide_expired_entries_summary">Οι καταχωρίσεις που έχουν λήξει είναι κρυμμένες</string>
<string name="show_recent_files_title">Εμφάνιση πρόσφατων αρχείων</string> <string name="show_recent_files_title">Εμφάνιση πρόσφατων αρχείων</string>

View File

@@ -58,7 +58,7 @@
<string name="entry_user_name">Nombre de usuario</string> <string name="entry_user_name">Nombre de usuario</string>
<string name="error_arc4">No se admite el cifrador de flujo Arcfour.</string> <string name="error_arc4">No se admite el cifrador de flujo Arcfour.</string>
<string name="error_can_not_handle_uri">KeePassDX no puede manejar este URI.</string> <string name="error_can_not_handle_uri">KeePassDX no puede manejar este URI.</string>
<string name="error_file_not_create">No se pudo crear el archivo:</string> <string name="error_file_not_create">No se pudo crear el archivo</string>
<string name="error_invalid_db">No se pudo leer la base de datos.</string> <string name="error_invalid_db">No se pudo leer la base de datos.</string>
<string name="error_invalid_path">Asegúrese de que la ruta sea correcta.</string> <string name="error_invalid_path">Asegúrese de que la ruta sea correcta.</string>
<string name="error_no_name">Proporcione un nombre.</string> <string name="error_no_name">Proporcione un nombre.</string>
@@ -220,8 +220,6 @@
<string name="path">Ruta</string> <string name="path">Ruta</string>
<string name="assign_master_key">Asignar una clave maestra</string> <string name="assign_master_key">Asignar una clave maestra</string>
<string name="create_keepass_file">Crear base de datos nueva</string> <string name="create_keepass_file">Crear base de datos nueva</string>
<string name="full_file_path_enable_title">Ruta de archivo</string>
<string name="full_file_path_enable_summary">Ver la ruta completa del archivo</string>
<string name="recycle_bin_title">Usar la papelera de reciclaje</string> <string name="recycle_bin_title">Usar la papelera de reciclaje</string>
<string name="recycle_bin_summary">Mueva un grupo o una entrada a la papelera de reciclaje antes de eliminar</string> <string name="recycle_bin_summary">Mueva un grupo o una entrada a la papelera de reciclaje antes de eliminar</string>
<string name="monospace_font_fields_enable_title">Fuente de los campos</string> <string name="monospace_font_fields_enable_title">Fuente de los campos</string>
@@ -397,7 +395,7 @@
<string name="error_create_database">No fue posible crear el archivo de base de datos.</string> <string name="error_create_database">No fue posible crear el archivo de base de datos.</string>
<string name="html_about_contribution">Parar lograr &lt;strong&gt;mantener nuestra libertad&lt;/strong&gt;, &lt;strong&gt;corregir errores&lt;/strong&gt;, &lt;strong&gt;agregar características&lt;/strong&gt; y &lt;strong&gt;siempre estar activos&lt;/strong&gt;, contamos con tu &lt;strong&gt;contribución&lt;/strong&gt;.</string> <string name="html_about_contribution">Parar lograr &lt;strong&gt;mantener nuestra libertad&lt;/strong&gt;, &lt;strong&gt;corregir errores&lt;/strong&gt;, &lt;strong&gt;agregar características&lt;/strong&gt; y &lt;strong&gt;siempre estar activos&lt;/strong&gt;, contamos con tu &lt;strong&gt;contribución&lt;/strong&gt;.</string>
<string name="content_description_add_item">Añadir elemento</string> <string name="content_description_add_item">Añadir elemento</string>
<string name="download_complete">Descarga completa! Toca para abrir el archivo.</string> <string name="download_complete">Descarga completa!</string>
<string name="download_finalization">Finalizando…</string> <string name="download_finalization">Finalizando…</string>
<string name="download_progression">En progreso: %1$d%%</string> <string name="download_progression">En progreso: %1$d%%</string>
<string name="download_initialization">Inicializando…</string> <string name="download_initialization">Inicializando…</string>
@@ -407,7 +405,7 @@
<string name="autofill_block">Bloquear autocompletado</string> <string name="autofill_block">Bloquear autocompletado</string>
<string name="keyboard_change">Cambiar teclado</string> <string name="keyboard_change">Cambiar teclado</string>
<string name="keyboard_auto_go_action_summary">Acción de la tecla \"Ir\" al presionar una tecla \"Campo\"</string> <string name="keyboard_auto_go_action_summary">Acción de la tecla \"Ir\" al presionar una tecla \"Campo\"</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="compression_none">Ninguna</string> <string name="compression_none">Ninguna</string>
<string name="compression">Compresión</string> <string name="compression">Compresión</string>
<string name="database_default_username_title">Nombre de usuario predeterminado</string> <string name="database_default_username_title">Nombre de usuario predeterminado</string>

View File

@@ -61,7 +61,7 @@
<string name="entry_user_name">Erabiltzaile izena</string> <string name="entry_user_name">Erabiltzaile izena</string>
<string name="error_arc4">Arcfour stream zifratze sisterako ez dago euskarririk..</string> <string name="error_arc4">Arcfour stream zifratze sisterako ez dago euskarririk..</string>
<string name="error_can_not_handle_uri">KeePassDX-ek ezin dut uri hau kudeatu.</string> <string name="error_can_not_handle_uri">KeePassDX-ek ezin dut uri hau kudeatu.</string>
<string name="error_file_not_create">Ezin izan da fitxategia sortu:</string> <string name="error_file_not_create">Ezin izan da fitxategia sortu</string>
<string name="error_invalid_db">Datubase baliogabea.</string> <string name="error_invalid_db">Datubase baliogabea.</string>
<string name="error_invalid_path">Fitxategirako bide baliogabea.</string> <string name="error_invalid_path">Fitxategirako bide baliogabea.</string>
<string name="error_no_name">Izen bat behar da.</string> <string name="error_no_name">Izen bat behar da.</string>

View File

@@ -0,0 +1,261 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="menu_appearance_settings">ظاهر</string>
<string name="database_history">تاریخچه</string>
<string name="credential_before_click_biometric_button">رمز ورود را وارد کنید و سپس روی دکمه \"بیومتریک\" کلیک کنید.</string>
<string name="no_credentials_stored">این پایگاه داده هنوز اطلاعات کاربری ذخیره نشده است.</string>
<string name="biometric_scanning_error">خطای بیومتریک:٪ 1 $ s</string>
<string name="biometric_not_recognized">بایومتریک قابل تشخیص نیست</string>
<string name="biometric_invalid_key">"کلید بیومتریک را نمی توان خواند. لطفاً آن را حذف کرده و روش شناخت بیومتریک را تکرار کنید."</string>
<string name="encrypted_value_stored">رمز رمزگذاری شده ذخیره شده است</string>
<string name="biometric_prompt_extract_credential_message">استخراج اطلاعات کاربری پایگاه داده با داده های بیومتریک</string>
<string name="biometric_prompt_extract_credential_title">پایگاه داده را با تشخیص بیومتریک باز کنید</string>
<string name="biometric_prompt_store_credential_message">هشدار: اگر از تشخیص بیومتریک استفاده می کنید ، هنوز باید رمز عبور اصلی خود را به خاطر بسپارید.</string>
<string name="biometric_prompt_store_credential_title">تشخیص بیومتریک را ذخیره کنید</string>
<string name="open_biometric_prompt_store_credential">اعلان بیومتریک را برای ذخیره اعتبارنامه باز کنید</string>
<string name="open_biometric_prompt_unlock_database">برای باز کردن قفل پایگاه داده ، دستور بیومتریک را باز کنید</string>
<string name="keystore_not_accessible">فروشگاه اصلی به درستی تنظیم نشده است.</string>
<string name="configure_biometric">بیومتریک پشتیبانی می شود ، اما تنظیم نشده است.</string>
<string name="build_label">٪ 1 $ s را بسازید</string>
<string name="version_label">نسخه٪ 1 $ s</string>
<string name="warning_permanently_delete_nodes">گره های انتخاب شده برای همیشه حذف شوند؟</string>
<string name="warning_no_encryption_key">بدون کلید رمزگذاری ادامه می دهید؟</string>
<string name="warning_empty_password">بدون محافظت در مورد باز کردن قفل رمز عبور ادامه می دهید؟</string>
<string name="warning_database_link_revoked">دسترسی به پرونده توسط مدیر فایل لغو شده است</string>
<string name="warning_database_read_only">برای ذخیره تغییرات پایگاه داده ، اجازه دسترسی به نوشتن پرونده را بدهید</string>
<string name="warning_password_encoding">از نویسه های رمز عبور خارج از قالب رمزگذاری متن در پرونده پایگاه داده خودداری کنید (کاراکترهای شناخته نشده به همان حرف تبدیل می شوند).</string>
<string name="warning">هشدار</string>
<string name="uppercase">بزرگ</string>
<string name="unsupported_db_version">نسخه پایگاه داده پشتیبانی نمی شود.</string>
<string name="underline">زیر خط بزنید</string>
<string name="search_results">نتایج جستجو</string>
<string name="search">جستجو</string>
<string name="special">ویژه</string>
<string name="sort_last_access_time">دسترسی</string>
<string name="sort_last_modify_time">تغییر</string>
<string name="sort_creation_time">ایجاد</string>
<string name="sort_username">نام کاربری</string>
<string name="sort_title">عنوان</string>
<string name="sort_db">نظم طبیعی</string>
<string name="sort_recycle_bin_bottom">سطل بازیافت در پایین</string>
<string name="sort_groups_before">گروه های قبل</string>
<string name="sort_ascending">اول کمترین</string>
<string name="sort_menu">مرتب سازی</string>
<string name="search_label">جستجو</string>
<string name="space">فاصله</string>
<string name="do_not_kill_app">برنامه را نکشید</string>
<string name="command_execution">اجرای فرمان…</string>
<string name="saving_database">در حال ذخیره پایگاه داده</string>
<string name="parallelism_explanation">درجه موازی سازی (یعنی تعداد موضوعات) که توسط عملکرد مشتق کلیدی استفاده می شود.</string>
<string name="parallelism">موازی کاری</string>
<string name="memory_usage_explanation">مقدار حافظه (در بایت) که توسط تابع مشتق کلید مورد استفاده قرار گیرد.</string>
<string name="memory_usage">استفاده از حافظه</string>
<string name="rounds_explanation">دورهای رمزگذاری اضافی محافظت بالاتری در برابر حملات نیروی وحشی ایجاد می کنند ، اما در واقع می توانند سرعت و بارگذاری را کاهش دهند.</string>
<string name="rounds">دور تحول</string>
<string name="kdf_explanation">برای تولید کلید برای الگوریتم رمزگذاری ، کلید اصلی با استفاده از یک تابع مشتق کلید نمکی تصادفی تبدیل می شود.</string>
<string name="encryption_explanation">الگوریتم رمزنگاری پایگاه داده برای همه داده ها استفاده می شود.</string>
<string name="root">ریشه</string>
<string name="hide_broken_locations_summary">پنهان کردن لینک های شکسته در لیست پایگاه داده های اخیر</string>
<string name="hide_broken_locations_title">پیوندهای پایگاه داده خراب را پنهان کنید</string>
<string name="show_recent_files_summary">نمایش مکان های پایگاه داده های اخیر</string>
<string name="show_recent_files_title">نمایش پرونده های اخیر</string>
<string name="remember_keyfile_locations_summary">پیگیری محل ذخیره فایل های کلیدی را نگه می دارد</string>
<string name="remember_keyfile_locations_title">مکانهای پرونده اصلی را بخاطر بسپارید</string>
<string name="remember_database_locations_summary">پیگیری محل ذخیره پایگاه داده ها را نگه می دارد</string>
<string name="remember_database_locations_title">مکان های پایگاه داده ها را به یاد داشته باشید</string>
<string name="selection_mode">حالت انتخاب</string>
<string name="contains_duplicate_uuid_procedure">با ایجاد UUID جدید برای ادامه نسخه ها ، مشکل حل می شود؟</string>
<string name="contains_duplicate_uuid">پایگاه داده شامل UUIDs تکراری است.</string>
<string name="read_only_warning">بسته به مدیر فایل شما، KeePassDX ممکن است اجازه نوشتن در ذخیره سازی شما را ندارد.</string>
<string name="read_only">محافظت از نوشتن</string>
<string name="protection">حفاظت</string>
<string name="progress_title">کار کردن…</string>
<string name="progress_create">ایجاد پایگاه داده جدید…</string>
<string name="subdomain_search_summary">جستجوی دامنه های وب با محدودیت های زیر دامنه ها</string>
<string name="subdomain_search_title">جستجوی زیردریایی</string>
<string name="auto_focus_search_summary">درخواست جستجو در هنگام باز کردن یک پایگاه داده</string>
<string name="auto_focus_search_title">جستجوی سریع</string>
<string name="omit_backup_search_summary">Omits \"پشتیبان گیری\" و \"سطل بازیافت\" گروه از نتایج جستجو</string>
<string name="omit_backup_search_title">از طریق ورودی های پشتیبان جستجو نکنید</string>
<string name="create_keepass_file">ایجاد پایگاه داده جدید</string>
<string name="select_database_file">باز کردن پایگاه داده موجود</string>
<string name="no_url_handler">برای باز کردن این URL یک مرورگر وب نصب کنید.</string>
<string name="no_results">بدون نتایج جستجو</string>
<string name="never">هرگز</string>
<string name="minus">منهای</string>
<string name="menu_delete_entry_history">حذف تاریخچه</string>
<string name="menu_restore_entry_history">تاریخ را بازیابی کنید</string>
<string name="menu_empty_recycle_bin">سطل بازیافت را خالی کن</string>
<string name="menu_open_file_read_and_write">قابل تغییر</string>
<string name="menu_file_selection_read_only">نوشتن-محافظت شده</string>
<string name="menu_url">رفتن به آدرس اینترنتی</string>
<string name="menu_biometric_remove_key">حذف کلید بیومتریک ذخیره شده</string>
<string name="menu_showpass">نمایش رمز عبور</string>
<string name="menu_search">جستجو</string>
<string name="menu_open">باز</string>
<string name="menu_save_database">ذخیره پایگاه داده</string>
<string name="menu_lock">بانک اطلاعاتی قفل</string>
<string name="menu_hide_password">پنهان کردن رمز عبور</string>
<string name="menu_cancel">لغو</string>
<string name="menu_delete">حذف</string>
<string name="menu_paste">جاگذاری</string>
<string name="menu_move">حرکت</string>
<string name="menu_copy">کپی</string>
<string name="menu_edit">ویرایش</string>
<string name="menu_donate">اهدا کنید</string>
<string name="menu_master_key_settings">تنظیمات کلید اصلی</string>
<string name="menu_security_settings">تنظیمات امنیتی</string>
<string name="menu_database_settings">تنظیمات پایگاه داده</string>
<string name="menu_advanced_unlock_settings">باز کردن قفل پیشرفته</string>
<string name="menu_form_filling_settings">پر کردن فرم</string>
<string name="menu_app_settings">تنظیمات برنامه</string>
<string name="settings">تنظیمات</string>
<string name="copy_field">کپی %1$s</string>
<string name="menu_change_key_settings">کلید اصلی را تغییر دهید</string>
<string name="about">در باره</string>
<string name="hide_password_summary">رمزهای عبور ماسک (***) به طور پیش فرض</string>
<string name="hide_password_title">پنهان کردن رمزهای عبور</string>
<string name="lowercase">ترجمه</string>
<string name="loading_database">پایگاه داده بارگذاری…</string>
<string name="creating_database">ایجاد پایگاه داده…</string>
<string name="list_size_summary">اندازه متن در لیست عناصر</string>
<string name="list_size_title">اندازه موارد لیست</string>
<string name="list_groups_show_number_entries_summary">نمایش تعداد ورودی ها در یک گروه</string>
<string name="list_groups_show_number_entries_title">نمایش تعداد ورودی ها</string>
<string name="list_entries_show_username_summary">نمایش نام های کاربری در لیست های ورودی</string>
<string name="list_entries_show_username_title">نمایش نام های کاربری</string>
<string name="length">طول</string>
<string name="keyfile_is_empty">فایل کلید خالی است</string>
<string name="invalid_db_sig">نمی توانست فرمت پایگاه داده را تشخیص دهد.</string>
<string name="invalid_db_same_uuid">%1$s با همان UUID %2$s در حال حاضر وجود دارد.</string>
<string name="invalid_algorithm">الگوریتم اشتباه</string>
<string name="invalid_credentials">نمی توانست اعتبارنامه ها را بخواند.</string>
<string name="password">رمز عبور</string>
<string name="hint_pass">رمز عبور</string>
<string name="hint_length">طول</string>
<string name="hint_keyfile">Keyfile</string>
<string name="hint_group_name">نام گروه</string>
<string name="hint_generated_password">رمز عبور تولید شده</string>
<string name="hint_conf_pass">تایید رمز عبور</string>
<string name="generate_password">تولید رمز عبور</string>
<string name="file_browser">مدیر پرونده</string>
<string name="file_not_found_content">نمی توانست پرونده را پیدا کند. سعی کنید آن را از مرورگر فایل خود بازگشایی کنید.</string>
<string name="field_value">مقدار میدان</string>
<string name="field_name">نام زمینه</string>
<string name="error_string_type">این متن با مورد درخواست شده مطابقت ندارد.</string>
<string name="error_otp_digits">نشانه باید شامل %1$d تا %2$d رقم باشد.</string>
<string name="error_otp_period">دوره باید بین %1$d و %2$d ثانیه باشد.</string>
<string name="error_otp_counter">شمارنده باید بین %1$d و %2$d باشد.</string>
<string name="error_otp_secret_key">کلید راز باید در فرمت Base32 باشد.</string>
<string name="error_save_database">نمی توانست پایگاه داده را ذخیره کند.</string>
<string name="error_create_database_file">قادر به ایجاد پایگاه داده با این رمز عبور و keyfile نیست.</string>
<string name="error_create_database">قادر به ایجاد فایل پایگاه داده نیست.</string>
<string name="error_copy_group_here">شما نمی توانید یک گروه را در اینجا کپی کنید.</string>
<string name="error_copy_entry_here">شما نمی توانید یک ورودی را در اینجا کپی کنید.</string>
<string name="error_move_entry_here">شما نمی توانید یک ورودی را به اینجا منتقل کنید.</string>
<string name="error_move_folder_in_itself">شما نمی توانید یک گروه را به خود منتقل کنید.</string>
<string name="error_autofill_enable_service">قادر به فعال کردن سرویس پر کردن خودکار نبود.</string>
<string name="error_wrong_length">یک عدد کامل مثبت را در زمینه \"طول\" وارد کنید.</string>
<string name="error_label_exists">این برچسب در حال حاضر وجود دارد.</string>
<string name="error_string_key">هر رشته باید یک نام فیلد داشته باشد.</string>
<string name="error_rounds_too_large">\"دور تحول\" بیش از حد بالا است. تنظیم به 2147483648.</string>
<string name="error_pass_match">رمزهای عبور با هم مطابقت نمی کنند.</string>
<string name="error_disallow_no_credentials">حداقل یک اعتبار نامه باید تعیین شود.</string>
<string name="error_pass_gen_type">حداقل یک نوع تولید رمز عبور باید انتخاب شود</string>
<string name="error_load_database_KDF_memory">نميتونستم کليد رو بار کنم سعی کنید KDF \"استفاده از حافظه\" را پایین بیاورید.</string>
<string name="error_load_database">پایگاه داده شما را نمی توان بارگذاری کرد</string>
<string name="error_out_of_memory">هیچ حافظه ای برای بارگذاری کل پایگاه داده خود را.</string>
<string name="error_nokeyfile">یک فایل کلید را انتخاب کنید.</string>
<string name="error_no_name">نامی را وارد کنید.</string>
<string name="error_invalid_OTP">راز OTP نامعتبر.</string>
<string name="error_invalid_path">مطمئن شوید که مسیر درست است</string>
<string name="error_invalid_db">نمی توانست پایگاه داده را بخواند.</string>
<string name="error_file_not_create">نمی تواند پرونده ایجاد کند:</string>
<string name="error_can_not_handle_uri">نمی تواند این URI در KeePassDX رسیدگی کند.</string>
<string name="error_arc4">رمز جریان Arcfour پشتیبانی نمی شود</string>
<string name="entry_user_name">نام کاربری</string>
<string name="entry_url">آدرس</string>
<string name="entry_otp">otp</string>
<string name="otp_algorithm">الگوریتم</string>
<string name="otp_digits">رقم</string>
<string name="otp_counter">شمارنده</string>
<string name="otp_period">دوره (ثانیه)</string>
<string name="otp_secret">مخفی</string>
<string name="otp_type">نوع OTP</string>
<string name="entry_setup_otp">گذرواژه یکبار مصرف تنظیم کنید</string>
<string name="entry_title">عنوان</string>
<string name="entry_save">ذخیره</string>
<string name="entry_password">رمز عبور</string>
<string name="entry_not_found">نمی توانست داده های ورودی را پیدا کند.</string>
<string name="entry_modified">تغییر</string>
<string name="entry_keyfile">پرونده کلید</string>
<string name="entry_attachments">پیوست</string>
<string name="entry_history">تاریخچه</string>
<string name="entry_UUID">UUID</string>
<string name="entry_expires">منقضی</string>
<string name="entry_created">ایجاد</string>
<string name="entry_confpassword">تایید رمز عبور</string>
<string name="entry_notes">یادداشت</string>
<string name="entry_cancel">لغو</string>
<string name="entry_accessed">دیده</string>
<string name="html_about_contribution">به منظور &lt;strong&gt;نگاه آزادی ما&lt;/strong&gt; ، &lt;strong&gt;نقش های فیکسی&lt;/strong&gt; ، &lt;strong&gt;خدمات مضاعف&lt;/strong&gt; و &lt;strong&gt; همیشه فعال باشد&lt;/strong&gt; ، ما در &lt;strong&gt;مسابق&lt;/strong&gt; حساب می &lt;/strong&gt;.</string>
<string name="html_about_licence">KeePassDX © ٪1 $d Kunzisoft است &lt;strong&gt;منا منبع باز&lt;/strong&gt; و &lt;strong&gt;با تبلیغات&lt;/strong&gt;.
\nاین است که ارائه شده است، تحت &lt;strong&gt;GPLv3&lt;/strong&gt;، بدون هیچ گونه گارانتی.</string>
<string name="digits">رقم</string>
<string name="default_checkbox">استفاده به عنوان پایگاه داده پیش فرض</string>
<string name="decrypting_db">رمزگشایی محتوای پایگاه داده…</string>
<string name="database">پایگاه داده</string>
<string name="retrieving_db_key">بازیابی کلید پایگاه داده…</string>
<string name="select_to_copy">انتخاب برای کپی %1$s به کلیپ بورد</string>
<string name="content_description_keyboard_close_fields">زمینه های نزدیک</string>
<string name="content_description_remove_from_list">حذف</string>
<string name="content_description_update_from_list">روز رسانی</string>
<string name="content_description_remove_field">حذف فیلد</string>
<string name="entry_add_attachment">افزودن پیوست</string>
<string name="entry_add_field">اضافه کردن زمینه</string>
<string name="content_description_password_length">طول رمز عبور</string>
<string name="entry_password_generator">ژنراتور رمز عبور</string>
<string name="discard">دور انداختن</string>
<string name="discard_changes">تغییرات را دور بیندازید؟</string>
<string name="validate">اعتبار</string>
<string name="content_description_entry_icon">نماد ورود</string>
<string name="content_description_repeat_toggle_password_visibility">تکرار تغییر دید رمز عبور</string>
<string name="content_description_keyfile_checkbox">جعبه چک فایل کلید</string>
<string name="content_description_password_checkbox">جعبه چک رمز عبور</string>
<string name="content_description_file_information">اطلاعات فایل</string>
<string name="content_description_add_item">افزودن آیتم</string>
<string name="content_description_add_group">اضافه کردن گروه</string>
<string name="content_description_add_entry">افزودن ورودی</string>
<string name="content_description_add_node">اضافه کردن گره</string>
<string name="content_description_node_children">گره کودکان</string>
<string name="content_description_open_file">باز کردن فایل</string>
<string name="content_description_background">پس زمینه</string>
<string name="clipboard_timeout_summary">مدت زمان ذخیره سازی در کلیپ بورد (در صورت پشتیبانی توسط دستگاه شما)</string>
<string name="clipboard_timeout">تایم آوت کلیپ بورد</string>
<string name="clipboard_error_clear">نمی توانست کلیپ بورد را پاک کند</string>
<string name="clipboard_error">برخی از دستگاه ها اجازه نمی دهند برنامه ها از کلیپ بورد استفاده کنند.</string>
<string name="clipboard_error_title">خطای کلیپ بورد</string>
<string name="clipboard_cleared">کلیپ بورد پاک شد</string>
<string name="allow">اجازه</string>
<string name="file_manager_install_description">یک مدیر پرونده که عمل Intent را می پذیرد ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT برای ایجاد، باز کردن و ذخیره فایل های پایگاه داده مورد نیاز است.</string>
<string name="extended_ASCII">گسترش ASCII</string>
<string name="brackets">براکت</string>
<string name="application">برنامه</string>
<string name="app_timeout_summary">زمان بیکار قبل از قفل کردن پایگاه داده</string>
<string name="app_timeout">تایم آوت برنامه</string>
<string name="key_derivation_function">تابع مشتق کلید</string>
<string name="encryption_algorithm">الگوریتم رمزنگاری</string>
<string name="encryption">رمزگذاری</string>
<string name="security">امنیت</string>
<string name="master_key">کلید استاد</string>
<string name="add_group">اضافه کردن گروه</string>
<string name="edit_entry">ویرایش ورودی</string>
<string name="add_entry">افزودن ورودی</string>
<string name="accept">قبول</string>
<string name="about_description">پیاده سازی آندروید از مدیر رمز عبور KeePass</string>
<string name="homepage">صفحه اصلی</string>
<string name="feedback">بازخورد</string>
<string name="contribution">سهم</string>
<string name="contact">مخاطب</string>
<string name="filter">فیلتر</string>
</resources>

View File

@@ -61,7 +61,7 @@
<string name="entry_user_name">Käyttäjänimi</string> <string name="entry_user_name">Käyttäjänimi</string>
<string name="error_arc4">Arcfour stream cipher ei ole tuettu.</string> <string name="error_arc4">Arcfour stream cipher ei ole tuettu.</string>
<string name="error_can_not_handle_uri">KeePassDX ei osaa käsitellä tätä osoitetta.</string> <string name="error_can_not_handle_uri">KeePassDX ei osaa käsitellä tätä osoitetta.</string>
<string name="error_file_not_create">Tiedoston luonti epäonnistui:</string> <string name="error_file_not_create">Tiedoston luonti epäonnistui</string>
<string name="error_invalid_db">Tietokantaa ei pystytty lukemaan.</string> <string name="error_invalid_db">Tietokantaa ei pystytty lukemaan.</string>
<string name="error_invalid_path">Varmista että polku on oikein.</string> <string name="error_invalid_path">Varmista että polku on oikein.</string>
<string name="error_no_name">Anna nimi.</string> <string name="error_no_name">Anna nimi.</string>
@@ -245,8 +245,6 @@
<string name="monospace_font_fields_enable_title">Kenttäfontti</string> <string name="monospace_font_fields_enable_title">Kenttäfontti</string>
<string name="recycle_bin_summary">Siirrä ryhmät ja tietueet \"Roskakori\" ryhmään ennen poistamista</string> <string name="recycle_bin_summary">Siirrä ryhmät ja tietueet \"Roskakori\" ryhmään ennen poistamista</string>
<string name="recycle_bin_title">Roskakorin käyttö</string> <string name="recycle_bin_title">Roskakorin käyttö</string>
<string name="full_file_path_enable_summary">Katso koko tiedostopolku</string>
<string name="full_file_path_enable_title">Tiedostopolku</string>
<string name="assign_master_key">Aseta pääavain</string> <string name="assign_master_key">Aseta pääavain</string>
<string name="path">Polku</string> <string name="path">Polku</string>
<string name="file_name">Tiedostonimi</string> <string name="file_name">Tiedostonimi</string>

View File

@@ -65,7 +65,7 @@
<string name="entry_user_name">Nom dutilisateur</string> <string name="entry_user_name">Nom dutilisateur</string>
<string name="error_arc4">Le chiffrement de flux Arcfour nest pas pris en charge.</string> <string name="error_arc4">Le chiffrement de flux Arcfour nest pas pris en charge.</string>
<string name="error_can_not_handle_uri">Impossible de gérer cette URI dans KeePassDX.</string> <string name="error_can_not_handle_uri">Impossible de gérer cette URI dans KeePassDX.</string>
<string name="error_file_not_create">Impossible de créer le fichier :</string> <string name="error_file_not_create">Impossible de créer le fichier</string>
<string name="error_invalid_db">Impossible de lire la base de données.</string> <string name="error_invalid_db">Impossible de lire la base de données.</string>
<string name="error_invalid_path">Vérifier la validité du chemin daccès.</string> <string name="error_invalid_path">Vérifier la validité du chemin daccès.</string>
<string name="error_no_name">Saisir un nom.</string> <string name="error_no_name">Saisir un nom.</string>
@@ -201,8 +201,6 @@
<string name="path">Chemin daccès</string> <string name="path">Chemin daccès</string>
<string name="assign_master_key">Affecter une clé principale</string> <string name="assign_master_key">Affecter une clé principale</string>
<string name="create_keepass_file">Créer une nouvelle base de données</string> <string name="create_keepass_file">Créer une nouvelle base de données</string>
<string name="full_file_path_enable_title">Chemin daccès du fichier</string>
<string name="full_file_path_enable_summary">Affiche le chemin daccès complet du fichier</string>
<string name="recycle_bin_title">Utilisation de la corbeille</string> <string name="recycle_bin_title">Utilisation de la corbeille</string>
<string name="recycle_bin_summary">Déplace les groupes et les entrées dans le groupe «Corbeille» avant leur suppression</string> <string name="recycle_bin_summary">Déplace les groupes et les entrées dans le groupe «Corbeille» avant leur suppression</string>
<string name="monospace_font_fields_enable_title">Fonte de caractères des champs</string> <string name="monospace_font_fields_enable_title">Fonte de caractères des champs</string>
@@ -435,7 +433,7 @@
<string name="database_custom_color_title">Couleur de la base de données</string> <string name="database_custom_color_title">Couleur de la base de données</string>
<string name="compression">Compression</string> <string name="compression">Compression</string>
<string name="compression_none">Aucune</string> <string name="compression_none">Aucune</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Paramètres du clavier de lappareil</string> <string name="device_keyboard_setting_title">Paramètres du clavier de lappareil</string>
<string name="error_save_database">Impossible denregistrer la base de données.</string> <string name="error_save_database">Impossible denregistrer la base de données.</string>
<string name="menu_save_database">Enregistrer la base de données</string> <string name="menu_save_database">Enregistrer la base de données</string>
@@ -456,7 +454,7 @@
<string name="download_initialization">Initialisation…</string> <string name="download_initialization">Initialisation…</string>
<string name="download_progression">En cours : %1$d%%</string> <string name="download_progression">En cours : %1$d%%</string>
<string name="download_finalization">Finalisation…</string> <string name="download_finalization">Finalisation…</string>
<string name="download_complete">Terminé ! Appuyer pour ouvrir le fichier.</string> <string name="download_complete">Terminé !</string>
<string name="hide_expired_entries_title">Masquer les entrées expirées</string> <string name="hide_expired_entries_title">Masquer les entrées expirées</string>
<string name="hide_expired_entries_summary">Les entrées expirées sont cachées</string> <string name="hide_expired_entries_summary">Les entrées expirées sont cachées</string>
<string name="contact">Contact</string> <string name="contact">Contact</string>

View File

@@ -62,7 +62,7 @@
<string name="entry_url">यू.आर.एल</string> <string name="entry_url">यू.आर.एल</string>
<string name="entry_user_name">उपयोगकर्ता का नाम</string> <string name="entry_user_name">उपयोगकर्ता का नाम</string>
<string name="error_can_not_handle_uri">KeePassDX में इस URI को संभाल नहीं सका।</string> <string name="error_can_not_handle_uri">KeePassDX में इस URI को संभाल नहीं सका।</string>
<string name="error_file_not_create">फाइल नहीं बना सका:</string> <string name="error_file_not_create">फाइल नहीं बना सका</string>
<string name="error_invalid_db">डाटाबेस नहीं पढ़ सका।</string> <string name="error_invalid_db">डाटाबेस नहीं पढ़ सका।</string>
<string name="error_invalid_path">सुनिश्चित करें कि रास्ता सही है।</string> <string name="error_invalid_path">सुनिश्चित करें कि रास्ता सही है।</string>
<string name="error_no_name">एक नाम दर्ज करें।</string> <string name="error_no_name">एक नाम दर्ज करें।</string>

View File

@@ -205,8 +205,6 @@
<string name="file_name">Ime datoteke</string> <string name="file_name">Ime datoteke</string>
<string name="path">Putanja</string> <string name="path">Putanja</string>
<string name="assign_master_key">Zadaj glavni ključ</string> <string name="assign_master_key">Zadaj glavni ključ</string>
<string name="full_file_path_enable_title">Putanja datoteke</string>
<string name="full_file_path_enable_summary">Prikaži punu putanju do datoteke</string>
<string name="database_data_compression_title">Komprimiranje podataka</string> <string name="database_data_compression_title">Komprimiranje podataka</string>
<string name="database_data_compression_summary">Komprimiranje podataka smanjuje veličinu baze podataka.</string> <string name="database_data_compression_summary">Komprimiranje podataka smanjuje veličinu baze podataka.</string>
<string name="max_history_items_title">Maksimalni broj</string> <string name="max_history_items_title">Maksimalni broj</string>
@@ -233,12 +231,12 @@
<string name="other">Ostalo</string> <string name="other">Ostalo</string>
<string name="compression">Komprimiranje</string> <string name="compression">Komprimiranje</string>
<string name="compression_none">Bez</string> <string name="compression_none">Bez</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="recycle_bin">Koš za smeće</string> <string name="recycle_bin">Koš za smeće</string>
<string name="content_description_node_children">Pod-čvor</string> <string name="content_description_node_children">Pod-čvor</string>
<string name="entry_accessed">Pristupljeno</string> <string name="entry_accessed">Pristupljeno</string>
<string name="error_arc4">Arcfour šifriranje nije podržano.</string> <string name="error_arc4">Arcfour šifriranje nije podržano.</string>
<string name="error_file_not_create">Nije moguće stvoriti datoteku:</string> <string name="error_file_not_create">Nije moguće stvoriti datoteku</string>
<string name="error_invalid_db">Nije moguće čitati bazu podataka.</string> <string name="error_invalid_db">Nije moguće čitati bazu podataka.</string>
<string name="error_invalid_path">Provjeri putanju do datoteke.</string> <string name="error_invalid_path">Provjeri putanju do datoteke.</string>
<string name="error_invalid_OTP">Neispravan OTP tajni ključ.</string> <string name="error_invalid_OTP">Neispravan OTP tajni ključ.</string>
@@ -290,7 +288,7 @@
<string name="password_size_title">Duljina generirane lozinke</string> <string name="password_size_title">Duljina generirane lozinke</string>
<string name="password_size_summary">Postavlja standardnu duljinu generiranih lozinki</string> <string name="password_size_summary">Postavlja standardnu duljinu generiranih lozinki</string>
<string name="clipboard_explanation_summary">Kopiraj polja unosa koristeći međuspremnik tvog uređaja</string> <string name="clipboard_explanation_summary">Kopiraj polja unosa koristeći međuspremnik tvog uređaja</string>
<string name="clipboard_notifications_summary">Aktiviraj obavijesti međuspremnika za kopiranje polja prilikom prikaza unosa</string> <string name="clipboard_notifications_summary">Pokaži obavijesti međuspremnika za kopiranje polja prilikom prikaza unosa</string>
<string name="lock">Zaključavanje</string> <string name="lock">Zaključavanje</string>
<string name="lock_database_screen_off_title">Zaključavanje ekrana</string> <string name="lock_database_screen_off_title">Zaključavanje ekrana</string>
<string name="recycle_bin_title">Koristi koš za smeće</string> <string name="recycle_bin_title">Koristi koš za smeće</string>
@@ -323,7 +321,7 @@
<string name="keyboard_key_vibrate_title">Vibracija tipki</string> <string name="keyboard_key_vibrate_title">Vibracija tipki</string>
<string name="keyboard_key_sound_title">Zvuk tipki</string> <string name="keyboard_key_sound_title">Zvuk tipki</string>
<string name="allow_no_password_title">Dozvoli bez lozinke</string> <string name="allow_no_password_title">Dozvoli bez lozinke</string>
<string name="allow_no_password_summary">Aktiviraj gumb „Otvori”, ako nijedna akreditacija nije odabrana</string> <string name="allow_no_password_summary">Dozvoljava dodir gumba „Otvori”, ako nijedna akreditacija nije odabrana</string>
<string name="delete_entered_password_title">Izbriši lozinku</string> <string name="delete_entered_password_title">Izbriši lozinku</string>
<string name="delete_entered_password_summary">Briše upisanu lozinku nakon pokušaja povezivanja s bazom podataka</string> <string name="delete_entered_password_summary">Briše upisanu lozinku nakon pokušaja povezivanja s bazom podataka</string>
<string name="enable_read_only_title">Zaštićeno od pisanja</string> <string name="enable_read_only_title">Zaštićeno od pisanja</string>
@@ -339,7 +337,7 @@
<string name="education_create_database_summary">Stvori svoju prvu datoteku za upravljanje lozinkama.</string> <string name="education_create_database_summary">Stvori svoju prvu datoteku za upravljanje lozinkama.</string>
<string name="education_select_database_title">Otvori jednu postojeću bazu podataka</string> <string name="education_select_database_title">Otvori jednu postojeću bazu podataka</string>
<string name="education_select_database_summary">Za daljnju upotrebu prijašnje datoteke baze podataka, otvori je iz upravitelja datoteka.</string> <string name="education_select_database_summary">Za daljnju upotrebu prijašnje datoteke baze podataka, otvori je iz upravitelja datoteka.</string>
<string name="remember_database_locations_title">Spremi mjesto baze podataka</string> <string name="remember_database_locations_title">Zapamti mjesto baze podataka</string>
<string name="auto_focus_search_title">Brza pretraga</string> <string name="auto_focus_search_title">Brza pretraga</string>
<string name="error_create_database">Nije moguće stvoriti datoteku baze podataka.</string> <string name="error_create_database">Nije moguće stvoriti datoteku baze podataka.</string>
<string name="error_rounds_too_large">Previše „transformacijskih prolaza”. Postavlja se na 2147483648.</string> <string name="error_rounds_too_large">Previše „transformacijskih prolaza”. Postavlja se na 2147483648.</string>
@@ -350,8 +348,8 @@
<string name="discard_changes">Odbaciti promjene\?</string> <string name="discard_changes">Odbaciti promjene\?</string>
<string name="contact">Kontakt</string> <string name="contact">Kontakt</string>
<string name="homepage">Početna stranica</string> <string name="homepage">Početna stranica</string>
<string name="remember_keyfile_locations_title">Spremi mjesto datoteke ključa</string> <string name="remember_keyfile_locations_title">Zapamti mjesto datoteke ključa</string>
<string name="unavailable_feature_version">Tvoja Android verzija %1$s ne odgovara minimalno potrebnoj verziji %2$s.</string> <string name="unavailable_feature_version">Uređaj koristi Android verziju %1$s, ali potrebna je verzija %2$s ili novija.</string>
<string name="autofill_auto_search_summary">Automatski predloži rezultate pretrage od web domene ili ID-a aplikacije</string> <string name="autofill_auto_search_summary">Automatski predloži rezultate pretrage od web domene ili ID-a aplikacije</string>
<string name="hide_broken_locations_summary">Sakrij pokvarene poveznice u popisu nedavnih baza podataka</string> <string name="hide_broken_locations_summary">Sakrij pokvarene poveznice u popisu nedavnih baza podataka</string>
<string name="html_text_dev_feature">Ova se funkcija nalazi &lt;strong&gt;u razvoju&lt;/strong&gt; i treba tvoj &lt;strong&gt;doprinos&lt;/strong&gt; kako bi uskoro bila dostupna.</string> <string name="html_text_dev_feature">Ova se funkcija nalazi &lt;strong&gt;u razvoju&lt;/strong&gt; i treba tvoj &lt;strong&gt;doprinos&lt;/strong&gt; kako bi uskoro bila dostupna.</string>
@@ -377,7 +375,7 @@
<string name="keyboard_entry_timeout_summary">Istek vremena za brisanje unosa tipkovnicom</string> <string name="keyboard_entry_timeout_summary">Istek vremena za brisanje unosa tipkovnicom</string>
<string name="education_read_only_title">Zaštiti bazu podataka od pisanja</string> <string name="education_read_only_title">Zaštiti bazu podataka od pisanja</string>
<string name="autofill_web_domain_blocklist_title">Popis blokiranja web domena</string> <string name="autofill_web_domain_blocklist_title">Popis blokiranja web domena</string>
<string name="education_biometric_title">Otključaj bazu podataka pomoću biometrije</string> <string name="education_biometric_title">Biometrijsko otključavanje baze podataka</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="contribution">Doprinos</string> <string name="contribution">Doprinos</string>
<string name="open_biometric_prompt_store_credential">Za spremanje akreditacija, otvori biometrijsku prijavu</string> <string name="open_biometric_prompt_store_credential">Za spremanje akreditacija, otvori biometrijsku prijavu</string>
@@ -397,7 +395,7 @@
<string name="keyboard_entry_timeout_title">Istek vremena</string> <string name="keyboard_entry_timeout_title">Istek vremena</string>
<string name="auto_focus_search_summary">Pokreni pretragu prilikom otvaranja baze podataka</string> <string name="auto_focus_search_summary">Pokreni pretragu prilikom otvaranja baze podataka</string>
<string name="education_entry_edit_summary">Uredi svoj unos pomoću prilagođenih polja. Moguće je unakrsno pozivanje podataka između različitih polja unosa.</string> <string name="education_entry_edit_summary">Uredi svoj unos pomoću prilagođenih polja. Moguće je unakrsno pozivanje podataka između različitih polja unosa.</string>
<string name="remember_database_locations_summary">Zapamti mjesto baza podataka</string> <string name="remember_database_locations_summary">Pamti mjesto spremanja baza podataka</string>
<string name="education_field_copy_summary">Kopirana polja mogu se umetnuti bilo gdje. <string name="education_field_copy_summary">Kopirana polja mogu se umetnuti bilo gdje.
\n \n
\nKoristi preferirani način ispunjavanja obrazaca.</string> \nKoristi preferirani način ispunjavanja obrazaca.</string>
@@ -418,7 +416,7 @@
<string name="autofill_block">Blokiranje automatskog ispunjavanja</string> <string name="autofill_block">Blokiranje automatskog ispunjavanja</string>
<string name="keystore_not_accessible">Baza ključeva nije ispravno inicijalizirana.</string> <string name="keystore_not_accessible">Baza ključeva nije ispravno inicijalizirana.</string>
<string name="icon_pack_choose_summary">Paket ikona, koji se koristi u aplikaciji</string> <string name="icon_pack_choose_summary">Paket ikona, koji se koristi u aplikaciji</string>
<string name="hide_expired_entries_summary">Istekli unosi su skrivaju</string> <string name="hide_expired_entries_summary">Istekli unosi se ne pokazuju</string>
<string name="education_lock_title">Zaključaj bazu podataka</string> <string name="education_lock_title">Zaključaj bazu podataka</string>
<string name="open_biometric_prompt_unlock_database">Za otključavanje baze podataka, otvori biometrijsku prijavu</string> <string name="open_biometric_prompt_unlock_database">Za otključavanje baze podataka, otvori biometrijsku prijavu</string>
<string name="education_unlock_title">Otključaj bazu podataka</string> <string name="education_unlock_title">Otključaj bazu podataka</string>
@@ -454,12 +452,12 @@
\nSpremi sigurnosnu kopiju datoteke baze podataka na sigurno mjesto nakon svake promjene.</string> \nSpremi sigurnosnu kopiju datoteke baze podataka na sigurno mjesto nakon svake promjene.</string>
<string name="configure_biometric">Biometrijska prijava je podržana, ali nije postavljena.</string> <string name="configure_biometric">Biometrijska prijava je podržana, ali nije postavljena.</string>
<string name="subdomain_search_title">Pretraživanje poddomenom</string> <string name="subdomain_search_title">Pretraživanje poddomenom</string>
<string name="education_setup_OTP_summary">Postavi upravljanje jednokratnih lozinki (HOTP / TOTP) za generiranje tokena koji je potreban za dvofaktorsku autentifikaciju (2FA).</string> <string name="education_setup_OTP_summary">Postavi upravljanje jednokratnim lozinkama (HOTP / TOTP) za generiranje tokena koji je potreban za dvofaktorsku autentifikaciju (2FA).</string>
<string name="hide_expired_entries_title">Sakrij istekle unose</string> <string name="hide_expired_entries_title">Sakrij istekle unose</string>
<string name="download_finalization">Završavanje …</string> <string name="download_finalization">Završavanje …</string>
<string name="download">Preuzimanje</string> <string name="download">Preuzimanje</string>
<string name="lock_database_show_button_summary">Prikazuje gumb za zaključavanje u korisničkom sučelju</string> <string name="lock_database_show_button_summary">Prikazuje gumb za zaključavanje u korisničkom sučelju</string>
<string name="remember_keyfile_locations_summary">Zapamti mjesto datoteka ključeva baze podataka</string> <string name="remember_keyfile_locations_summary">Pamti mjesto spremanja datoteka ključeva</string>
<string name="html_text_ad_free">Za razliku od mnogih aplikacija za upravljanje lozinkama, ova je &lt;strong&gt;bez oglasa&lt;/strong&gt;, &lt;strong&gt;copylefted slobodan softver&lt;/strong&gt; i ne prikuplja osobne podatke na svojim poslužiteljima, bez obzira na korištenu verziju.</string> <string name="html_text_ad_free">Za razliku od mnogih aplikacija za upravljanje lozinkama, ova je &lt;strong&gt;bez oglasa&lt;/strong&gt;, &lt;strong&gt;copylefted slobodan softver&lt;/strong&gt; i ne prikuplja osobne podatke na svojim poslužiteljima, bez obzira na korištenu verziju.</string>
<string name="rounds">Transformacijski prolazi</string> <string name="rounds">Transformacijski prolazi</string>
<string name="download_initialization">Inicijaliziranje …</string> <string name="download_initialization">Inicijaliziranje …</string>
@@ -469,7 +467,7 @@
\n \n
\nGrupe (~mape) organiziraju unose u bazi podataka.</string> \nGrupe (~mape) organiziraju unose u bazi podataka.</string>
<string name="download_progression">U tijeku: %1$d%%</string> <string name="download_progression">U tijeku: %1$d%%</string>
<string name="download_complete">Gotovo! Dodirni, za otvaranje datoteke.</string> <string name="download_complete">Gotovo!</string>
<string name="keyboard_previous_fill_in_summary">Automatski se vrati na prethodnu tipkovnicu nakon izvršavanja automatske radnje tipke</string> <string name="keyboard_previous_fill_in_summary">Automatski se vrati na prethodnu tipkovnicu nakon izvršavanja automatske radnje tipke</string>
<string name="keyboard_previous_fill_in_title">Automatska radnja tipke</string> <string name="keyboard_previous_fill_in_title">Automatska radnja tipke</string>
<string name="keyboard_previous_database_credentials_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka</string> <string name="keyboard_previous_database_credentials_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka</string>

View File

@@ -60,7 +60,7 @@
<string name="entry_user_name">Felhasználónév</string> <string name="entry_user_name">Felhasználónév</string>
<string name="error_arc4">Az Arcfour adatfolyam-titkosítás nem támogatott.</string> <string name="error_arc4">Az Arcfour adatfolyam-titkosítás nem támogatott.</string>
<string name="error_can_not_handle_uri">Ez az URI nem kezelhető a KeePassDX-ben.</string> <string name="error_can_not_handle_uri">Ez az URI nem kezelhető a KeePassDX-ben.</string>
<string name="error_file_not_create">Nem sikerült létrehozni a fájlt:</string> <string name="error_file_not_create">Nem sikerült létrehozni a fájlt</string>
<string name="error_invalid_db">Az adatbázist nem lehet olvasni.</string> <string name="error_invalid_db">Az adatbázist nem lehet olvasni.</string>
<string name="error_invalid_path">Győződjön meg róla, hogy az útvonal helyes.</string> <string name="error_invalid_path">Győződjön meg róla, hogy az útvonal helyes.</string>
<string name="error_no_name">Adjon meg egy nevet.</string> <string name="error_no_name">Adjon meg egy nevet.</string>
@@ -235,8 +235,6 @@
<string name="file_name">Fájlnév</string> <string name="file_name">Fájlnév</string>
<string name="path">Útvonal</string> <string name="path">Útvonal</string>
<string name="assign_master_key">Mesterkulcs hozzárendelése</string> <string name="assign_master_key">Mesterkulcs hozzárendelése</string>
<string name="full_file_path_enable_title">Fájlútvonal</string>
<string name="full_file_path_enable_summary">A teljes fájlútvonal megtekintése</string>
<string name="recycle_bin_title">Kuka használata</string> <string name="recycle_bin_title">Kuka használata</string>
<string name="recycle_bin_summary">A csoportok és bejegyzések „Kukába” helyezése törlés előtt</string> <string name="recycle_bin_summary">A csoportok és bejegyzések „Kukába” helyezése törlés előtt</string>
<string name="monospace_font_fields_enable_title">Mező betűkészlete</string> <string name="monospace_font_fields_enable_title">Mező betűkészlete</string>
@@ -384,7 +382,7 @@
<string name="contact">Kapcsolat</string> <string name="contact">Kapcsolat</string>
<string name="hide_expired_entries_summary">A lejárt bejegyzések rejtettek</string> <string name="hide_expired_entries_summary">A lejárt bejegyzések rejtettek</string>
<string name="hide_expired_entries_title">Lejárt bejegyzések elrejtése</string> <string name="hide_expired_entries_title">Lejárt bejegyzések elrejtése</string>
<string name="download_complete">Kész! Koppintson a fájl megnyitásához.</string> <string name="download_complete">Kész!</string>
<string name="download_finalization">Befejezés…</string> <string name="download_finalization">Befejezés…</string>
<string name="download_progression">Folyamatban: %1$d%%</string> <string name="download_progression">Folyamatban: %1$d%%</string>
<string name="download_initialization">Előkészítés…</string> <string name="download_initialization">Előkészítés…</string>
@@ -405,7 +403,7 @@
<string name="menu_save_database">Adatbázis mentése</string> <string name="menu_save_database">Adatbázis mentése</string>
<string name="error_save_database">Az adatbázis mentése sikertelen.</string> <string name="error_save_database">Az adatbázis mentése sikertelen.</string>
<string name="device_keyboard_setting_title">Eszköz billentyűzetének beállításai</string> <string name="device_keyboard_setting_title">Eszköz billentyűzetének beállításai</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="compression_none">Nincs</string> <string name="compression_none">Nincs</string>
<string name="compression">Tömörítés</string> <string name="compression">Tömörítés</string>
<string name="database_custom_color_title">Egyéni adatbázisszín</string> <string name="database_custom_color_title">Egyéni adatbázisszín</string>

View File

@@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="no_results">Pencarian Tidak Ditemukan</string>
<string name="never">Tak Pernah</string>
<string name="minus">Minimal</string>
<string name="menu_delete_entry_history">Hapus Riwayat</string>
<string name="menu_restore_entry_history">Pulihkan Riwayat</string>
<string name="menu_empty_recycle_bin">Kosongkan Tempat Sampah</string>
<string name="menu_open_file_read_and_write">Bisa Diubah</string>
<string name="menu_file_selection_read_only">Lindungi Dari Perubahan</string>
<string name="menu_url">Membuka Tautan</string>
<string name="menu_biometric_remove_key">Hapus Kunci Sidik Jari Yang Tersimpan</string>
<string name="menu_showpass">Tampilkan Kata Sandi</string>
<string name="menu_search">Cari</string>
<string name="menu_open">Buka</string>
<string name="menu_save_database">Simpan Basisdata</string>
<string name="menu_lock">Basisdata Terkunci</string>
<string name="menu_hide_password">Sembunyikan Kata Sandi</string>
<string name="menu_cancel">Batal</string>
<string name="menu_delete">Hapus</string>
<string name="menu_paste">Tempel</string>
<string name="menu_move">Pindah</string>
<string name="menu_copy">Salin</string>
<string name="menu_edit">Ubah</string>
<string name="menu_donate">Donasi</string>
<string name="menu_master_key_settings">Pengaturan Kunci Utama</string>
<string name="menu_security_settings">Pengaturan Keamanan</string>
<string name="menu_app_settings">Pengaturan Aplikasi</string>
<string name="menu_database_settings">Pengaturan Basisdata</string>
<string name="menu_advanced_unlock_settings">Buka Kunci Lanjutan</string>
<string name="menu_form_filling_settings">Pengisian Formulir</string>
<string name="settings">Pengaturan</string>
<string name="copy_field">Salinan dari %1$s</string>
<string name="menu_change_key_settings">Ubah Kunci Utama</string>
<string name="about">Tentang</string>
<string name="hide_password_summary">Secara otomatis tutupi kata sandi (***)</string>
<string name="hide_password_title">Sembunyikan Kata Sandi</string>
<string name="lowercase">Huruf Kecil</string>
<string name="loading_database">Memuat basisdata…</string>
<string name="creating_database">Pembuatan basisdata…</string>
<string name="list_size_summary">Ukuran teks dalam daftar elemen</string>
<string name="list_size_title">Ukuran daftar item</string>
<string name="list_groups_show_number_entries_summary">Tampilkan jumlah entri dalam sebuah grup</string>
<string name="list_groups_show_number_entries_title">Tampilkan jumlah entri</string>
<string name="list_entries_show_username_summary">Tampilkan nama pengguna dalam daftar entri</string>
<string name="list_entries_show_username_title">Tampilkan nama pengguna</string>
<string name="length">Panjangnya</string>
<string name="keyfile_is_empty">File Kunci kosong.</string>
<string name="invalid_db_sig">Tidak bisa mengenali format basisdata.</string>
<string name="invalid_db_same_uuid">%1$s dengan UUID yang sama %2$s sudah ada.</string>
<string name="invalid_algorithm">Algoritma salah.</string>
<string name="invalid_credentials">Tidak bisa membaca kredensial.</string>
<string name="password">Kata Sandi</string>
<string name="hint_pass">Kata Sandi</string>
<string name="hint_length">Panjangnya</string>
<string name="hint_keyfile">File Kunci</string>
<string name="hint_group_name">Nama Grup</string>
<string name="hint_generated_password">Kata Sandi Telah Dibuat</string>
<string name="hint_conf_pass">Konfirmasi Kata Sandi</string>
<string name="generate_password">Buatkan Kata Sandi</string>
<string name="file_browser">Pengelola File</string>
<string name="file_not_found_content">Tidak bisa menemukan file. Cobalah buka kembali dari pengelola file anda.</string>
<string name="field_value">Nilai Bidang</string>
<string name="field_name">Nama Bidang</string>
<string name="error_string_type">Teks ini tidak sesuai dengan item yang diminta.</string>
<string name="error_otp_digits">Token harus berisi %1$d sampai %2$d dijit.</string>
<string name="error_otp_period">Periode harus antara %1$d dan %2$d detik.</string>
<string name="error_otp_counter">Penghitung harus antara %1$d dan %2$d.</string>
<string name="error_otp_secret_key">Kunci rahasia harus dalam format Base32.</string>
<string name="error_save_database">Tidak bisa menyimpan basisdata.</string>
<string name="error_create_database_file">Tidak bisa membuat basisdata dengan kata sandi dan file kunci ini.</string>
<string name="error_create_database">Tidak bisa membuat file basisdata.</string>
<string name="error_copy_group_here">Anda tidak bisa menyalin grup di sini.</string>
<string name="error_copy_entry_here">Anda tidak bisa menyalin entri di sini.</string>
<string name="error_move_entry_here">Anda tidak bisa memindahkan entri ke sini.</string>
<string name="error_move_folder_in_itself">Anda tidak bisa memindahkan grup ke grup itu sendiri.</string>
<string name="error_autofill_enable_service">Tidak bisa mengaktifkan layanan IsiOtomatis.</string>
<string name="error_wrong_length">Masukkan bilangan bulat positif di bidang \"Panjang\".</string>
<string name="error_label_exists">Label ini sudah ada.</string>
<string name="error_string_key">Setiap string harus memiliki bidang nama.</string>
<string name="error_rounds_too_large">\"Putaran Transformasi\" terlalu tinggi. Atur ke 2147483648.</string>
<string name="error_pass_match">Kata sandi tidak sesuai.</string>
<string name="error_disallow_no_credentials">Setidaknya ada satu kredensial yang harus ditetapkan.</string>
<string name="error_pass_gen_type">Setidaknya ada satu jenis pembuatan kata sandi yang harus dipilih.</string>
<string name="error_load_database_KDF_memory">Tidak bisa memuat kunci. Cobalah untuk mengurangi penggunaan memori (mematikan aplikasi lainnya).</string>
<string name="error_load_database">Tidak bisa memuat basisdata anda.</string>
<string name="error_out_of_memory">Tidak cukup memori untuk memuat seluruh basisdata anda.</string>
<string name="entry_keyfile">File Kunci</string>
<string name="error_nokeyfile">Pilih file kunci.</string>
<string name="error_no_name">Ketik sebuah nama.</string>
<string name="error_invalid_OTP">Rahasia OTP tidak valid.</string>
<string name="error_invalid_path">Pastikan lokasi filenya sudah benar.</string>
<string name="error_invalid_db">Tidak bisa membaca basisdata.</string>
<string name="error_file_not_create">Tidak bisa membuat file:</string>
<string name="entry_add_attachment">Tambahkan Lampiran</string>
<string name="digits">Dijit</string>
<string name="app_timeout_summary">Waktu idle sebelum mengunci basisdata</string>
<string name="file_manager_install_description">Ijin Pengolaan File untuk menerima perintah \"ACTION_CREATE_DOCUMENT\" dan \"ACTION_OPEN_DOCUMENT\" diperlukan untuk membuat, membuka, dan menyimpan file basisdata.</string>
<string name="otp_type">Jenis OTP</string>
<string name="entry_setup_otp">Penyiapan Sandi Sekali Pakai (OTP)</string>
<string name="error_can_not_handle_uri">Tidak bisa menangani URI ini di KeePassDX.</string>
<string name="error_arc4">Stream Cipher Arcfour tidak didukung.</string>
<string name="entry_user_name">Nama Pengguna</string>
<string name="entry_url">Tautan</string>
<string name="entry_otp">OTP (One Time Password)</string>
<string name="otp_algorithm">Algoritma</string>
<string name="otp_digits">Dijit</string>
<string name="otp_counter">Penghitung</string>
<string name="otp_period">Periode (detik)</string>
<string name="otp_secret">Rahasia</string>
<string name="entry_title">Judul</string>
<string name="entry_save">Simpan</string>
<string name="entry_password">Kata Sandi</string>
<string name="entry_not_found">Tidak bisa menemukan data entri.</string>
<string name="retrieving_db_key">Mengambil kunci basisdata…</string>
<string name="clipboard_error_clear">Tidak bisa membersihkan papan klip</string>
<string name="clipboard_timeout_summary">Durasi simpan pada papan klip (jika didukung oleh perangkat anda)</string>
<string name="content_description_repeat_toggle_password_visibility">Ulangi Peralihan Penampakan Kata Sandi</string>
<string name="clipboard_timeout">Batas Waktu Papan Klip</string>
<string name="clipboard_cleared">Papan Klip Dibersihkan</string>
<string name="entry_modified">Diubah</string>
<string name="entry_attachments">Lampiran</string>
<string name="entry_history">Riwayat</string>
<string name="entry_UUID">UUID (Identitas Unik Universal)</string>
<string name="entry_expires">Kedaluwarsa</string>
<string name="entry_created">Dibuat</string>
<string name="entry_confpassword">Konfirmasi Kata Sandi</string>
<string name="entry_notes">Catatan</string>
<string name="entry_cancel">Batalkan</string>
<string name="entry_accessed">Diakses</string>
<string name="html_about_contribution">Untuk &lt;strong&gt;menjaga kebebasan kami&lt;/strong&gt;, &lt;strong&gt;memperbaiki bug&lt;/strong&gt;, &lt;strong&gt; menambah fitur&lt;/strong&gt; dan &lt;strong&gt;agar selalu aktif&lt;/strong&gt;, kami mengandalkan &lt;strong&gt; kontribusi&lt;/strong&gt;.</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft &lt;strong&gt; open source &lt;/strong&gt; dan &lt;strong&gt; tanpa iklan &lt;/strong&gt;.&lt;br&gt;Tersedia apa adanya, di bawah lisensi &lt;strong&gt; GPLv3 &lt;/strong&gt;, tanpa jaminan apa pun.</string>
<string name="default_checkbox">Gunakan sebagai basisdata standar</string>
<string name="decrypting_db">Mendekripsi konten basisdata…</string>
<string name="database">BasisData</string>
<string name="select_to_copy">Pilih untuk menyalin %1$s ke papan klip</string>
<string name="content_description_keyboard_close_fields">Tutup Bidang</string>
<string name="content_description_remove_from_list">Buang</string>
<string name="content_description_update_from_list">Perbarui</string>
<string name="content_description_remove_field">Hapus Bidang</string>
<string name="entry_add_field">Tambahkan Bidang</string>
<string name="content_description_password_length">Panjang Kata Sandi</string>
<string name="entry_password_generator">Pembuat Kata Sandi</string>
<string name="discard">Batalkan</string>
<string name="discard_changes">Batalkan perubahan\?</string>
<string name="validate">Mengesahkan</string>
<string name="content_description_entry_icon">Ikon Entri</string>
<string name="content_description_keyfile_checkbox">Kotak Centang Berkas Kunci</string>
<string name="content_description_password_checkbox">Kotak Centang Kata Sandi</string>
<string name="content_description_file_information">Info File</string>
<string name="content_description_add_item">Tambahkan Item</string>
<string name="content_description_add_group">Tambahkan Grup</string>
<string name="content_description_add_entry">Tambahkan Entri</string>
<string name="content_description_add_node">Tambahkan Node</string>
<string name="content_description_node_children">Node Anak</string>
<string name="content_description_open_file">Buka Berkas</string>
<string name="content_description_background">Latar Belakang</string>
<string name="clipboard_error">Beberapa perangkat tidak mengizinkan aplikasi menggunakan papan klip.</string>
<string name="clipboard_error_title">Kesalahan Papan Klip</string>
<string name="allow">Ijinkan</string>
<string name="extended_ASCII">ASCII Diperpanjang</string>
<string name="brackets">Tanda Kurung</string>
<string name="application">Aplikasi</string>
<string name="app_timeout">Batas Waktu Aplikasi</string>
<string name="key_derivation_function">Fungsi Derivasi Kunci</string>
<string name="encryption_algorithm">Algoritma Enkripsi</string>
<string name="encryption">Enkripsi</string>
<string name="security">Keamanan</string>
<string name="master_key">Kunci Utama</string>
<string name="add_group">Tambahkan Grup</string>
<string name="edit_entry">Rubah Entri</string>
<string name="add_entry">Tambahkan Entri</string>
<string name="accept">Terima</string>
<string name="about_description">Implementasi Android dari pengelola kata sandi KeePass</string>
<string name="homepage">Beranda</string>
<string name="feedback">Umpan Balik</string>
<string name="contribution">Kontribusi</string>
<string name="contact">Kontak</string>
</resources>

View File

@@ -37,7 +37,7 @@
<string name="clipboard_error">Alcuni dispositivi non permettono alle app di usare gli appunti.</string> <string name="clipboard_error">Alcuni dispositivi non permettono alle app di usare gli appunti.</string>
<string name="clipboard_error_clear">Eliminazione degli appunti fallita</string> <string name="clipboard_error_clear">Eliminazione degli appunti fallita</string>
<string name="clipboard_timeout">Scadenza appunti</string> <string name="clipboard_timeout">Scadenza appunti</string>
<string name="clipboard_timeout_summary">Tempo prima di eliminare gli appunti</string> <string name="clipboard_timeout_summary">Tempo prima di eliminare gli appunti (se supportato dal dispositivo)</string>
<string name="select_to_copy">Copia %1$s negli appunti</string> <string name="select_to_copy">Copia %1$s negli appunti</string>
<string name="retrieving_db_key">Creazione file chiave database…</string> <string name="retrieving_db_key">Creazione file chiave database…</string>
<string name="database">Banca dati</string> <string name="database">Banca dati</string>
@@ -63,7 +63,7 @@
<string name="entry_user_name">Nome utente</string> <string name="entry_user_name">Nome utente</string>
<string name="error_arc4">La codifica a flusso Arcfour non è supportata.</string> <string name="error_arc4">La codifica a flusso Arcfour non è supportata.</string>
<string name="error_can_not_handle_uri">KeePassDX non può gestire questo URI.</string> <string name="error_can_not_handle_uri">KeePassDX non può gestire questo URI.</string>
<string name="error_file_not_create">Impossibile creare il file:</string> <string name="error_file_not_create">Impossibile creare il file</string>
<string name="error_invalid_db">Lettura del database fallita.</string> <string name="error_invalid_db">Lettura del database fallita.</string>
<string name="error_invalid_path">Assicurati che il percorso sia corretto.</string> <string name="error_invalid_path">Assicurati che il percorso sia corretto.</string>
<string name="error_no_name">Inserisci un nome.</string> <string name="error_no_name">Inserisci un nome.</string>
@@ -228,8 +228,6 @@
<string name="path">Percorso</string> <string name="path">Percorso</string>
<string name="assign_master_key">Assegna una chiave master</string> <string name="assign_master_key">Assegna una chiave master</string>
<string name="create_keepass_file">Crea un nuovo database</string> <string name="create_keepass_file">Crea un nuovo database</string>
<string name="full_file_path_enable_title">Percorso file</string>
<string name="full_file_path_enable_summary">Visualizza il percorso file completo</string>
<string name="recycle_bin_title">Uso del Cestino</string> <string name="recycle_bin_title">Uso del Cestino</string>
<string name="recycle_bin_summary">Sposta i gruppi e le voci nel gruppo \"Cestino\" prima di eliminarlo</string> <string name="recycle_bin_summary">Sposta i gruppi e le voci nel gruppo \"Cestino\" prima di eliminarlo</string>
<string name="monospace_font_fields_enable_title">Carattere campi</string> <string name="monospace_font_fields_enable_title">Carattere campi</string>
@@ -343,7 +341,7 @@
<string name="lock_database_back_root_title">Premere \'\'Indietro\'\' per bloccare</string> <string name="lock_database_back_root_title">Premere \'\'Indietro\'\' per bloccare</string>
<string name="lock_database_back_root_summary">Bloccare il database quando l\'utente preme il pulsante Indietro nella schermata principale</string> <string name="lock_database_back_root_summary">Bloccare il database quando l\'utente preme il pulsante Indietro nella schermata principale</string>
<string name="clear_clipboard_notification_title">Pulisci alla chiusura</string> <string name="clear_clipboard_notification_title">Pulisci alla chiusura</string>
<string name="clear_clipboard_notification_summary">Blocca il database alla chiusura della notifica</string> <string name="clear_clipboard_notification_summary">Blocca il database quando scade la durata degli appunti o la notifica viene chiusa dopo che inizi ad usarlo</string>
<string name="recycle_bin">Cestino</string> <string name="recycle_bin">Cestino</string>
<string name="keyboard_selection_entry_title">Selezione elemento</string> <string name="keyboard_selection_entry_title">Selezione elemento</string>
<string name="keyboard_selection_entry_summary">Mostra i campi di input nella Magitastiera durante la visualizzazione di un elemento</string> <string name="keyboard_selection_entry_summary">Mostra i campi di input nella Magitastiera durante la visualizzazione di un elemento</string>
@@ -448,7 +446,7 @@
<string name="hide_expired_entries_summary">I record scaduti sono nascosti</string> <string name="hide_expired_entries_summary">I record scaduti sono nascosti</string>
<string name="hide_expired_entries_title">Nascondi i record scaduti</string> <string name="hide_expired_entries_title">Nascondi i record scaduti</string>
<string name="education_setup_OTP_summary">Imposta la gestione delle OTP (HOTP / TOTP) per generare un token richiesto per la 2FA.</string> <string name="education_setup_OTP_summary">Imposta la gestione delle OTP (HOTP / TOTP) per generare un token richiesto per la 2FA.</string>
<string name="download_complete">Completo! Tocca per aprire il file.</string> <string name="download_complete">Completo!</string>
<string name="download_finalization">Finalizzazione…</string> <string name="download_finalization">Finalizzazione…</string>
<string name="download_progression">Avanzamento %1$d%%</string> <string name="download_progression">Avanzamento %1$d%%</string>
<string name="download_initialization">Inizializzazione…</string> <string name="download_initialization">Inizializzazione…</string>
@@ -456,12 +454,12 @@
<string name="education_setup_OTP_title">Imposta OTP</string> <string name="education_setup_OTP_title">Imposta OTP</string>
<string name="enable_auto_save_database_summary">Salva il database dopo ogni azione importante (in modalità \"Modificabile\")</string> <string name="enable_auto_save_database_summary">Salva il database dopo ogni azione importante (in modalità \"Modificabile\")</string>
<string name="enable_auto_save_database_title">Salvataggio automatico del database</string> <string name="enable_auto_save_database_title">Salvataggio automatico del database</string>
<string name="autofill_auto_search_summary">Suggerisci automaticamente risultati dal dominio web o l\'ID dell\'applicazione</string> <string name="autofill_auto_search_summary">Suggerisci automaticamente risultati dal dominio web o ID dell\'applicazione</string>
<string name="autofill_auto_search_title">Ricerca automatica</string> <string name="autofill_auto_search_title">Ricerca automatica</string>
<string name="keyboard_auto_go_action_summary">Dopo la pressione del tasto \"Campo\" invia il tasto \"Vai\"</string> <string name="keyboard_auto_go_action_summary">Dopo la pressione del tasto \"Campo\" invia il tasto \"Vai\"</string>
<string name="keyboard_auto_go_action_title">Azione auto key</string> <string name="keyboard_auto_go_action_title">Azione auto key</string>
<string name="device_keyboard_setting_title">Impostazioni tastiera dispositivo</string> <string name="device_keyboard_setting_title">Impostazioni tastiera dispositivo</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="compression_none">Nessuna</string> <string name="compression_none">Nessuna</string>
<string name="compression">Compressione</string> <string name="compression">Compressione</string>
<string name="database_custom_color_title">Colore del database customizzato</string> <string name="database_custom_color_title">Colore del database customizzato</string>
@@ -490,4 +488,9 @@
<string name="subdomain_search_summary">Cerca nei domini web includendo i sotto-domini</string> <string name="subdomain_search_summary">Cerca nei domini web includendo i sotto-domini</string>
<string name="subdomain_search_title">Ricerca per sotto-dominio</string> <string name="subdomain_search_title">Ricerca per sotto-dominio</string>
<string name="content_description_add_item">Aggiungi elemento</string> <string name="content_description_add_item">Aggiungi elemento</string>
<string name="keyboard_previous_fill_in_summary">Torna automaticamente alla tastiera precedente quando si esegue l\'azione del tasto automatico</string>
<string name="keyboard_previous_fill_in_title">Azione tasto automatico</string>
<string name="keyboard_previous_database_credentials_summary">Torna automaticamente alla tastiera precedente nella schermata delle credenziali del database</string>
<string name="keyboard_previous_database_credentials_title">Schermata credenziali database</string>
<string name="keyboard_change">Cambia tastiera</string>
</resources> </resources>

View File

@@ -60,7 +60,7 @@
<string name="entry_user_name">שם משתמש</string> <string name="entry_user_name">שם משתמש</string>
<string name="error_arc4">צופן זרם Arcfour אינו נתמך.</string> <string name="error_arc4">צופן זרם Arcfour אינו נתמך.</string>
<string name="error_can_not_handle_uri">KeePassDX לא יכול לטפל ב-URI הזה.</string> <string name="error_can_not_handle_uri">KeePassDX לא יכול לטפל ב-URI הזה.</string>
<string name="error_file_not_create">לא הצליח ליצור קובץ:</string> <string name="error_file_not_create">לא הצליח ליצור קובץ</string>
<string name="error_invalid_db">מסד נתונים לא חוקי.</string> <string name="error_invalid_db">מסד נתונים לא חוקי.</string>
<string name="error_invalid_path">נתיב לא חוקי.</string> <string name="error_invalid_path">נתיב לא חוקי.</string>
<string name="error_no_name">שם נדרש.</string> <string name="error_no_name">שם נדרש.</string>

View File

@@ -25,11 +25,11 @@
<string name="add_group">グループを追加</string> <string name="add_group">グループを追加</string>
<string name="encryption_algorithm">暗号化アルゴリズム</string> <string name="encryption_algorithm">暗号化アルゴリズム</string>
<string name="app_timeout">アプリのタイムアウト</string> <string name="app_timeout">アプリのタイムアウト</string>
<string name="app_timeout_summary">アプリがアイドル状態になってからデータベースをロックするまでの時間</string> <string name="app_timeout_summary">この期間アプリの操作がなかった場合、データベースをロックしま</string>
<string name="application">アプリ</string> <string name="application">アプリ</string>
<string name="menu_app_settings">アプリの設定</string> <string name="menu_app_settings">アプリの設定</string>
<string name="brackets">かっこ</string> <string name="brackets">かっこ</string>
<string name="file_manager_install_description">ACTION_CREATE_DOCUMENT と ACTION_OPEN_DOCUMENT のインテント アクションを受け入れるファイル マネージャーがデータベース ファイルの作成、オープン、保存に必要です。</string> <string name="file_manager_install_description">ACTION_CREATE_DOCUMENT と ACTION_OPEN_DOCUMENT のインテント アクションを受け入れるファイル マネージャーがデータベース ファイルの作成、オープン、保存に必要です。</string>
<string name="clipboard_cleared">クリップボードを消去しました</string> <string name="clipboard_cleared">クリップボードを消去しました</string>
<string name="clipboard_timeout">クリップボードのタイムアウト</string> <string name="clipboard_timeout">クリップボードのタイムアウト</string>
<string name="clipboard_timeout_summary">クリップボード内での保存期間(デバイスが対応している場合)</string> <string name="clipboard_timeout_summary">クリップボード内での保存期間(デバイスが対応している場合)</string>
@@ -39,7 +39,7 @@
<string name="decrypting_db">データベースの内容を復号しています…</string> <string name="decrypting_db">データベースの内容を復号しています…</string>
<string name="default_checkbox">デフォルトのデータベースとして使用</string> <string name="default_checkbox">デフォルトのデータベースとして使用</string>
<string name="digits">数字</string> <string name="digits">数字</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft は&lt;strong&gt;オープンソース&lt;/strong&gt;&lt;strong&gt;広告はありません&lt;/strong&gt; <string name="html_about_licence">KeePassDX © %1$d Kunzisoft は&lt;strong&gt;オープンソース&lt;/strong&gt;&lt;strong&gt;広告なし&lt;/strong&gt;です
\nそのままの状態で、&lt;strong&gt;GPLv3&lt;/strong&gt; ライセンスの下、いかなる保証もなく提供されます。</string> \nそのままの状態で、&lt;strong&gt;GPLv3&lt;/strong&gt; ライセンスの下、いかなる保証もなく提供されます。</string>
<string name="select_database_file">既存のデータベースを開く</string> <string name="select_database_file">既存のデータベースを開く</string>
<string name="entry_accessed">アクセス日時</string> <string name="entry_accessed">アクセス日時</string>
@@ -57,7 +57,7 @@
<string name="entry_user_name">ユーザー名</string> <string name="entry_user_name">ユーザー名</string>
<string name="error_arc4">Arcfour ストリーム暗号には対応していません。</string> <string name="error_arc4">Arcfour ストリーム暗号には対応していません。</string>
<string name="error_can_not_handle_uri">KeePassDX ではこの URI を処理できませんでした。</string> <string name="error_can_not_handle_uri">KeePassDX ではこの URI を処理できませんでした。</string>
<string name="error_file_not_create">ファイルを作成できませんでした</string> <string name="error_file_not_create">ファイルを作成できませんでした</string>
<string name="error_invalid_db">データベースを読み取れませんでした。</string> <string name="error_invalid_db">データベースを読み取れませんでした。</string>
<string name="error_invalid_path">パスが正しいことを確認してください。</string> <string name="error_invalid_path">パスが正しいことを確認してください。</string>
<string name="error_no_name">名前を入力してください。</string> <string name="error_no_name">名前を入力してください。</string>
@@ -65,7 +65,7 @@
<string name="error_out_of_memory">データベース全体を読み込むメモリがありません。</string> <string name="error_out_of_memory">データベース全体を読み込むメモリがありません。</string>
<string name="error_pass_gen_type">少なくとも 1 つのパスワード生成タイプを選択する必要があります。</string> <string name="error_pass_gen_type">少なくとも 1 つのパスワード生成タイプを選択する必要があります。</string>
<string name="error_pass_match">パスワードが一致しません。</string> <string name="error_pass_match">パスワードが一致しません。</string>
<string name="error_rounds_too_large">「変換ラウンド」が多ぎます。2147483648 に設定します。</string> <string name="error_rounds_too_large">「変換ラウンド」が多ぎます。2147483648 に設定します。</string>
<string name="error_wrong_length">「長さ」フィールドには正の整数を入力してください。</string> <string name="error_wrong_length">「長さ」フィールドには正の整数を入力してください。</string>
<string name="file_browser">ファイル マネージャー</string> <string name="file_browser">ファイル マネージャー</string>
<string name="generate_password">パスワードを生成</string> <string name="generate_password">パスワードを生成</string>
@@ -118,13 +118,13 @@
<string name="special">特殊文字</string> <string name="special">特殊文字</string>
<string name="search">検索</string> <string name="search">検索</string>
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="underline">下線</string> <string name="underline">アンダースコア</string>
<string name="unsupported_db_version">対応していないバージョンのデータベースです。</string> <string name="unsupported_db_version">対応していないバージョンのデータベースです。</string>
<string name="uppercase">大文字</string> <string name="uppercase">大文字</string>
<string name="version_label">バージョン %1$s</string> <string name="version_label">バージョン %1$s</string>
<string name="education_unlock_summary">データベースのロックを解除するには、パスワードまたはキーファイル、またはその両方を入力します。 <string name="education_unlock_summary">データベースのロックを解除するには、パスワードまたはキーファイル、またはその両方を入力します。
\n \n
\nデータベース ファイルは変更するたびに安全な場所バックアップしてください。</string> \nデータベース ファイルは変更するたびに安全な場所バックアップしてください。</string>
<string-array name="timeout_options"> <string-array name="timeout_options">
<item>5秒</item> <item>5秒</item>
<item>10秒</item> <item>10秒</item>
@@ -168,7 +168,7 @@
<string name="menu_move">移動</string> <string name="menu_move">移動</string>
<string name="menu_paste">貼り付け</string> <string name="menu_paste">貼り付け</string>
<string name="menu_cancel">キャンセル</string> <string name="menu_cancel">キャンセル</string>
<string name="menu_biometric_remove_key">保存済み生体認証キーを削除</string> <string name="menu_biometric_remove_key">保存済み生体を削除</string>
<string name="menu_file_selection_read_only">書き込み禁止</string> <string name="menu_file_selection_read_only">書き込み禁止</string>
<string name="menu_open_file_read_and_write">変更可能</string> <string name="menu_open_file_read_and_write">変更可能</string>
<string name="create_keepass_file">新しいデータベースを作成</string> <string name="create_keepass_file">新しいデータベースを作成</string>
@@ -190,7 +190,7 @@
<string name="content_description_add_group">グループを追加</string> <string name="content_description_add_group">グループを追加</string>
<string name="content_description_file_information">ファイル情報</string> <string name="content_description_file_information">ファイル情報</string>
<string name="content_description_entry_icon">エントリーのアイコン</string> <string name="content_description_entry_icon">エントリーのアイコン</string>
<string name="entry_password_generator">パスワード ジェネレータ</string> <string name="entry_password_generator">パスワード生成機能</string>
<string name="content_description_password_length">パスワードの長さ</string> <string name="content_description_password_length">パスワードの長さ</string>
<string name="entry_add_field">フィールドを追加</string> <string name="entry_add_field">フィールドを追加</string>
<string name="content_description_remove_field">フィールドを削除</string> <string name="content_description_remove_field">フィールドを削除</string>
@@ -199,12 +199,12 @@
<string name="configure_biometric">生体認証プロンプト対応端末ですが未設定です。</string> <string name="configure_biometric">生体認証プロンプト対応端末ですが未設定です。</string>
<string name="master_key">マスターキー</string> <string name="master_key">マスターキー</string>
<string name="entry_history">履歴</string> <string name="entry_history">履歴</string>
<string name="otp_type">OTP の型式</string> <string name="otp_type">OTP の種類</string>
<string name="otp_period">周期(秒)</string> <string name="otp_period">周期(秒)</string>
<string name="otp_algorithm">アルゴリズム</string> <string name="otp_algorithm">アルゴリズム</string>
<string name="entry_otp">OTP</string> <string name="entry_otp">OTP</string>
<string name="error_copy_group_here">ここではグループをコピーすることはできません。</string> <string name="error_copy_group_here">ここではグループをコピーすることはできません。</string>
<string name="error_otp_secret_key">秘密鍵は Base32 形式内でなければなりません。</string> <string name="error_otp_secret_key">シークレット キーは Base32 形式内でなければなりません。</string>
<string name="error_otp_period">周期は %1$d 秒から %2$d 秒の間でなければなりません。</string> <string name="error_otp_period">周期は %1$d 秒から %2$d 秒の間でなければなりません。</string>
<string name="creating_database">データベースを作成しています…</string> <string name="creating_database">データベースを作成しています…</string>
<string name="menu_security_settings">セキュリティの設定</string> <string name="menu_security_settings">セキュリティの設定</string>
@@ -225,7 +225,7 @@
<string name="error_create_database">データベース ファイルを作成できません。</string> <string name="error_create_database">データベース ファイルを作成できません。</string>
<string name="error_label_exists">このラベルはすでに存在します。</string> <string name="error_label_exists">このラベルはすでに存在します。</string>
<string name="error_disallow_no_credentials">少なくとも 1 つの認証情報を設定する必要があります。</string> <string name="error_disallow_no_credentials">少なくとも 1 つの認証情報を設定する必要があります。</string>
<string name="error_invalid_OTP">OTP 秘密鍵が無効です。</string> <string name="error_invalid_OTP">無効な OTP シークレットです。</string>
<string name="otp_digits"></string> <string name="otp_digits"></string>
<string name="entry_setup_otp">ワンタイム パスワードを設定</string> <string name="entry_setup_otp">ワンタイム パスワードを設定</string>
<string name="entry_attachments">添付ファイル</string> <string name="entry_attachments">添付ファイル</string>
@@ -244,7 +244,7 @@
<string name="open_biometric_prompt_store_credential">生体認証プロンプトを開き認証情報を保存します</string> <string name="open_biometric_prompt_store_credential">生体認証プロンプトを開き認証情報を保存します</string>
<string name="open_biometric_prompt_unlock_database">生体認証プロンプトを開きロックを解除します</string> <string name="open_biometric_prompt_unlock_database">生体認証プロンプトを開きロックを解除します</string>
<string name="keystore_not_accessible">キーストアが正しく初期化されていません。</string> <string name="keystore_not_accessible">キーストアが正しく初期化されていません。</string>
<string name="warning_no_encryption_key">暗号化キーなしで続行しますか?</string> <string name="warning_no_encryption_key">暗号なしで続行しますか?</string>
<string name="warning_empty_password">パスワードによるロック解除の保護なしで続行しますか?</string> <string name="warning_empty_password">パスワードによるロック解除の保護なしで続行しますか?</string>
<string name="warning_database_read_only">データベースの変更を保存するために、ファイル書き込みアクセスを許可します</string> <string name="warning_database_read_only">データベースの変更を保存するために、ファイル書き込みアクセスを許可します</string>
<string name="search_results">検索結果</string> <string name="search_results">検索結果</string>
@@ -259,10 +259,10 @@
<string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string> <string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string>
<string name="hide_broken_locations_title">データベースへの壊れたリンクを非表示にする</string> <string name="hide_broken_locations_title">データベースへの壊れたリンクを非表示にする</string>
<string name="show_recent_files_summary">最近使ったデータベースの場所を表示します</string> <string name="show_recent_files_summary">最近使ったデータベースの場所を表示します</string>
<string name="remember_keyfile_locations_summary">データベースのキーファイルの場所を記憶します</string> <string name="remember_keyfile_locations_summary">キーファイルの保存先を追跡します</string>
<string name="remember_keyfile_locations_title">キーファイルの場所を保存</string> <string name="remember_keyfile_locations_title">キーファイルの場所を記憶</string>
<string name="remember_database_locations_summary">データベースの場所を記憶します</string> <string name="remember_database_locations_summary">データベースの保存先を追跡します</string>
<string name="remember_database_locations_title">データベースの場所を保存</string> <string name="remember_database_locations_title">データベースの場所を記憶</string>
<string name="selection_mode">選択モード</string> <string name="selection_mode">選択モード</string>
<string name="contains_duplicate_uuid_procedure">重複したエントリーに対する新しい UUID を生成して、問題を解決し続行しますか?</string> <string name="contains_duplicate_uuid_procedure">重複したエントリーに対する新しい UUID を生成して、問題を解決し続行しますか?</string>
<string name="contains_duplicate_uuid">データベースには重複する UUID が含まれています。</string> <string name="contains_duplicate_uuid">データベースには重複する UUID が含まれています。</string>
@@ -272,7 +272,7 @@
<string name="error_string_type">このテキストは指定された項目と整合しません。</string> <string name="error_string_type">このテキストは指定された項目と整合しません。</string>
<string name="error_otp_counter">カウンターは %1$d から %2$d の間でなければなりません。</string> <string name="error_otp_counter">カウンターは %1$d から %2$d の間でなければなりません。</string>
<string name="otp_counter">カウンター</string> <string name="otp_counter">カウンター</string>
<string name="otp_secret">秘密鍵</string> <string name="otp_secret">シークレット</string>
<string name="content_description_keyboard_close_fields">フィールドを閉じる</string> <string name="content_description_keyboard_close_fields">フィールドを閉じる</string>
<string name="content_description_update_from_list">更新</string> <string name="content_description_update_from_list">更新</string>
<string name="entry_add_attachment">添付ファイルを追加</string> <string name="entry_add_attachment">添付ファイルを追加</string>
@@ -287,21 +287,19 @@
<string name="education_generate_password_summary">エントリーに関連付ける強力なパスワードを生成します。フォームの基準に従って定義することは簡単で、安全なパスワードを忘れることはありません。</string> <string name="education_generate_password_summary">エントリーに関連付ける強力なパスワードを生成します。フォームの基準に従って定義することは簡単で、安全なパスワードを忘れることはありません。</string>
<string name="monospace_font_fields_enable_summary">フィールド内で使用するフォントを変更して、文字を見やすくします</string> <string name="monospace_font_fields_enable_summary">フィールド内で使用するフォントを変更して、文字を見やすくします</string>
<string name="monospace_font_fields_enable_title">フィールド フォント</string> <string name="monospace_font_fields_enable_title">フィールド フォント</string>
<string name="full_file_path_enable_summary">ファイルのフルパスを表示します</string>
<string name="full_file_path_enable_title">ファイルパス</string>
<string name="path">パス</string> <string name="path">パス</string>
<string name="education_generate_password_title">強力なパスワードを作成</string> <string name="education_generate_password_title">強力なパスワードを作成</string>
<string name="biometric_auto_open_prompt_summary">データベースが生体認証を使用するように設定されている場合、生体情報の取得を自動的に求めます</string> <string name="biometric_auto_open_prompt_summary">データベースが生体認証を使用するように設定されている場合、生体情報の取得を自動的に求めます</string>
<string name="biometric_delete_all_key_title">暗号化キーを削除</string> <string name="biometric_delete_all_key_title">暗号を削除</string>
<string name="biometric_auto_open_prompt_title">生体認証プロンプトを自動で開く</string> <string name="biometric_auto_open_prompt_title">生体認証プロンプトを自動で開く</string>
<string name="biometric_delete_all_key_warning">生体認証に関するすべての暗号化キーを削除しますか?</string> <string name="biometric_delete_all_key_warning">生体認証に関するすべての暗号を削除しますか?</string>
<string name="biometric_delete_all_key_summary">生体認証に関するすべての暗号化キーを削除します</string> <string name="biometric_delete_all_key_summary">生体認証に関するすべての暗号を削除します</string>
<string name="advanced_unlock_explanation_summary">高度なロック解除を使用すると、データベースがより簡単に開きます</string> <string name="advanced_unlock_explanation_summary">高度なロック解除を使用すると、データベースがより簡単に開きます</string>
<string name="education_lock_summary">データベースをすばやくロックします。時間が経ったり画面がオフになったときロックするようアプリを設定することもできます。</string> <string name="education_lock_summary">データベースをすばやくロックします。時間が経ったり画面がオフになったときロックするようアプリを設定することもできます。</string>
<string name="education_lock_title">データベースをロック</string> <string name="education_lock_title">データベースをロック</string>
<string name="education_sort_summary">エントリーとグループの並べ替え方法を選択します。</string> <string name="education_sort_summary">エントリーとグループの並べ替え方法を選択します。</string>
<string name="education_search_summary">パスワードを取得するには、タイトル、ユーザー名、または他のフィールドの内容を入力します。</string> <string name="education_search_summary">パスワードを取得するには、タイトル、ユーザー名、または他のフィールドの内容を入力します。</string>
<string name="education_new_node_summary">エントリーはデジタル アイデンティティの管理に役立ちます。 <string name="education_new_node_summary">エントリーはデジタル ID の管理に役立ちます。
\n \n
\nグループ≒フォルダはデータベース内のエントリーを整理します。</string> \nグループ≒フォルダはデータベース内のエントリーを整理します。</string>
<string name="education_search_title">エントリーを検索</string> <string name="education_search_title">エントリーを検索</string>
@@ -313,14 +311,14 @@
<string name="education_select_database_title">既存のデータペースを開く</string> <string name="education_select_database_title">既存のデータペースを開く</string>
<string name="list_password_generator_options_title">パスワードの文字種</string> <string name="list_password_generator_options_title">パスワードの文字種</string>
<string name="build_label">Build %1$s</string> <string name="build_label">Build %1$s</string>
<string name="warning_password_encoding">データベース ファイル内のテキスト エンコーディング形式以外の文字を、パスワードに用いることは避けてください(認識されない文字は同じ文字に変換されます)。</string> <string name="warning_password_encoding">データベース ファイル内のテキスト エンコーディング形式以外の文字を、パスワードに使うことは避けてください(認識されない文字は同じ文字に変換されます)。</string>
<string name="sort_groups_before">グループを先に並べる</string> <string name="sort_groups_before">グループを先に並べる</string>
<string name="enable_auto_save_database_title">データベースを自動保存</string> <string name="enable_auto_save_database_title">データベースを自動保存</string>
<string name="enable_read_only_summary">デフォルトではデータベースを読み取り専用で開きます</string> <string name="enable_read_only_summary">デフォルトではデータベースを読み取り専用で開きます</string>
<string name="enable_read_only_title">書き込み禁止</string> <string name="enable_read_only_title">書き込み禁止</string>
<string name="delete_entered_password_summary">入力されたパスワードをデータベースへの接続試行後に削除します</string> <string name="delete_entered_password_summary">入力されたパスワードをデータベースへの接続試行後に削除します</string>
<string name="delete_entered_password_title">パスワードを削除</string> <string name="delete_entered_password_title">パスワードを削除</string>
<string name="allow_no_password_summary">認証情報が選択されていない場合でも「開く」ボタンを有効にします</string> <string name="allow_no_password_summary">認証情報が選択されていない場合でも「開く」ボタンのタップを許可します</string>
<string name="autofill_block_restart">ブロッキングを有効にするには、そのフォームを含むアプリを再起動します。</string> <string name="autofill_block_restart">ブロッキングを有効にするには、そのフォームを含むアプリを再起動します。</string>
<string name="autofill_block">自動入力をブロック</string> <string name="autofill_block">自動入力をブロック</string>
<string name="autofill_web_domain_blocklist_title">ウェブドメインのブロックリスト</string> <string name="autofill_web_domain_blocklist_title">ウェブドメインのブロックリスト</string>
@@ -343,7 +341,7 @@
<string name="lock_database_screen_off_title">画面ロック</string> <string name="lock_database_screen_off_title">画面ロック</string>
<string name="lock">ロック</string> <string name="lock">ロック</string>
<string name="clipboard_warning">クリップボードの自動削除に失敗した場合は、手動でその履歴を削除してください。</string> <string name="clipboard_warning">クリップボードの自動削除に失敗した場合は、手動でその履歴を削除してください。</string>
<string name="clipboard_notifications_summary">エントリーを表示しているとき、フィールドをコピーするクリップボード通知を有効にします</string> <string name="clipboard_notifications_summary">エントリーを開いているとき、フィールドをコピーするクリップボード通知を表示します</string>
<string name="clipboard_notifications_title">クリップボード通知</string> <string name="clipboard_notifications_title">クリップボード通知</string>
<string name="clipboard">クリップボード</string> <string name="clipboard">クリップボード</string>
<string name="database_opened">データベースが開かれています</string> <string name="database_opened">データベースが開かれています</string>
@@ -363,9 +361,9 @@
<string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string> <string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string>
<string name="biometric_scanning_error">生体認証エラー:%1$s</string> <string name="biometric_scanning_error">生体認証エラー:%1$s</string>
<string name="biometric_not_recognized">生体情報を認識できませんでした</string> <string name="biometric_not_recognized">生体情報を認識できませんでした</string>
<string name="biometric_invalid_key">生体認証キーが読み取れません。削除して生体認証の手順を繰り返してください。</string> <string name="biometric_invalid_key">生体が読み取れません。削除して生体認証の手順を繰り返してください。</string>
<string name="encrypted_value_stored">保存された暗号化済みパスワード</string> <string name="encrypted_value_stored">保存された暗号化済みパスワード</string>
<string name="biometric_prompt_extract_credential_message">生体情報を用いてデータベースの認証情報を取り出します</string> <string name="biometric_prompt_extract_credential_message">生体情報を使ってデータベースの認証情報を取り出します</string>
<string name="html_text_feature_generosity">この&lt;strong&gt;ビジュアル スタイル&lt;/strong&gt;はあなたの厚意により利用可能となります。</string> <string name="html_text_feature_generosity">この&lt;strong&gt;ビジュアル スタイル&lt;/strong&gt;はあなたの厚意により利用可能となります。</string>
<string name="subdomain_search_summary">サブドメインの制約つきでウェブドメインを検索します</string> <string name="subdomain_search_summary">サブドメインの制約つきでウェブドメインを検索します</string>
<string name="lock_database_back_root_summary">ユーザーがルート画面上で戻るボタンをタップしたとき、データベースをロックします</string> <string name="lock_database_back_root_summary">ユーザーがルート画面上で戻るボタンをタップしたとき、データベースをロックします</string>
@@ -380,7 +378,7 @@
<string name="education_biometric_summary">スキャンした生体情報にパスワードをリンクして、データベースのロックをすばやく解除します。</string> <string name="education_biometric_summary">スキャンした生体情報にパスワードをリンクして、データベースのロックをすばやく解除します。</string>
<string name="reset_education_screens_text">教育的なヒントをリセットしました</string> <string name="reset_education_screens_text">教育的なヒントをリセットしました</string>
<string name="enable_education_screens_title">教育的なヒント</string> <string name="enable_education_screens_title">教育的なヒント</string>
<string name="keyboard_previous_database_credentials_summary">データベース認証画面で、切り替え前のキーボードへ自動的に戻します</string> <string name="keyboard_previous_database_credentials_summary">データベース認証情報の画面で、切り替え前のキーボードへ自動的に戻します</string>
<string name="autofill_auto_search_summary">ウェブドメインまたはアプリケーション ID から検索結果を自動的に提案します</string> <string name="autofill_auto_search_summary">ウェブドメインまたはアプリケーション ID から検索結果を自動的に提案します</string>
<string name="keyboard_previous_fill_in_summary">自動キーアクションの実行後、切り替え前のキーボードへ自動的に戻します</string> <string name="keyboard_previous_fill_in_summary">自動キーアクションの実行後、切り替え前のキーボードへ自動的に戻します</string>
<string name="keyboard_previous_fill_in_title">自動キーアクション</string> <string name="keyboard_previous_fill_in_title">自動キーアクション</string>
@@ -396,7 +394,7 @@
<string name="device_keyboard_setting_title">デバイス キーボードの設定</string> <string name="device_keyboard_setting_title">デバイス キーボードの設定</string>
<string name="settings_database_force_changing_master_key_next_time_summary">次回マスターキーの変更を必須にします1 回のみ)</string> <string name="settings_database_force_changing_master_key_next_time_summary">次回マスターキーの変更を必須にします1 回のみ)</string>
<string name="recycle_bin_summary">グループとエントリーを削除する前に「ごみ箱」グループに移動します</string> <string name="recycle_bin_summary">グループとエントリーを削除する前に「ごみ箱」グループに移動します</string>
<string name="keyboard_selection_entry_summary">エントリーを表示しているとき、Magikeyboard に入力フィールドを表示します</string> <string name="keyboard_selection_entry_summary">エントリーを開いているとき、Magikeyboard に入力フィールドを表示します</string>
<string name="lock_database_show_button_title">ロックボタンを表示</string> <string name="lock_database_show_button_title">ロックボタンを表示</string>
<string name="download_finalization">終了しています…</string> <string name="download_finalization">終了しています…</string>
<string name="download_initialization">初期化しています…</string> <string name="download_initialization">初期化しています…</string>
@@ -407,7 +405,7 @@
<string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string> <string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string>
<string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string> <string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string>
<string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string> <string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string>
<string name="download_complete">完了しました!タップするとファイルが開きます。</string> <string name="download_complete">完了しました!</string>
<string name="download_progression">進行中:%1$d%%</string> <string name="download_progression">進行中:%1$d%%</string>
<string name="download_attachment">%1$s をダウンロード</string> <string name="download_attachment">%1$s をダウンロード</string>
<string name="contribute">貢献</string> <string name="contribute">貢献</string>
@@ -422,7 +420,7 @@
<string name="allow_no_password_title">空のマスターキーを許可</string> <string name="allow_no_password_title">空のマスターキーを許可</string>
<string name="autofill_auto_search_title">自動検索</string> <string name="autofill_auto_search_title">自動検索</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string> <string name="keyboard_label">Magikeyboard (KeePassDX)</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="compression_none">なし</string> <string name="compression_none">なし</string>
<string name="compression">圧縮</string> <string name="compression">圧縮</string>
<string name="other">その他</string> <string name="other">その他</string>
@@ -436,13 +434,13 @@
<string name="enable">有効にする</string> <string name="enable">有効にする</string>
<string name="allow_copy_password_warning">警告:クリップボードはすべてのアプリで共有されます。機密データがコピーされた場合、他のソフトウェアがデータを復元する可能性があります。</string> <string name="allow_copy_password_warning">警告:クリップボードはすべてのアプリで共有されます。機密データがコピーされた場合、他のソフトウェアがデータを復元する可能性があります。</string>
<string name="allow_copy_password_summary">エントリーのパスワードと保護されたフィールドを、クリップボードにコピーすることを許可します</string> <string name="allow_copy_password_summary">エントリーのパスワードと保護されたフィールドを、クリップボードにコピーすることを許可します</string>
<string name="education_donation_summary">安定性やセキュリティを向上させ、機能を追加することに協力します。</string> <string name="education_donation_summary">安定性やセキュリティを向上させ、機能を追加することを支援します。</string>
<string name="education_donation_title">参加</string> <string name="education_donation_title">参加</string>
<string name="education_field_copy_title">フィールドをコピー</string> <string name="education_field_copy_title">フィールドをコピー</string>
<string name="education_read_only_title">データベースの書き込みを禁止</string> <string name="education_read_only_title">データベースの書き込みを禁止</string>
<string name="education_entry_new_field_summary">追加フィールドを登録し、値を追加し、必要に応じて保護します。</string> <string name="education_entry_new_field_summary">追加フィールドを登録し、値を追加し、必要に応じて保護します。</string>
<string name="education_entry_new_field_title">カスタム フィールドを追加</string> <string name="education_entry_new_field_title">カスタム フィールドを追加</string>
<string name="education_entry_edit_summary">カスタム フィールドを用いてエントリーを編集します。共有データは異なるエントリーのフィールド間で参照することができます。</string> <string name="education_entry_edit_summary">カスタム フィールドを使ってエントリーを編集します。共有データは異なるエントリーのフィールド間で参照することができます。</string>
<string name="education_entry_edit_title">エントリーを編集</string> <string name="education_entry_edit_title">エントリーを編集</string>
<string name="education_biometric_title">生体認証によるロック解除</string> <string name="education_biometric_title">生体認証によるロック解除</string>
<string name="education_create_database_summary">最初のパスワード管理ファイルを作成します。</string> <string name="education_create_database_summary">最初のパスワード管理ファイルを作成します。</string>
@@ -456,8 +454,8 @@
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_Argon2">Argon2</string> <string name="kdf_Argon2">Argon2</string>
<string name="kdf_AES">AES</string> <string name="kdf_AES">AES</string>
<string name="list_password_generator_options_summary">パスワード ジェネレータが用いる文字種を設定します</string> <string name="list_password_generator_options_summary">パスワードの生成に使う文字種を設定します</string>
<string name="keyboard_previous_database_credentials_title">データベース認証画面</string> <string name="keyboard_previous_database_credentials_title">データベース認証情報の画面</string>
<string name="keyboard_change">キーボードの切り替え</string> <string name="keyboard_change">キーボードの切り替え</string>
<string name="keyboard_keys_category">キー</string> <string name="keyboard_keys_category">キー</string>
<string name="keyboard_theme_title">キーボードのテーマ</string> <string name="keyboard_theme_title">キーボードのテーマ</string>
@@ -479,7 +477,7 @@
<string name="database_data_compression_title">データ圧縮</string> <string name="database_data_compression_title">データ圧縮</string>
<string name="file_name">ファイル名</string> <string name="file_name">ファイル名</string>
<string name="unavailable_feature_hardware">対応するハードウェアが見つかりませんでした。</string> <string name="unavailable_feature_hardware">対応するハードウェアが見つかりませんでした。</string>
<string name="unavailable_feature_version">Android のバージョン %1$s が、必要な最小バージョン %2$s を満たしていません</string> <string name="unavailable_feature_version">デバイスは Android %1$s を実行していますが、%2$s 以降が必要です</string>
<string name="unavailable_feature_text">この機能を起動できませんでした。</string> <string name="unavailable_feature_text">この機能を起動できませんでした。</string>
<string name="biometric_unlock_enable_summary">生体情報をスキャンしてデータベースを開くことができるようにします</string> <string name="biometric_unlock_enable_summary">生体情報をスキャンしてデータベースを開くことができるようにします</string>
<string name="biometric_unlock_enable_title">生体認証によるロック解除</string> <string name="biometric_unlock_enable_title">生体認証によるロック解除</string>

View File

@@ -64,7 +64,7 @@
<string name="entry_user_name">아이디</string> <string name="entry_user_name">아이디</string>
<string name="error_arc4">Arcfour 스트림 암호는 지원되지 않습니다.</string> <string name="error_arc4">Arcfour 스트림 암호는 지원되지 않습니다.</string>
<string name="error_can_not_handle_uri">KeePassDX에서는 이 URI를 처리할 수 없습니다.</string> <string name="error_can_not_handle_uri">KeePassDX에서는 이 URI를 처리할 수 없습니다.</string>
<string name="error_file_not_create">파일을 생성할 수 없음:</string> <string name="error_file_not_create">파일을 생성할 수 없음</string>
<string name="error_invalid_db">데이터베이스를 읽을 수 없음.</string> <string name="error_invalid_db">데이터베이스를 읽을 수 없음.</string>
<string name="error_invalid_path">경로가 확실한지 확인하십시오.</string> <string name="error_invalid_path">경로가 확실한지 확인하십시오.</string>
<string name="error_no_name">이름을 입력하십시오.</string> <string name="error_no_name">이름을 입력하십시오.</string>

View File

@@ -58,7 +58,7 @@
<string name="entry_user_name">Lietotāja vārds</string> <string name="entry_user_name">Lietotāja vārds</string>
<string name="error_arc4">Arcfour plūsmas šifrs netiek atbalstīts.</string> <string name="error_arc4">Arcfour plūsmas šifrs netiek atbalstīts.</string>
<string name="error_can_not_handle_uri">Neizdevās pātiet uz norādīto adresi.</string> <string name="error_can_not_handle_uri">Neizdevās pātiet uz norādīto adresi.</string>
<string name="error_file_not_create">Neizdevās izveidot failu:</string> <string name="error_file_not_create">Neizdevās izveidot failu</string>
<string name="error_invalid_db">Nederīga datu bāze.</string> <string name="error_invalid_db">Nederīga datu bāze.</string>
<string name="error_invalid_path">Nederīgs ceļš.</string> <string name="error_invalid_path">Nederīgs ceļš.</string>
<string name="error_no_name">Vajag ievadīt faila nosaukumu</string> <string name="error_no_name">Vajag ievadīt faila nosaukumu</string>

View File

@@ -76,7 +76,7 @@
<string name="error_save_database">ഡാറ്റാബേസ് സംരക്ഷിക്കാൻ കഴിഞ്ഞില്ല.</string> <string name="error_save_database">ഡാറ്റാബേസ് സംരക്ഷിക്കാൻ കഴിഞ്ഞില്ല.</string>
<string name="error_pass_match">പാസ്‌വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല.</string> <string name="error_pass_match">പാസ്‌വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല.</string>
<string name="error_no_name">ഒരു പേര് നൽകുക.</string> <string name="error_no_name">ഒരു പേര് നൽകുക.</string>
<string name="error_file_not_create">ഫയൽ സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല:</string> <string name="error_file_not_create">ഫയൽ സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല</string>
<string name="error_invalid_db">ഡാറ്റാബേസ് വായിക്കാൻ സാധിച്ചില്ല.</string> <string name="error_invalid_db">ഡാറ്റാബേസ് വായിക്കാൻ സാധിച്ചില്ല.</string>
<string name="entry_user_name">ഉപയോക്തൃനാമം</string> <string name="entry_user_name">ഉപയോക്തൃനാമം</string>
<string name="entry_url">URL</string> <string name="entry_url">URL</string>
@@ -154,12 +154,12 @@
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="encryption_twofish">Twofish</string> <string name="encryption_twofish">Twofish</string>
<string name="encryption_rijndael">Rijndael (AES)</string> <string name="encryption_rijndael">Rijndael (AES)</string>
<string name="download_complete">പൂർത്തിയാക്കി! ഫയൽ തുറക്കാൻ സ്പർശിക്കുക</string> <string name="download_complete">പൂർത്തിയാക്കി!</string>
<string name="education_create_database_title">നിങ്ങളുടെ ഡാറ്റാബേസ് ഫയൽ സൃഷ്ടിക്കുക</string> <string name="education_create_database_title">നിങ്ങളുടെ ഡാറ്റാബേസ് ഫയൽ സൃഷ്ടിക്കുക</string>
<string name="autofill_auto_search_title">സ്വയം തിരയൽ</string> <string name="autofill_auto_search_title">സ്വയം തിരയൽ</string>
<string name="keyboard_change">കീബോർഡ് മാറ്റുക</string> <string name="keyboard_change">കീബോർഡ് മാറ്റുക</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string> <string name="keyboard_label">Magikeyboard (KeePassDX)</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="database_version_title">"ഡാറ്റാബ്‌സിൻ്റെ പതിപ്പ്"</string> <string name="database_version_title">"ഡാറ്റാബ്‌സിൻ്റെ പതിപ്പ്"</string>
<string name="max_history_items_title">പരമാവധി നമ്പർ</string> <string name="max_history_items_title">പരമാവധി നമ്പർ</string>
<string name="recycle_bin_title">റീസൈക്കിൾ ബിനിൻ്റെ ഉപയോഗം</string> <string name="recycle_bin_title">റീസൈക്കിൾ ബിനിൻ്റെ ഉപയോഗം</string>

View File

@@ -64,7 +64,7 @@
<string name="entry_user_name">Brukernavn</string> <string name="entry_user_name">Brukernavn</string>
<string name="error_arc4">Arcfour-strømchifferet støttes ikke.</string> <string name="error_arc4">Arcfour-strømchifferet støttes ikke.</string>
<string name="error_can_not_handle_uri">KeePassDX kan ikke håntere denne URI-en.</string> <string name="error_can_not_handle_uri">KeePassDX kan ikke håntere denne URI-en.</string>
<string name="error_file_not_create">Kunne ikke opprette fil:</string> <string name="error_file_not_create">Kunne ikke opprette fil</string>
<string name="error_invalid_db">Ugyldig database eller fremmed hovednøkkel.</string> <string name="error_invalid_db">Ugyldig database eller fremmed hovednøkkel.</string>
<string name="error_invalid_path">Ugyldig sti.</string> <string name="error_invalid_path">Ugyldig sti.</string>
<string name="error_no_name">Et navn er påkrevd.</string> <string name="error_no_name">Et navn er påkrevd.</string>
@@ -213,8 +213,6 @@
<string name="path">Sti</string> <string name="path">Sti</string>
<string name="assign_master_key">Tildel en hovednøkkel</string> <string name="assign_master_key">Tildel en hovednøkkel</string>
<string name="create_keepass_file">Opprett ny KeePass-fil</string> <string name="create_keepass_file">Opprett ny KeePass-fil</string>
<string name="full_file_path_enable_title">Filsti</string>
<string name="full_file_path_enable_summary">Vis hele filstien</string>
<string name="recycle_bin_title">Bruk papirkurv</string> <string name="recycle_bin_title">Bruk papirkurv</string>
<string name="recycle_bin_summary">Flytt en gruppe eller oppføring til \"Papirkurv\" før sletting</string> <string name="recycle_bin_summary">Flytt en gruppe eller oppføring til \"Papirkurv\" før sletting</string>
<string name="monospace_font_fields_enable_title">Feltskrift</string> <string name="monospace_font_fields_enable_title">Feltskrift</string>
@@ -387,7 +385,7 @@
<string name="database_data_compression_summary">Datakomprimering reduserer databasens størrelse.</string> <string name="database_data_compression_summary">Datakomprimering reduserer databasens størrelse.</string>
<string name="compression">Komprimering</string> <string name="compression">Komprimering</string>
<string name="compression_none">Ingen</string> <string name="compression_none">Ingen</string>
<string name="compression_gzip">GZip</string> <string name="compression_gzip">Gzip</string>
<string name="error_save_database">Kunne ikke lagre database.</string> <string name="error_save_database">Kunne ikke lagre database.</string>
<string name="menu_empty_recycle_bin">Tøm papirkurven</string> <string name="menu_empty_recycle_bin">Tøm papirkurven</string>
<string name="command_execution">Kjører kommandoen…</string> <string name="command_execution">Kjører kommandoen…</string>
@@ -401,7 +399,7 @@
<string name="download_attachment">Last ned %1$s</string> <string name="download_attachment">Last ned %1$s</string>
<string name="download_progression">Underveis: %1$d%%</string> <string name="download_progression">Underveis: %1$d%%</string>
<string name="download_finalization">Fullfører…</string> <string name="download_finalization">Fullfører…</string>
<string name="download_complete">Fullført. Trykk for å åpne filen.</string> <string name="download_complete">Fullført!</string>
<string name="hide_expired_entries_title">Skjul utløpte oppføringer</string> <string name="hide_expired_entries_title">Skjul utløpte oppføringer</string>
<string name="auto_focus_search_title">Hurtigsøk</string> <string name="auto_focus_search_title">Hurtigsøk</string>
<string name="entry_add_attachment">Legg til vedlegg</string> <string name="entry_add_attachment">Legg til vedlegg</string>

View File

@@ -59,7 +59,7 @@
<string name="entry_user_name">Gebruikersnaam</string> <string name="entry_user_name">Gebruikersnaam</string>
<string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string> <string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string>
<string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string> <string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string>
<string name="error_file_not_create">Bestand is niet aangemaakt:</string> <string name="error_file_not_create">Bestand is niet aangemaakt</string>
<string name="error_invalid_db">Kan database niet uitlezen.</string> <string name="error_invalid_db">Kan database niet uitlezen.</string>
<string name="error_invalid_path">Zorg ervoor dat het pad juist is.</string> <string name="error_invalid_path">Zorg ervoor dat het pad juist is.</string>
<string name="error_no_name">Voer een naam in.</string> <string name="error_no_name">Voer een naam in.</string>
@@ -238,8 +238,6 @@
<string name="path">Pad</string> <string name="path">Pad</string>
<string name="assign_master_key">Hoofdsleutel toewijzen</string> <string name="assign_master_key">Hoofdsleutel toewijzen</string>
<string name="create_keepass_file">Nieuwe database aanmaken</string> <string name="create_keepass_file">Nieuwe database aanmaken</string>
<string name="full_file_path_enable_title">Bestandspad</string>
<string name="full_file_path_enable_summary">Volledig bestandspad tonen</string>
<string name="recycle_bin_title">Prullenbak gebruiken</string> <string name="recycle_bin_title">Prullenbak gebruiken</string>
<string name="recycle_bin_summary">Verplaatst groepen en items naar \"Prullenbak\" voordat ze worden verwijderd</string> <string name="recycle_bin_summary">Verplaatst groepen en items naar \"Prullenbak\" voordat ze worden verwijderd</string>
<string name="monospace_font_fields_enable_title">Veldlettertype</string> <string name="monospace_font_fields_enable_title">Veldlettertype</string>
@@ -425,7 +423,7 @@
<string name="database_custom_color_title">Aangepaste databasekleur</string> <string name="database_custom_color_title">Aangepaste databasekleur</string>
<string name="compression">Compressie</string> <string name="compression">Compressie</string>
<string name="compression_none">Geen</string> <string name="compression_none">Geen</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Toetsenbordinstellingen</string> <string name="device_keyboard_setting_title">Toetsenbordinstellingen</string>
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string> <string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
<string name="education_setup_OTP_title">Instellingen OTP</string> <string name="education_setup_OTP_title">Instellingen OTP</string>
@@ -433,7 +431,7 @@
<string name="remember_database_locations_title">Databaselocatie opslaan</string> <string name="remember_database_locations_title">Databaselocatie opslaan</string>
<string name="hide_expired_entries_summary">Verlopen items worden verborgen</string> <string name="hide_expired_entries_summary">Verlopen items worden verborgen</string>
<string name="hide_expired_entries_title">Verberg verlopen items</string> <string name="hide_expired_entries_title">Verberg verlopen items</string>
<string name="download_complete">Klaar! Tik om het bestand te openen.</string> <string name="download_complete">Tik om het bestand te openen</string>
<string name="download_finalization">Voltooien…</string> <string name="download_finalization">Voltooien…</string>
<string name="download_progression">Voortgang: %1$d%%</string> <string name="download_progression">Voortgang: %1$d%%</string>
<string name="download_initialization">Initialiseren…</string> <string name="download_initialization">Initialiseren…</string>

View File

@@ -56,7 +56,7 @@
<string name="entry_user_name">Brukaramn</string> <string name="entry_user_name">Brukaramn</string>
<string name="error_arc4">Kan ikkje bruka Arcfour dataflytkryptering.</string> <string name="error_arc4">Kan ikkje bruka Arcfour dataflytkryptering.</string>
<string name="error_can_not_handle_uri">KeePassDX kan ikkje bruka denne ressursen.</string> <string name="error_can_not_handle_uri">KeePassDX kan ikkje bruka denne ressursen.</string>
<string name="error_file_not_create">Klarte ikkje å laga fila:</string> <string name="error_file_not_create">Klarte ikkje å laga fila</string>
<string name="error_invalid_db">Ugyldig database.</string> <string name="error_invalid_db">Ugyldig database.</string>
<string name="error_invalid_path">Ugyldig stig.</string> <string name="error_invalid_path">Ugyldig stig.</string>
<string name="error_no_name">Treng eit namn.</string> <string name="error_no_name">Treng eit namn.</string>

View File

@@ -6,7 +6,7 @@
<string name="icon_pack_choose_title">ਆਈਕਾਨ ਪੈਕ</string> <string name="icon_pack_choose_title">ਆਈਕਾਨ ਪੈਕ</string>
<string name="style_choose_summary">ਐਪ ਵਿੱਚ ਵਰਤਿਆ ਥੀਮ</string> <string name="style_choose_summary">ਐਪ ਵਿੱਚ ਵਰਤਿਆ ਥੀਮ</string>
<string name="style_choose_title">ਐਪ ਦਾ ਥੀਮ</string> <string name="style_choose_title">ਐਪ ਦਾ ਥੀਮ</string>
<string name="download_complete">ਪੂਰਾ ਹੋਇਆ! ਫ਼ਾਇਲ ਖੋਲ੍ਹਣ ਲਈ ਛੂਹੋ।</string> <string name="download_complete">ਪੂਰਾ ਹੋਇਆ!</string>
<string name="download_finalization">…ਪੂਰਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string> <string name="download_finalization">…ਪੂਰਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string>
<string name="download_progression">ਜਾਰੀ ਹੈ: %1$d%%</string> <string name="download_progression">ਜਾਰੀ ਹੈ: %1$d%%</string>
<string name="download_initialization">…ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string> <string name="download_initialization">…ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string>
@@ -22,7 +22,6 @@
<string name="clear_clipboard_notification_title">ਬੰਦ ਕਰਨ ਉੱਤੇ ਸਾਫ਼ ਕਰੋ</string> <string name="clear_clipboard_notification_title">ਬੰਦ ਕਰਨ ਉੱਤੇ ਸਾਫ਼ ਕਰੋ</string>
<string name="disable">ਅਸਮਰੱਥ</string> <string name="disable">ਅਸਮਰੱਥ</string>
<string name="enable">ਸਮਰੱਥ</string> <string name="enable">ਸਮਰੱਥ</string>
<string name="full_file_path_enable_title">ਫ਼ਾਇਲ ਦਾ ਮਾਰਗ</string>
<string name="assign_master_key">ਮਾਸਟਰ ਕੁੰਜੀ ਦਿਓ</string> <string name="assign_master_key">ਮਾਸਟਰ ਕੁੰਜੀ ਦਿਓ</string>
<string name="path">ਮਾਰਗ</string> <string name="path">ਮਾਰਗ</string>
<string name="file_name">ਫ਼ਾਇਲ ਦਾ ਨਾਂ</string> <string name="file_name">ਫ਼ਾਇਲ ਦਾ ਨਾਂ</string>
@@ -156,7 +155,7 @@
<string name="error_invalid_OTP">ਗ਼ਲਤ OTP ਭੇਤ ਹੈ।</string> <string name="error_invalid_OTP">ਗ਼ਲਤ OTP ਭੇਤ ਹੈ।</string>
<string name="error_invalid_path">ਪਾਥ ਦੇ ਠੀਕ ਹੋਣ ਨੂੰ ਯਕੀਨੀ ਬਣਾਓ।</string> <string name="error_invalid_path">ਪਾਥ ਦੇ ਠੀਕ ਹੋਣ ਨੂੰ ਯਕੀਨੀ ਬਣਾਓ।</string>
<string name="error_invalid_db">ਡਾਟਾਬੇਸ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ।</string> <string name="error_invalid_db">ਡਾਟਾਬੇਸ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ।</string>
<string name="error_file_not_create">ਫ਼ਾਇਲ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ:</string> <string name="error_file_not_create">ਫ਼ਾਇਲ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ</string>
<string name="error_can_not_handle_uri">ਇਹ URI KeePassDX ਵਿੱਚ ਹੈਂਡਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।</string> <string name="error_can_not_handle_uri">ਇਹ URI KeePassDX ਵਿੱਚ ਹੈਂਡਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।</string>
<string name="error_arc4">Arcfour ਸਟਰੀਮ ਸੀਫ਼ਰ ਸਹਾਇਕ ਨਹੀਂ ਹੈ।</string> <string name="error_arc4">Arcfour ਸਟਰੀਮ ਸੀਫ਼ਰ ਸਹਾਇਕ ਨਹੀਂ ਹੈ।</string>
<string name="entry_user_name">ਵਰਤੋਂਕਾਰ-ਨਾਂ</string> <string name="entry_user_name">ਵਰਤੋਂਕਾਰ-ਨਾਂ</string>

View File

@@ -55,7 +55,7 @@
<string name="entry_user_name">Nazwa użytkownika</string> <string name="entry_user_name">Nazwa użytkownika</string>
<string name="error_arc4">Strumieniowe szyfrowanie Arcfour nie jest wspierane.</string> <string name="error_arc4">Strumieniowe szyfrowanie Arcfour nie jest wspierane.</string>
<string name="error_can_not_handle_uri">Nie można obsłużyć tego identyfikatora URI w KeePassDX.</string> <string name="error_can_not_handle_uri">Nie można obsłużyć tego identyfikatora URI w KeePassDX.</string>
<string name="error_file_not_create">Nie można utworzyć pliku:</string> <string name="error_file_not_create">Nie można utworzyć pliku</string>
<string name="error_invalid_db">Nie można odczytać bazy danych.</string> <string name="error_invalid_db">Nie można odczytać bazy danych.</string>
<string name="error_invalid_path">Upewnij się, że ścieżka jest prawidłowa.</string> <string name="error_invalid_path">Upewnij się, że ścieżka jest prawidłowa.</string>
<string name="error_no_name">Wpisz nazwę.</string> <string name="error_no_name">Wpisz nazwę.</string>
@@ -193,7 +193,7 @@
<string name="warning_empty_password">Kontynuować bez ochrony odblokowującej hasło\?</string> <string name="warning_empty_password">Kontynuować bez ochrony odblokowującej hasło\?</string>
<string name="warning_no_encryption_key">Kontynuować bez klucza szyfrowania\?</string> <string name="warning_no_encryption_key">Kontynuować bez klucza szyfrowania\?</string>
<string name="version_label">Wersja %1$s</string> <string name="version_label">Wersja %1$s</string>
<string name="configure_biometric">Skanowanie odcisków palców jest obsługiwane, ale nie skonfigurowane.</string> <string name="configure_biometric">Skanowanie odcisków palców jest obsługiwane, ale nie jest skonfigurowane.</string>
<string name="encrypted_value_stored">Zapisano zaszyfrowane hasło</string> <string name="encrypted_value_stored">Zapisano zaszyfrowane hasło</string>
<string name="sort_groups_before">Grupy poprzednie</string> <string name="sort_groups_before">Grupy poprzednie</string>
<string name="open_biometric_prompt_unlock_database">Otwórz żądanie biometryczne, aby odblokować bazę danych</string> <string name="open_biometric_prompt_unlock_database">Otwórz żądanie biometryczne, aby odblokować bazę danych</string>
@@ -216,7 +216,7 @@
<string name="list_password_generator_options_summary">Ustaw dozwolone znaki generatora haseł</string> <string name="list_password_generator_options_summary">Ustaw dozwolone znaki generatora haseł</string>
<string name="clipboard">Schowek</string> <string name="clipboard">Schowek</string>
<string name="clipboard_notifications_title">Powiadomienia ze schowka</string> <string name="clipboard_notifications_title">Powiadomienia ze schowka</string>
<string name="clipboard_notifications_summary">Włącz powiadomienia schowka, aby skopiować pola podczas wyświetlania wpisu</string> <string name="clipboard_notifications_summary">Pokaż powiadomienia schowka, aby skopiować pola podczas przeglądania wpisu</string>
<string name="clipboard_warning">Jeśli automatyczne usuwanie schowka nie powiedzie się, ręcznie usuń jego historię.</string> <string name="clipboard_warning">Jeśli automatyczne usuwanie schowka nie powiedzie się, ręcznie usuń jego historię.</string>
<string name="lock">Blokada</string> <string name="lock">Blokada</string>
<string name="lock_database_screen_off_title">Blokada ekranu</string> <string name="lock_database_screen_off_title">Blokada ekranu</string>
@@ -228,14 +228,12 @@
<string name="biometric_delete_all_key_summary">Usuń wszystkie klucze szyfrowania związane z rozpoznawaniem linii papilarnych</string> <string name="biometric_delete_all_key_summary">Usuń wszystkie klucze szyfrowania związane z rozpoznawaniem linii papilarnych</string>
<string name="biometric_delete_all_key_warning">Czy usunąć wszystkie klucze szyfrowania związane z rozpoznawaniem biometrycznym\?</string> <string name="biometric_delete_all_key_warning">Czy usunąć wszystkie klucze szyfrowania związane z rozpoznawaniem biometrycznym\?</string>
<string name="unavailable_feature_text">Nie można uruchomić tej funkcji.</string> <string name="unavailable_feature_text">Nie można uruchomić tej funkcji.</string>
<string name="unavailable_feature_version">Twoja wersja Androida %1$s nie spełnia wymaganej minimalnej wersji %2$s.</string> <string name="unavailable_feature_version">Urządzenie pracuje na systemie Android %1$s, ale wymaga wersji %2$s lub nowszej.</string>
<string name="unavailable_feature_hardware">Nie można znaleźć odpowiedniego sprzętu.</string> <string name="unavailable_feature_hardware">Nie można znaleźć odpowiedniego sprzętu.</string>
<string name="file_name">Nazwa pliku</string> <string name="file_name">Nazwa pliku</string>
<string name="path">Ścieżka</string> <string name="path">Ścieżka</string>
<string name="assign_master_key">Przypisz klucz główny</string> <string name="assign_master_key">Przypisz klucz główny</string>
<string name="create_keepass_file">Utwórz nową bazę danych</string> <string name="create_keepass_file">Utwórz nową bazę danych</string>
<string name="full_file_path_enable_title">Ścieżka dostępu do plików</string>
<string name="full_file_path_enable_summary">Wyświetl pełną ścieżkę do pliku</string>
<string name="recycle_bin_title">Wykorzystaj kosz</string> <string name="recycle_bin_title">Wykorzystaj kosz</string>
<string name="recycle_bin_summary">Przenosi grupy i wpisy do grupy \"Kosz\" przed usunięciem</string> <string name="recycle_bin_summary">Przenosi grupy i wpisy do grupy \"Kosz\" przed usunięciem</string>
<string name="monospace_font_fields_enable_title">Pole czcionka</string> <string name="monospace_font_fields_enable_title">Pole czcionka</string>
@@ -253,7 +251,7 @@
<string name="magic_keyboard_title">Magikeyboard</string> <string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_explanation_summary">Aktywuj niestandardową klawiaturę wypełniającą hasła i wszystkie pola tożsamości</string> <string name="magic_keyboard_explanation_summary">Aktywuj niestandardową klawiaturę wypełniającą hasła i wszystkie pola tożsamości</string>
<string name="allow_no_password_title">Zezwalaj na brak klucza głównego</string> <string name="allow_no_password_title">Zezwalaj na brak klucza głównego</string>
<string name="allow_no_password_summary">Włącz przycisk \"Otwórz\", jeśli nie wybrano uwierzytelni</string> <string name="allow_no_password_summary">Umożliwia naciśnięcie przycisku \"Otwórz\", jeśli nie wybrano żadnych poświadcz</string>
<string name="enable_read_only_title">Ochrona przed zapisem</string> <string name="enable_read_only_title">Ochrona przed zapisem</string>
<string name="enable_read_only_summary">Domyślnie otwarte bazy danych są tylko do odczytu</string> <string name="enable_read_only_summary">Domyślnie otwarte bazy danych są tylko do odczytu</string>
<string name="enable_education_screens_title">Wskazówki edukacyjne</string> <string name="enable_education_screens_title">Wskazówki edukacyjne</string>
@@ -271,7 +269,7 @@
\nGrupy (~ foldery) organizują wpisy w bazie danych.</string> \nGrupy (~ foldery) organizują wpisy w bazie danych.</string>
<string name="education_search_title">Przeszukuj wpisy</string> <string name="education_search_title">Przeszukuj wpisy</string>
<string name="education_search_summary">Wprowadź tytuł, nazwę użytkownika lub zawartość innych pól, aby odzyskać swoje hasła.</string> <string name="education_search_summary">Wprowadź tytuł, nazwę użytkownika lub zawartość innych pól, aby odzyskać swoje hasła.</string>
<string name="education_biometric_title">Odblokuj bazę danych za pomocą odcisku palca</string> <string name="education_biometric_title">Biometryczne odblokowanie bazy danych</string>
<string name="education_biometric_summary">Połącz swoje hasło z zeskanowanym odciskiem palca, aby szybko odblokować bazę danych.</string> <string name="education_biometric_summary">Połącz swoje hasło z zeskanowanym odciskiem palca, aby szybko odblokować bazę danych.</string>
<string name="education_entry_edit_title">Edytuj wpis</string> <string name="education_entry_edit_title">Edytuj wpis</string>
<string name="education_entry_edit_summary">Edytuj swój wpis za pomocą pól niestandardowych. Dane puli mogą być przywoływane między różnymi polami wprowadzania.</string> <string name="education_entry_edit_summary">Edytuj swój wpis za pomocą pól niestandardowych. Dane puli mogą być przywoływane między różnymi polami wprowadzania.</string>
@@ -405,7 +403,7 @@
<string name="database_custom_color_title">Niestandardowy kolor bazy danych</string> <string name="database_custom_color_title">Niestandardowy kolor bazy danych</string>
<string name="compression">Kompresja</string> <string name="compression">Kompresja</string>
<string name="compression_none">Żaden</string> <string name="compression_none">Żaden</string>
<string name="compression_gzip">gzip</string> <string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Ustawienia klawiatury urządzenia</string> <string name="device_keyboard_setting_title">Ustawienia klawiatury urządzenia</string>
<string name="error_invalid_OTP">Nieprawidłowy klucz tajny OTP.</string> <string name="error_invalid_OTP">Nieprawidłowy klucz tajny OTP.</string>
<string name="error_disallow_no_credentials">Należy ustawić co najmniej jedno poświadczenie.</string> <string name="error_disallow_no_credentials">Należy ustawić co najmniej jedno poświadczenie.</string>
@@ -442,17 +440,17 @@
<string name="download_initialization">Inicjowanie…</string> <string name="download_initialization">Inicjowanie…</string>
<string name="download_progression">W trakcie realizacji: %1$d%%</string> <string name="download_progression">W trakcie realizacji: %1$d%%</string>
<string name="download_finalization">Kończę…</string> <string name="download_finalization">Kończę…</string>
<string name="download_complete">Kompletny! Stuknij, aby otworzyć plik.</string> <string name="download_complete">Kompletny!</string>
<string name="hide_expired_entries_title">Ukryj wygasłe wpisy</string> <string name="hide_expired_entries_title">Ukryj wygasłe wpisy</string>
<string name="hide_expired_entries_summary">Wygasłe wpisy są ukryte</string> <string name="hide_expired_entries_summary">Wygasłe wpisy nie są wyświetlane</string>
<string name="contact">Kontakt</string> <string name="contact">Kontakt</string>
<string name="html_about_contribution">Aby &lt;strong&gt;zachować naszą wolność&lt;/strong&gt;, &lt;strong&gt;sprawdzać błędy&lt;/strong&gt;, &lt;strong&gt;dodać funkcje&lt;/strong&gt; i &lt;strong&gt;by być zawsze aktywnym&lt;/strong&gt;, liczymy na twój &lt;strong&gt;wkład&lt;/strong&gt;.</string> <string name="html_about_contribution">Aby &lt;strong&gt;zachować naszą wolność&lt;/strong&gt;, &lt;strong&gt;sprawdzać błędy&lt;/strong&gt;, &lt;strong&gt;dodać funkcje&lt;/strong&gt; i &lt;strong&gt;by być zawsze aktywnym&lt;/strong&gt;, liczymy na twój &lt;strong&gt;wkład&lt;/strong&gt;.</string>
<string name="auto_focus_search_title">Szybkie wyszukiwanie</string> <string name="auto_focus_search_title">Szybkie wyszukiwanie</string>
<string name="auto_focus_search_summary">Wyszukiwanie po otwarciu bazy danych</string> <string name="auto_focus_search_summary">Wyszukiwanie po otwarciu bazy danych</string>
<string name="remember_database_locations_title">Zapisz lokalizację baz danych</string> <string name="remember_database_locations_title">Zapamiętaj lokalizacje baz danych</string>
<string name="remember_database_locations_summary">Zapamiętaj lokalizację baz danych</string> <string name="remember_database_locations_summary">Śledzi, gdzie przechowywane są bazy danych</string>
<string name="remember_keyfile_locations_title">Zapisz lokalizację plików kluczy</string> <string name="remember_keyfile_locations_title">Zapamiętaj lokalizacje plików kluczy</string>
<string name="remember_keyfile_locations_summary">Zapamiętaj lokalizację plików kluczy baz danych</string> <string name="remember_keyfile_locations_summary">Śledzi, gdzie przechowywane są pliki z kluczami</string>
<string name="show_recent_files_title">Pokaż najnowsze pliki</string> <string name="show_recent_files_title">Pokaż najnowsze pliki</string>
<string name="show_recent_files_summary">Pokaż lokalizacje najnowszych baz danych</string> <string name="show_recent_files_summary">Pokaż lokalizacje najnowszych baz danych</string>
<string name="hide_broken_locations_title">Ukryj uszkodzone łącza do bazy danych</string> <string name="hide_broken_locations_title">Ukryj uszkodzone łącza do bazy danych</string>

View File

@@ -56,7 +56,7 @@
<string name="entry_user_name">Nome de usuário</string> <string name="entry_user_name">Nome de usuário</string>
<string name="error_arc4">A cifra de fluxo Arcfour não é suportada.</string> <string name="error_arc4">A cifra de fluxo Arcfour não é suportada.</string>
<string name="error_can_not_handle_uri">Não pôde tratar esta URI no KeePassDX.</string> <string name="error_can_not_handle_uri">Não pôde tratar esta URI no KeePassDX.</string>
<string name="error_file_not_create">Não foi possível criar o arquivo:</string> <string name="error_file_not_create">Não foi possível criar o arquivo</string>
<string name="error_invalid_db">Falha ao ler o banco.</string> <string name="error_invalid_db">Falha ao ler o banco.</string>
<string name="error_invalid_path">Certifique-se de que o caminho está correto.</string> <string name="error_invalid_path">Certifique-se de que o caminho está correto.</string>
<string name="error_no_name">Digite um nome.</string> <string name="error_no_name">Digite um nome.</string>
@@ -229,8 +229,6 @@
<string name="path">Caminho</string> <string name="path">Caminho</string>
<string name="assign_master_key">Defina uma chave mestre</string> <string name="assign_master_key">Defina uma chave mestre</string>
<string name="create_keepass_file">Criar novo banco</string> <string name="create_keepass_file">Criar novo banco</string>
<string name="full_file_path_enable_title">Caminho do arquivo</string>
<string name="full_file_path_enable_summary">Veja o caminho inteiro do arquivo</string>
<string name="recycle_bin_title">Usar lixeira</string> <string name="recycle_bin_title">Usar lixeira</string>
<string name="recycle_bin_summary">Mover grupos e entradas para o grupo \"Lixeira\" antes de apagar</string> <string name="recycle_bin_summary">Mover grupos e entradas para o grupo \"Lixeira\" antes de apagar</string>
<string name="monospace_font_fields_enable_title">Fonte do Campo</string> <string name="monospace_font_fields_enable_title">Fonte do Campo</string>
@@ -423,7 +421,7 @@
<string name="database_custom_color_title">Cor personalizada do banco de dados</string> <string name="database_custom_color_title">Cor personalizada do banco de dados</string>
<string name="compression">Compressão</string> <string name="compression">Compressão</string>
<string name="compression_none">Nada</string> <string name="compression_none">Nada</string>
<string name="compression_gzip">GZip</string> <string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Configurações do teclado do aparelho</string> <string name="device_keyboard_setting_title">Configurações do teclado do aparelho</string>
<string name="error_save_database">Não foi possível salvar no banco de dados.</string> <string name="error_save_database">Não foi possível salvar no banco de dados.</string>
<string name="menu_save_database">Salvar banco de dados</string> <string name="menu_save_database">Salvar banco de dados</string>
@@ -443,7 +441,7 @@
<string name="autofill_auto_search_summary">Sugerir resultados de pesquisa de domínios da internet ou de aplicações automaticamente</string> <string name="autofill_auto_search_summary">Sugerir resultados de pesquisa de domínios da internet ou de aplicações automaticamente</string>
<string name="hide_expired_entries_summary">Entradas expeiradas foram escondidas</string> <string name="hide_expired_entries_summary">Entradas expeiradas foram escondidas</string>
<string name="hide_expired_entries_title">Esconder entradas expiradas</string> <string name="hide_expired_entries_title">Esconder entradas expiradas</string>
<string name="download_complete">Completo! Toque para abrir o aquivo.</string> <string name="download_complete">Completo!</string>
<string name="download_finalization">Finalizando…</string> <string name="download_finalization">Finalizando…</string>
<string name="download_progression">Em progresso: %1$d%%</string> <string name="download_progression">Em progresso: %1$d%%</string>
<string name="download_initialization">Inicializando…</string> <string name="download_initialization">Inicializando…</string>

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