mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe7074736a | ||
|
|
69c523ffad | ||
|
|
6aeefdf43d | ||
|
|
5f4c8be3d3 | ||
|
|
3092e4c557 | ||
|
|
ea119068da | ||
|
|
14371ecf94 | ||
|
|
45b0fcfe15 | ||
|
|
00aa5f5586 | ||
|
|
79fd53fd4c | ||
|
|
357ee3daf0 | ||
|
|
c2f7897f10 | ||
|
|
75455c0c48 | ||
|
|
a394bb9f8e | ||
|
|
2f5a846493 | ||
|
|
90376b361d | ||
|
|
229cf6bf5f | ||
|
|
bc46737353 | ||
|
|
1db2243a2e | ||
|
|
7cf836b3cb | ||
|
|
f7bbd295d6 | ||
|
|
62dbd95b48 | ||
|
|
df07e9c719 | ||
|
|
9aaf72726e | ||
|
|
5289927619 | ||
|
|
fa8c686f75 | ||
|
|
df5f28b7c4 | ||
|
|
280d8368fa | ||
|
|
80dbff1f21 | ||
|
|
7ee68a8481 | ||
|
|
ac8dd42c45 | ||
|
|
eed2148b2a | ||
|
|
dc5345b6d3 | ||
|
|
221af0b5bb | ||
|
|
a10ccc1eb0 | ||
|
|
b72d858480 | ||
|
|
60412cc90b | ||
|
|
512ac87dc9 | ||
|
|
d97020d1c5 | ||
|
|
f82cb617ba | ||
|
|
f79281a1a0 | ||
|
|
7be1dbb78b | ||
|
|
f875787799 | ||
|
|
f82c208556 | ||
|
|
f2150e3d85 | ||
|
|
ecc198e8a0 | ||
|
|
949bc58a80 | ||
|
|
7d79fff16f | ||
|
|
3aeb678292 | ||
|
|
160ac41bed | ||
|
|
9dfbcbe89c | ||
|
|
30da529348 | ||
|
|
dd8d114711 | ||
|
|
2191a4a848 | ||
|
|
8b9ea8d988 | ||
|
|
46dda8567d | ||
|
|
6953da4d9a | ||
|
|
e987d6647e | ||
|
|
359d85727e | ||
|
|
a994bf9dd8 | ||
|
|
14c4e095f6 | ||
|
|
59dce0e56f | ||
|
|
1f54a893a7 | ||
|
|
9489f1ee3d | ||
|
|
dc3d720e8d | ||
|
|
efe30b598b | ||
|
|
42515bfb2d | ||
|
|
acb3657d95 | ||
|
|
e7159c9d36 | ||
|
|
f3fdca368b | ||
|
|
4ea3e08a45 | ||
|
|
1eebc72b21 | ||
|
|
9a91be7e36 | ||
|
|
48476f9b88 | ||
|
|
68c991eb9b | ||
|
|
b51c77b01b | ||
|
|
57105db554 | ||
|
|
4bd3bdaddf | ||
|
|
9cf59b8d73 | ||
|
|
a793b0bb42 | ||
|
|
4d9e2e1471 | ||
|
|
1719887e55 | ||
|
|
2be00aca9d | ||
|
|
6fd05c5ad7 | ||
|
|
65e404374f | ||
|
|
0b78731bb3 | ||
|
|
65d318ed88 | ||
|
|
1bfcea55a9 | ||
|
|
6780eb004d | ||
|
|
0e12b2d021 | ||
|
|
1b356f87ec | ||
|
|
bf24d0bae1 | ||
|
|
97c831d4bb | ||
|
|
e3e48ffa6d | ||
|
|
b678416122 | ||
|
|
df722925fa | ||
|
|
4cbc0d9806 | ||
|
|
b0f3711b4e | ||
|
|
14ec6579b2 | ||
|
|
30bf039473 | ||
|
|
33bea317b0 | ||
|
|
c6814dc05e | ||
|
|
16808069ec | ||
|
|
e813974e29 | ||
|
|
7e0010b536 | ||
|
|
93171adcb3 | ||
|
|
9501cc76a4 | ||
|
|
5d7db046ac | ||
|
|
46c259bc3e | ||
|
|
3bacff91d3 | ||
|
|
bd79d483d2 | ||
|
|
16e31f4881 | ||
|
|
afa23c393d | ||
|
|
54d23cb781 | ||
|
|
c757e410e9 | ||
|
|
39dd25567d | ||
|
|
b3c0494618 |
@@ -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)
|
||||
* Fix themes / new UI
|
||||
* Fix multiples notifications
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 29
|
||||
versionCode = 38
|
||||
versionName = "2.8.2"
|
||||
versionCode = 39
|
||||
versionName = "2.8.3"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
|
||||
@@ -45,8 +45,9 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.education.EntryActivityEducation
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryAttachment
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||
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 mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
|
||||
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
|
||||
|
||||
private var clipboardHelper: ClipboardHelper? = null
|
||||
private var mFirstLaunchOfActivity: Boolean = false
|
||||
@@ -212,8 +213,8 @@ class EntryActivity : LockingActivity() {
|
||||
mAttachmentFileBinderManager?.apply {
|
||||
registerProgressTask()
|
||||
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
|
||||
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
|
||||
entryContentsView?.updateAttachmentDownloadProgress(attachment)
|
||||
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
|
||||
entryContentsView?.putAttachment(entryAttachmentState)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,14 +241,13 @@ class EntryActivity : LockingActivity() {
|
||||
toolbar?.title = entryTitle
|
||||
|
||||
// Assign basic fields
|
||||
entryContentsView?.assignUserName(entry.username)
|
||||
entryContentsView?.assignUserNameCopyListener(View.OnClickListener {
|
||||
entryContentsView?.assignUserName(entry.username) {
|
||||
database.startManageEntry(entry)
|
||||
clipboardHelper?.timeoutCopyToClipboard(entry.username,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_user_name)))
|
||||
database.stopManageEntry(entry)
|
||||
})
|
||||
}
|
||||
|
||||
val isFirstTimeAskAllowCopyPasswordAndProtectedFields =
|
||||
PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)
|
||||
@@ -274,23 +274,25 @@ class EntryActivity : LockingActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
entryContentsView?.assignPassword(entry.password, allowCopyPasswordAndProtectedFields)
|
||||
if (allowCopyPasswordAndProtectedFields) {
|
||||
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
|
||||
val onPasswordCopyClickListener: View.OnClickListener? = if (allowCopyPasswordAndProtectedFields) {
|
||||
View.OnClickListener {
|
||||
database.startManageEntry(entry)
|
||||
clipboardHelper?.timeoutCopyToClipboard(entry.password,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_password)))
|
||||
database.stopManageEntry(entry)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// If dialog not already shown
|
||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||
entryContentsView?.assignPasswordCopyListener(showWarningClipboardDialogOnClickListener)
|
||||
showWarningClipboardDialogOnClickListener
|
||||
} else {
|
||||
entryContentsView?.assignPasswordCopyListener(null)
|
||||
null
|
||||
}
|
||||
}
|
||||
entryContentsView?.assignPassword(entry.password,
|
||||
allowCopyPasswordAndProtectedFields,
|
||||
onPasswordCopyClickListener)
|
||||
|
||||
//Assign OTP field
|
||||
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
|
||||
@@ -304,7 +306,7 @@ class EntryActivity : LockingActivity() {
|
||||
})
|
||||
|
||||
entryContentsView?.assignURL(entry.url)
|
||||
entryContentsView?.assignComment(entry.notes)
|
||||
entryContentsView?.assignNotes(entry.notes)
|
||||
|
||||
// Assign custom fields
|
||||
if (entry.allowCustomFields()) {
|
||||
@@ -312,12 +314,12 @@ class EntryActivity : LockingActivity() {
|
||||
for ((label, value) in entry.customFields) {
|
||||
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
|
||||
if (allowCopyProtectedField) {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField, View.OnClickListener {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
|
||||
clipboardHelper?.timeoutCopyToClipboard(
|
||||
value.toString(),
|
||||
getString(R.string.copy_field, label)
|
||||
)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// If dialog not already shown
|
||||
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||
@@ -328,20 +330,15 @@ class EntryActivity : LockingActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
||||
entryContentsView?.setHiddenProtectedValue(!mShowPassword)
|
||||
|
||||
// Manage attachments
|
||||
entryContentsView?.assignAttachments(entry.getAttachments()) { attachmentItem ->
|
||||
when (attachmentItem.downloadState) {
|
||||
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> {
|
||||
mDatabase?.binaryPool?.let { binaryPool ->
|
||||
entryContentsView?.assignAttachments(entry.getAttachments(binaryPool).toSet(), StreamDirection.DOWNLOAD) { attachmentItem ->
|
||||
createDocument(this, attachmentItem.name)?.let { requestCode ->
|
||||
mAttachmentsToDownload[requestCode] = attachmentItem
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// TODO Stop download
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign dates
|
||||
@@ -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 {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
|
||||
@@ -418,15 +405,6 @@ class EntryActivity : LockingActivity() {
|
||||
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)
|
||||
gotoUrl?.apply {
|
||||
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
|
||||
@@ -449,28 +427,31 @@ class EntryActivity : LockingActivity() {
|
||||
|
||||
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
||||
menu: Menu) {
|
||||
val entryCopyEducationPerformed = entryContentsView?.isUserNamePresent == true
|
||||
val entryFieldCopyView = findViewById<View>(R.id.entry_field_copy)
|
||||
val entryCopyEducationPerformed = entryFieldCopyView != null
|
||||
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
|
||||
findViewById(R.id.entry_user_name_action_image),
|
||||
entryFieldCopyView,
|
||||
{
|
||||
clipboardHelper?.timeoutCopyToClipboard(mEntry!!.username,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_user_name)))
|
||||
val appNameString = getString(R.string.app_name)
|
||||
clipboardHelper?.timeoutCopyToClipboard(appNameString,
|
||||
getString(R.string.copy_field, appNameString))
|
||||
},
|
||||
{
|
||||
performedNextEducation(entryActivityEducation, menu)
|
||||
})
|
||||
|
||||
if (!entryCopyEducationPerformed) {
|
||||
val menuEditView = toolbar?.findViewById<View>(R.id.menu_edit)
|
||||
// entryEditEducationPerformed
|
||||
toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
|
||||
toolbar!!.findViewById(R.id.menu_edit),
|
||||
menuEditView != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
|
||||
menuEditView,
|
||||
{
|
||||
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
|
||||
},
|
||||
{
|
||||
performedNextEducation(entryActivityEducation, menu)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,12 +461,6 @@ class EntryActivity : LockingActivity() {
|
||||
MenuUtil.onContributionItemSelected(this)
|
||||
return true
|
||||
}
|
||||
R.id.menu_toggle_pass -> {
|
||||
mShowPassword = !mShowPassword
|
||||
changeShowPasswordIcon(item)
|
||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
||||
return true
|
||||
}
|
||||
R.id.menu_edit -> {
|
||||
mEntry?.let {
|
||||
EntryEditActivity.launch(this@EntryActivity, it)
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.app.Activity
|
||||
import android.app.DatePickerDialog
|
||||
import android.app.TimePickerDialog
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
@@ -36,18 +38,17 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import com.kunzisoft.keepass.R
|
||||
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.database.element.Database
|
||||
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.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
import com.kunzisoft.keepass.model.FocusedEditField
|
||||
import com.kunzisoft.keepass.model.*
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||
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_UPDATE_ENTRY_TASK
|
||||
@@ -55,8 +56,10 @@ import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.EntryEditContentsView
|
||||
import com.kunzisoft.keepass.view.showActionError
|
||||
import com.kunzisoft.keepass.view.updateLockPaddingLeft
|
||||
@@ -69,7 +72,9 @@ class EntryEditActivity : LockingActivity(),
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
||||
SetOTPDialogFragment.CreateOtpListener,
|
||||
DatePickerDialog.OnDateSetListener,
|
||||
TimePickerDialog.OnTimeSetListener {
|
||||
TimePickerDialog.OnTimeSetListener,
|
||||
FileTooBigDialogFragment.ActionChooseListener,
|
||||
ReplaceFileDialogFragment.ActionChooseListener {
|
||||
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
@@ -90,6 +95,11 @@ class EntryEditActivity : LockingActivity(),
|
||||
|
||||
private var mFocusedEditExtraField: FocusedEditField? = null
|
||||
|
||||
// To manage attachments
|
||||
private var mSelectFileHelper: SelectFileHelper? = null
|
||||
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||
private var mAllowMultipleAttachments: Boolean = false
|
||||
|
||||
// Education
|
||||
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
||||
|
||||
@@ -215,22 +225,22 @@ class EntryEditActivity : LockingActivity(),
|
||||
entryEditAddToolBar?.apply {
|
||||
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 {
|
||||
val allowCustomField = mNewEntry?.allowCustomFields() == true
|
||||
isEnabled = 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 {
|
||||
val allowOTP = mDatabase?.allowOTP == true
|
||||
isEnabled = allowOTP
|
||||
isVisible = allowOTP
|
||||
// OTP not compatible below KitKat
|
||||
isVisible = allowOTP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
||||
}
|
||||
|
||||
setOnMenuItemClickListener { item ->
|
||||
@@ -239,6 +249,10 @@ class EntryEditActivity : LockingActivity(),
|
||||
addNewCustomField()
|
||||
true
|
||||
}
|
||||
R.id.menu_add_attachment -> {
|
||||
addNewAttachment(item)
|
||||
true
|
||||
}
|
||||
R.id.menu_add_otp -> {
|
||||
setupOTP()
|
||||
true
|
||||
@@ -248,6 +262,10 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
// To retrieve attachment
|
||||
mSelectFileHelper = SelectFileHelper(this)
|
||||
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
|
||||
|
||||
// Save button
|
||||
validateButton = findViewById(R.id.entry_edit_validate)
|
||||
validateButton?.setOnClickListener { saveEntry() }
|
||||
@@ -279,6 +297,54 @@ class EntryEditActivity : LockingActivity(),
|
||||
|
||||
// Padding if lock button visible
|
||||
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) {
|
||||
@@ -303,12 +369,17 @@ class EntryEditActivity : LockingActivity(),
|
||||
notes = newEntry.notes
|
||||
assignExtraFields(newEntry.customFields.mapTo(ArrayList()) {
|
||||
Field(it.key, it.value)
|
||||
}, {
|
||||
editCustomField(it)
|
||||
}, mFocusedEditExtraField)
|
||||
assignAttachments(newEntry.getAttachments()) { attachment ->
|
||||
|
||||
mDatabase?.binaryPool?.let { binaryPool ->
|
||||
assignAttachments(newEntry.getAttachments(binaryPool).toSet(), StreamDirection.UPLOAD) { attachment ->
|
||||
newEntry.removeAttachment(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateEntryWithViews(newEntry: Entry) {
|
||||
|
||||
@@ -327,9 +398,14 @@ class EntryEditActivity : LockingActivity(),
|
||||
expiryTime = entryView.expiresDate
|
||||
}
|
||||
notes = entryView.notes
|
||||
entryView.getExtraField().forEach { customField ->
|
||||
entryView.getExtraFields().forEach { customField ->
|
||||
putExtraField(customField.name, customField.protectedValue)
|
||||
}
|
||||
mDatabase?.binaryPool?.let { binaryPool ->
|
||||
entryView.getAttachments().forEach {
|
||||
putAttachment(it, binaryPool)
|
||||
}
|
||||
}
|
||||
mFocusedEditExtraField = entryView.getExtraFieldFocused()
|
||||
}
|
||||
}
|
||||
@@ -358,12 +434,84 @@ class EntryEditActivity : LockingActivity(),
|
||||
EntryCustomFieldDialogFragment.getInstance().show(supportFragmentManager, "customFieldDialog")
|
||||
}
|
||||
|
||||
override fun onNewCustomFieldApproved(label: String, protection: Boolean) {
|
||||
entryEditContentsView?.putExtraField(Field(label, ProtectedString(protection)))
|
||||
private fun editCustomField(field: Field) {
|
||||
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() {
|
||||
// Retrieve the current otpElement if exists
|
||||
// and open the dialog to set up the OTP
|
||||
@@ -441,8 +589,8 @@ class EntryEditActivity : LockingActivity(),
|
||||
if (!generatePasswordEducationPerformed) {
|
||||
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
|
||||
val addNewFieldEducationPerformed = mNewEntry != null
|
||||
&& mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty()
|
||||
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
|
||||
&& mNewEntry!!.allowCustomFields() && addNewFieldView != null
|
||||
&& addNewFieldView.visibility == View.VISIBLE
|
||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
||||
addNewFieldView,
|
||||
{
|
||||
@@ -453,13 +601,27 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
)
|
||||
if (!addNewFieldEducationPerformed) {
|
||||
val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment)
|
||||
val addAttachmentEducationPerformed = attachmentView != null && attachmentView.visibility == View.VISIBLE
|
||||
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation(
|
||||
attachmentView,
|
||||
{
|
||||
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
|
||||
val otpField = OtpEntryFields.buildOtpField(otpElement,
|
||||
mEntry?.title, mEntry?.username)
|
||||
entryEditContentsView?.putExtraField(otpField)
|
||||
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
|
||||
entryEditContentsView?.apply {
|
||||
putExtraField(otpField)
|
||||
getExtraFieldViewPosition(otpField) { position ->
|
||||
scrollView?.smoothScrollTo(0, position.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun iconPicked(bundle: Bundle) {
|
||||
|
||||
@@ -45,7 +45,7 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
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.adapters.FileDatabaseHistoryAdapter
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
@@ -69,7 +69,6 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
|
||||
// Views
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
private var fileManagerExplanationButton: View? = null
|
||||
private var createDatabaseButtonView: View? = null
|
||||
private var openDatabaseButtonView: View? = null
|
||||
|
||||
@@ -82,7 +81,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
|
||||
private var mDatabaseFileUri: Uri? = null
|
||||
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
private var mSelectFileHelper: SelectFileHelper? = null
|
||||
|
||||
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
|
||||
|
||||
@@ -98,20 +97,15 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
toolbar.title = ""
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
fileManagerExplanationButton = findViewById(R.id.file_manager_explanation_button)
|
||||
fileManagerExplanationButton?.setOnClickListener {
|
||||
UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
|
||||
}
|
||||
|
||||
// Create database button
|
||||
createDatabaseButtonView = findViewById(R.id.create_database_button)
|
||||
createDatabaseButtonView?.setOnClickListener { createNewFile() }
|
||||
|
||||
// Open database button
|
||||
mOpenFileHelper = OpenFileHelper(this)
|
||||
mSelectFileHelper = SelectFileHelper(this)
|
||||
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
|
||||
openDatabaseButtonView?.apply {
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.let {
|
||||
mSelectFileHelper?.selectFileOnClickViewListener?.let {
|
||||
setOnClickListener(it)
|
||||
setOnLongClickListener(it)
|
||||
}
|
||||
@@ -389,7 +383,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
||||
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
||||
if (uri != null) {
|
||||
launchPasswordActivityWithPath(uri)
|
||||
}
|
||||
@@ -445,7 +439,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
openDatabaseButtonView!!,
|
||||
{tapTargetView ->
|
||||
tapTargetView?.let {
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
|
||||
mSelectFileHelper?.selectFileOnClickViewListener?.onClick(it)
|
||||
}
|
||||
},
|
||||
{}
|
||||
@@ -454,6 +448,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
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.SelectFileHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||
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.READ_ONLY_KEY
|
||||
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.KeyFileSelectionView
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
@@ -92,7 +94,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
private var mDatabaseKeyFileUri: Uri? = null
|
||||
|
||||
private var mRememberKeyFile: Boolean = false
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
private var mSelectFileHelper: SelectFileHelper? = null
|
||||
|
||||
private var mPermissionAsked = false
|
||||
private var readOnly: Boolean = false
|
||||
@@ -136,9 +138,9 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
||||
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
|
||||
|
||||
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
||||
mSelectFileHelper = SelectFileHelper(this@PasswordActivity)
|
||||
keyFileSelectionView?.apply {
|
||||
mOpenFileHelper?.openFileOnClickViewListener?.let {
|
||||
mSelectFileHelper?.selectFileOnClickViewListener?.let {
|
||||
setOnClickListener(it)
|
||||
setOnLongClickListener(it)
|
||||
}
|
||||
@@ -747,7 +749,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
}
|
||||
|
||||
var keyFileResult = false
|
||||
mOpenFileHelper?.let {
|
||||
mSelectFileHelper?.let {
|
||||
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
|
||||
@@ -25,16 +25,16 @@ import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
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.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
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.activities.helpers.OpenFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||
|
||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
@@ -56,7 +56,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
|
||||
private var mListener: AssignPasswordDialogListener? = null
|
||||
|
||||
private var mOpenFileHelper: OpenFileHelper? = null
|
||||
private var mSelectFileHelper: SelectFileHelper? = null
|
||||
|
||||
private val passwordTextWatcher = object : TextWatcher {
|
||||
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)
|
||||
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
|
||||
|
||||
mOpenFileHelper = OpenFileHelper(this)
|
||||
mSelectFileHelper = SelectFileHelper(this)
|
||||
keyFileSelectionView?.apply {
|
||||
setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||
setOnLongClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||
setOnClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
|
||||
setOnLongClickListener(mSelectFileHelper?.selectFileOnClickViewListener)
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
@@ -249,8 +249,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
||||
uri?.let { pathUri ->
|
||||
keyFileCheckBox?.isChecked = true
|
||||
keyFileSelectionView?.uri = pathUri
|
||||
|
||||
@@ -22,24 +22,31 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
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.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
|
||||
|
||||
class EntryCustomFieldDialogFragment: DialogFragment() {
|
||||
|
||||
private var oldField: Field? = null
|
||||
|
||||
private var entryCustomFieldListener: EntryCustomFieldListener? = null
|
||||
|
||||
private var customFieldLabelContainer: TextInputLayout? = null
|
||||
private var customFieldLabel: TextView? = null
|
||||
private var customFieldDeleteButton: ImageView? = null
|
||||
private var customFieldProtectionButton: CompoundButton? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
@@ -58,17 +65,27 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
|
||||
val root = activity.layoutInflater.inflate(R.layout.fragment_entry_new_field, null)
|
||||
customFieldLabelContainer = root?.findViewById(R.id.entry_custom_field_label_container)
|
||||
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)
|
||||
|
||||
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)
|
||||
builder.setView(root)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
entryCustomFieldListener?.onNewCustomFieldCanceled(
|
||||
customFieldLabel?.text.toString(),
|
||||
customFieldProtectionButton?.isChecked == true
|
||||
)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
val dialogCreated = builder.create()
|
||||
|
||||
customFieldLabel?.requestFocus()
|
||||
@@ -102,10 +119,19 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
|
||||
|
||||
private fun approveIfValid() {
|
||||
if (isValid()) {
|
||||
entryCustomFieldListener?.onNewCustomFieldApproved(
|
||||
customFieldLabel?.text.toString(),
|
||||
customFieldProtectionButton?.isChecked == true
|
||||
oldField?.let {
|
||||
// New property with old value
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -127,13 +153,25 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
|
||||
}
|
||||
|
||||
interface EntryCustomFieldListener {
|
||||
fun onNewCustomFieldApproved(label: String, protection: Boolean)
|
||||
fun onNewCustomFieldCanceled(label: String, protection: Boolean)
|
||||
fun onNewCustomFieldApproved(newField: Field)
|
||||
fun onEditCustomFieldApproved(oldField: Field, newField: Field)
|
||||
fun onDeleteCustomFieldApproved(oldField: Field)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val KEY_FIELD = "KEY_FIELD"
|
||||
|
||||
fun getInstance(): EntryCustomFieldDialogFragment {
|
||||
return EntryCustomFieldDialogFragment()
|
||||
}
|
||||
|
||||
fun getInstance(field: Field): EntryCustomFieldDialogFragment {
|
||||
return EntryCustomFieldDialogFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(KEY_FIELD, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,19 +28,20 @@ import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class OpenFileHelper {
|
||||
class SelectFileHelper {
|
||||
|
||||
private var activity: Activity? = null
|
||||
private var fragment: Fragment? = null
|
||||
|
||||
val openFileOnClickViewListener: OpenFileOnClickViewListener
|
||||
get() = OpenFileOnClickViewListener()
|
||||
val selectFileOnClickViewListener: SelectFileOnClickViewListener
|
||||
get() = SelectFileOnClickViewListener()
|
||||
|
||||
constructor(context: Activity) {
|
||||
this.activity = context
|
||||
@@ -52,7 +53,10 @@ class OpenFileHelper {
|
||||
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) {
|
||||
try {
|
||||
@@ -85,6 +89,11 @@ class OpenFileHelper {
|
||||
onAbstractClick(true)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
||||
onAbstractClick()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
@@ -32,6 +32,18 @@ abstract class AnimatedItemsAdapter<Item, T: RecyclerView.ViewHolder>(val contex
|
||||
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) {
|
||||
val previousSize = itemsList.size
|
||||
if (itemsList.contains(item)) {
|
||||
@@ -46,13 +58,42 @@ abstract class AnimatedItemsAdapter<Item, T: RecyclerView.ViewHolder>(val contex
|
||||
onListSizeChangedListener?.invoke(previousSize, itemsList.size)
|
||||
}
|
||||
|
||||
fun onBindDeleteButton(holder: T, deleteButton: View, item: Item, position: Int) {
|
||||
deleteButton.apply {
|
||||
visibility = View.VISIBLE
|
||||
if (mItemToRemove == item) {
|
||||
/**
|
||||
* 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 {
|
||||
visibility = View.VISIBLE
|
||||
if (performDeletion(holder, item)) {
|
||||
setOnClickListener(null)
|
||||
} else {
|
||||
setOnClickListener {
|
||||
|
||||
@@ -23,36 +23,34 @@ import android.content.Context
|
||||
import android.text.format.Formatter
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
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)
|
||||
: AnimatedItemsAdapter<EntryAttachment, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
||||
class EntryAttachmentsItemsAdapter(context: Context)
|
||||
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
||||
|
||||
var onItemClickListener: ((item: EntryAttachment)->Unit)? = null
|
||||
|
||||
private val mDatabase = Database.getInstance()
|
||||
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
|
||||
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
|
||||
val entryAttachment = itemsList[position]
|
||||
val entryAttachmentState = itemsList[position]
|
||||
|
||||
holder.itemView.visibility = View.VISIBLE
|
||||
holder.binaryFileTitle.text = entryAttachment.name
|
||||
holder.binaryFileTitle.text = entryAttachmentState.attachment.name
|
||||
holder.binaryFileSize.text = Formatter.formatFileSize(context,
|
||||
entryAttachment.binaryAttachment.length())
|
||||
entryAttachmentState.attachment.binaryAttachment.length())
|
||||
holder.binaryFileCompression.apply {
|
||||
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
|
||||
|| entryAttachment.binaryAttachment.isCompressed == true) {
|
||||
if (entryAttachmentState.attachment.binaryAttachment.isCompressed) {
|
||||
text = CompressionAlgorithm.GZip.getName(context.resources)
|
||||
visibility = View.VISIBLE
|
||||
} else {
|
||||
@@ -60,42 +58,60 @@ class EntryAttachmentsItemsAdapter(context: Context, private val editable: Boole
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
if (editable) {
|
||||
when (entryAttachmentState.streamDirection) {
|
||||
StreamDirection.UPLOAD -> {
|
||||
holder.binaryFileProgressIcon.isActivated = true
|
||||
when (entryAttachmentState.downloadState) {
|
||||
AttachmentState.START,
|
||||
AttachmentState.IN_PROGRESS -> {
|
||||
holder.binaryFileProgressContainer.visibility = View.VISIBLE
|
||||
holder.binaryFileProgress.apply {
|
||||
visibility = View.VISIBLE
|
||||
progress = entryAttachmentState.downloadProgression
|
||||
}
|
||||
holder.binaryFileDeleteButton.apply {
|
||||
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, entryAttachment, position)
|
||||
onBindDeleteButton(holder, this, entryAttachmentState, position)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
holder.itemView.setOnClickListener(null)
|
||||
}
|
||||
StreamDirection.DOWNLOAD -> {
|
||||
holder.binaryFileProgressIcon.isActivated = false
|
||||
holder.binaryFileProgressContainer.visibility = View.VISIBLE
|
||||
holder.binaryFileDeleteButton.visibility = View.GONE
|
||||
holder.binaryFileProgress.apply {
|
||||
visibility = when (entryAttachment.downloadState) {
|
||||
visibility = when (entryAttachmentState.downloadState) {
|
||||
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
|
||||
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
|
||||
}
|
||||
progress = entryAttachment.downloadProgression
|
||||
progress = entryAttachmentState.downloadProgression
|
||||
}
|
||||
holder.itemView.setOnClickListener {
|
||||
onItemClickListener?.invoke(entryAttachment)
|
||||
onItemClickListener?.invoke(entryAttachmentState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateProgress(entryAttachment: EntryAttachment) {
|
||||
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) {
|
||||
class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
|
||||
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
|
||||
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
|
||||
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 binaryFileDeleteButton: View = itemView.findViewById(R.id.item_attachment_delete_button)
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ class EntryExtraFieldsItemsAdapter(context: Context)
|
||||
private var mLastFocusedEditField = FocusedEditField()
|
||||
private var mLastFocusedTimestamp: Long = 0L
|
||||
|
||||
var onEditButtonClickListener: ((item: Field)->Unit)? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryExtraFieldViewHolder {
|
||||
val view = EntryExtraFieldViewHolder(
|
||||
inflater.inflate(R.layout.item_entry_edit_extra_field, parent, false)
|
||||
@@ -73,13 +75,12 @@ class EntryExtraFieldsItemsAdapter(context: Context)
|
||||
setFocusField(extraField, selectionStart, selectionEnd)
|
||||
} else {
|
||||
// request focus on last text focused
|
||||
if (focusedTimestampNotExpired()) {
|
||||
if (focusedTimestampNotExpired())
|
||||
requestFocusField(this, extraField, false)
|
||||
} else {
|
||||
else
|
||||
removeFocusField(extraField)
|
||||
}
|
||||
}
|
||||
}
|
||||
addOnSelectionChangedListener(object: EditTextSelectable.OnSelectionChangedListener {
|
||||
override fun onSelectionChanged(start: Int, end: Int) {
|
||||
mLastFocusedEditField.apply {
|
||||
@@ -95,9 +96,10 @@ class EntryExtraFieldsItemsAdapter(context: Context)
|
||||
if (applyFontVisibility)
|
||||
applyFontVisibility()
|
||||
}
|
||||
holder.extraFieldDeleteButton.apply {
|
||||
onBindDeleteButton(holder, this, extraField, position)
|
||||
holder.extraFieldEditButton.setOnClickListener {
|
||||
onEditButtonClickListener?.invoke(extraField)
|
||||
}
|
||||
performDeletion(holder, extraField)
|
||||
}
|
||||
|
||||
fun assignItems(items: List<Field>, focusedEditField: FocusedEditField?) {
|
||||
@@ -107,15 +109,6 @@ class EntryExtraFieldsItemsAdapter(context: Context)
|
||||
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,
|
||||
selectionStart: Int,
|
||||
selectionEnd: Int,
|
||||
@@ -147,6 +140,7 @@ class EntryExtraFieldsItemsAdapter(context: Context)
|
||||
setEditTextSelection(editText)
|
||||
}
|
||||
requestFocus()
|
||||
removeFocusField(field)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,7 +170,7 @@ class EntryExtraFieldsItemsAdapter(context: Context)
|
||||
class EntryExtraFieldViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
var extraFieldValueContainer: TextInputLayout = itemView.findViewById(R.id.entry_extra_field_value_container)
|
||||
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 {
|
||||
|
||||
@@ -101,9 +101,9 @@ class FileDatabaseHistoryAdapter(context: Context)
|
||||
// Modification
|
||||
databaseFile.databaseLastModified?.let {
|
||||
holder.fileModification.text = it
|
||||
holder.fileModification.visibility = View.VISIBLE
|
||||
holder.fileModificationContainer.visibility = View.VISIBLE
|
||||
} ?: run {
|
||||
holder.fileModification.visibility = View.GONE
|
||||
holder.fileModificationContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
// Size
|
||||
@@ -234,7 +234,7 @@ class FileDatabaseHistoryAdapter(context: Context)
|
||||
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)
|
||||
|
||||
@@ -250,6 +250,7 @@ class FileDatabaseHistoryAdapter(context: Context)
|
||||
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
|
||||
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
|
||||
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 fileSize: TextView = itemView.findViewById(R.id.file_size)
|
||||
}
|
||||
|
||||
@@ -338,6 +338,9 @@ class NodeAdapter (private val context: Context)
|
||||
}
|
||||
}
|
||||
|
||||
holder.attachmentIcon?.visibility =
|
||||
if (entry.containsAttachment()) View.VISIBLE else View.GONE
|
||||
|
||||
mDatabase.stopManageEntry(entry)
|
||||
}
|
||||
|
||||
@@ -391,6 +394,7 @@ class NodeAdapter (private val context: Context)
|
||||
var text: TextView = itemView.findViewById(R.id.node_text)
|
||||
var subText: TextView = itemView.findViewById(R.id.node_subtext)
|
||||
var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers)
|
||||
var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -119,15 +119,19 @@ class FileDatabaseHistoryAction(private val applicationContext: Context) {
|
||||
IOActionTask(
|
||||
{
|
||||
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(
|
||||
databaseUri.toString(),
|
||||
databaseFileToAddOrUpdate.databaseAlias ?: "",
|
||||
databaseFileToAddOrUpdate.databaseAlias
|
||||
?: fileDatabaseHistoryRetrieve?.databaseAlias
|
||||
?: "",
|
||||
databaseFileToAddOrUpdate.keyFileUri?.toString(),
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val fileDatabaseHistoryRetrieve = databaseFileHistoryDao.getByDatabaseUri(fileDatabaseHistory.databaseUri)
|
||||
|
||||
// Update values if history element not yet in the database
|
||||
if (fileDatabaseHistoryRetrieve == null) {
|
||||
databaseFileHistoryDao.add(fileDatabaseHistory)
|
||||
|
||||
@@ -34,7 +34,7 @@ class DeleteEntryHistoryDatabaseRunnable (
|
||||
|
||||
override fun onStartRun() {
|
||||
try {
|
||||
mainEntry.removeEntryFromHistory(entryHistoryPosition)
|
||||
database.removeEntryHistory(mainEntry, entryHistoryPosition)
|
||||
} catch (e: Exception) {
|
||||
setError(e)
|
||||
}
|
||||
|
||||
@@ -64,6 +64,10 @@ class DeleteNodesRunnable(context: Context,
|
||||
} else {
|
||||
database.deleteEntry(currentNode)
|
||||
}
|
||||
// Remove the oldest attachments
|
||||
currentNode.getAttachments(database.binaryPool).forEach {
|
||||
database.removeAttachmentIfNotUsed(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.action.node
|
||||
|
||||
import android.content.Context
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
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
|
||||
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
|
||||
mOldEntry.updateWith(mNewEntry)
|
||||
mNewEntry.touch(modified = true, touchParents = true)
|
||||
|
||||
// Create an entry history (an entry history don't have history)
|
||||
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
|
||||
database.removeOldestEntryHistory(mOldEntry)
|
||||
database.removeOldestEntryHistory(mOldEntry, database.binaryPool)
|
||||
|
||||
// Only change data in index
|
||||
database.updateEntry(mOldEntry)
|
||||
|
||||
// Remove oldest attachments
|
||||
attachmentsToRemove.forEach {
|
||||
database.removeAttachmentIfNotUsed(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun nodeFinish(): ActionNodesValues {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,7 @@ import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.database.element.database.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
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 java.io.*
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
|
||||
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,
|
||||
newCompression: CompressionAlgorithm) {
|
||||
mDatabaseKDBX?.changeBinaryCompression(oldCompression, newCompression)
|
||||
@@ -268,14 +278,14 @@ class Database {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if RecycleBin is available or not for this version of database
|
||||
* @return true if RecycleBin available
|
||||
* Determine if a configurable RecycleBin is available or not for this version of database
|
||||
* @return true if a configurable RecycleBin available
|
||||
*/
|
||||
val allowRecycleBin: Boolean
|
||||
val allowConfigurableRecycleBin: Boolean
|
||||
get() = mDatabaseKDBX != null
|
||||
|
||||
var isRecycleBinEnabled: Boolean
|
||||
// TODO #394 isRecycleBinEnabled mDatabaseKDB
|
||||
// Backup is always enabled in KDB database
|
||||
get() = mDatabaseKDB != null || mDatabaseKDBX?.isRecycleBinEnabled ?: false
|
||||
set(value) {
|
||||
mDatabaseKDBX?.isRecycleBinEnabled = value
|
||||
@@ -293,12 +303,12 @@ class Database {
|
||||
}
|
||||
|
||||
fun ensureRecycleBinExists(resources: Resources) {
|
||||
mDatabaseKDB?.ensureRecycleBinExists()
|
||||
mDatabaseKDB?.ensureBackupExists()
|
||||
mDatabaseKDBX?.ensureRecycleBinExists(resources)
|
||||
}
|
||||
|
||||
fun removeRecycleBin() {
|
||||
// TODO #394 delete backup mDatabaseKDB?.removeRecycleBin()
|
||||
// Don't allow remove backup in KDB
|
||||
mDatabaseKDBX?.removeRecycleBin()
|
||||
}
|
||||
|
||||
@@ -428,6 +438,37 @@ class Database {
|
||||
}, 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)
|
||||
fun saveData(contentResolver: ContentResolver) {
|
||||
try {
|
||||
@@ -473,7 +514,7 @@ class Database {
|
||||
} else {
|
||||
var outputStream: OutputStream? = null
|
||||
try {
|
||||
outputStream = contentResolver.openOutputStream(uri)
|
||||
outputStream = contentResolver.openOutputStream(uri, "rwt")
|
||||
outputStream?.let { definedOutputStream ->
|
||||
val databaseOutput = mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
|
||||
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, definedOutputStream) }
|
||||
@@ -718,7 +759,7 @@ class Database {
|
||||
fun canRecycle(entry: Entry): Boolean {
|
||||
var canRecycle: Boolean? = null
|
||||
entry.entryKDB?.let {
|
||||
canRecycle = mDatabaseKDB?.canRecycle()
|
||||
canRecycle = mDatabaseKDB?.canRecycle(it)
|
||||
}
|
||||
entry.entryKDBX?.let {
|
||||
canRecycle = mDatabaseKDBX?.canRecycle(it)
|
||||
@@ -729,7 +770,7 @@ class Database {
|
||||
fun canRecycle(group: Group): Boolean {
|
||||
var canRecycle: Boolean? = null
|
||||
group.groupKDB?.let {
|
||||
canRecycle = mDatabaseKDB?.canRecycle()
|
||||
canRecycle = mDatabaseKDB?.canRecycle(it)
|
||||
}
|
||||
group.groupKDBX?.let {
|
||||
canRecycle = mDatabaseKDBX?.canRecycle(it)
|
||||
@@ -800,7 +841,7 @@ class Database {
|
||||
rootGroup?.doForEachChildAndForIt(
|
||||
object : NodeHandler<Entry>() {
|
||||
override fun operate(node: Entry): Boolean {
|
||||
removeOldestEntryHistory(node)
|
||||
removeOldestEntryHistory(node, binaryPool)
|
||||
return true
|
||||
}
|
||||
},
|
||||
@@ -808,34 +849,19 @@ class Database {
|
||||
override fun operate(node: Group): Boolean {
|
||||
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
|
||||
*/
|
||||
fun removeOldestEntryHistory(entry: Entry) {
|
||||
fun removeOldestEntryHistory(entry: Entry, binaryPool: BinaryPool) {
|
||||
mDatabaseKDBX?.let {
|
||||
|
||||
val maxItems = historyMaxItems
|
||||
if (maxItems >= 0) {
|
||||
while (entry.getHistory().size > maxItems) {
|
||||
entry.removeOldestEntryFromHistory()
|
||||
removeOldestEntryHistory(entry)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,11 +870,10 @@ class Database {
|
||||
while (true) {
|
||||
var historySize: Long = 0
|
||||
for (entryHistory in entry.getHistory()) {
|
||||
historySize += entryHistory.getSize()
|
||||
historySize += entryHistory.getSize(binaryPool)
|
||||
}
|
||||
|
||||
if (historySize > maxSize) {
|
||||
entry.removeOldestEntryFromHistory()
|
||||
removeOldestEntryHistory(entry)
|
||||
} else {
|
||||
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) {
|
||||
|
||||
private val TAG = Database::class.java.name
|
||||
|
||||
@@ -21,6 +21,7 @@ package com.kunzisoft.keepass.database.element
|
||||
|
||||
import android.os.Parcel
|
||||
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.entry.EntryKDB
|
||||
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.Type
|
||||
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.Field
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
@@ -317,40 +317,38 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
}
|
||||
}
|
||||
|
||||
fun startToManageFieldReferences(db: DatabaseKDBX) {
|
||||
entryKDBX?.startToManageFieldReferences(db)
|
||||
fun startToManageFieldReferences(database: DatabaseKDBX) {
|
||||
entryKDBX?.startToManageFieldReferences(database)
|
||||
}
|
||||
|
||||
fun stopToManageFieldReferences() {
|
||||
entryKDBX?.stopToManageFieldReferences()
|
||||
}
|
||||
|
||||
fun getAttachments(): ArrayList<EntryAttachment> {
|
||||
val attachments = ArrayList<EntryAttachment>()
|
||||
|
||||
entryKDB?.binaryData?.let { binaryKDB ->
|
||||
attachments.add(EntryAttachment(entryKDB?.binaryDescription ?: "", binaryKDB))
|
||||
fun getAttachments(binaryPool: BinaryPool, inHistory: Boolean = false): List<Attachment> {
|
||||
val attachments = ArrayList<Attachment>()
|
||||
entryKDB?.getAttachment()?.let {
|
||||
attachments.add(it)
|
||||
}
|
||||
|
||||
entryKDBX?.binaries?.let { binariesKDBX ->
|
||||
for ((key, value) in binariesKDBX) {
|
||||
attachments.add(EntryAttachment(key, value))
|
||||
entryKDBX?.getAttachments(binaryPool, inHistory)?.let {
|
||||
attachments.addAll(it)
|
||||
}
|
||||
}
|
||||
|
||||
return attachments
|
||||
}
|
||||
|
||||
fun removeAttachment(attachment: EntryAttachment) {
|
||||
entryKDB?.apply {
|
||||
if (binaryDescription == attachment.name
|
||||
&& binaryData == attachment.binaryAttachment) {
|
||||
binaryDescription = ""
|
||||
binaryData = null
|
||||
}
|
||||
fun containsAttachment(): Boolean {
|
||||
return entryKDB?.containsAttachment() == true
|
||||
|| entryKDBX?.containsAttachment() == true
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -368,20 +366,22 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
}
|
||||
}
|
||||
|
||||
fun removeEntryFromHistory(position: Int) {
|
||||
entryKDBX?.removeEntryFromHistory(position)
|
||||
fun removeEntryFromHistory(position: Int): Entry? {
|
||||
entryKDBX?.removeEntryFromHistory(position)?.let {
|
||||
return Entry(it)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun removeAllHistory() {
|
||||
entryKDBX?.removeAllHistory()
|
||||
fun removeOldestEntryFromHistory(): Entry? {
|
||||
entryKDBX?.removeOldestEntryFromHistory()?.let {
|
||||
return Entry(it)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun removeOldestEntryFromHistory() {
|
||||
entryKDBX?.removeOldestEntryFromHistory()
|
||||
}
|
||||
|
||||
fun getSize(): Long {
|
||||
return entryKDBX?.size ?: 0L
|
||||
fun getSize(binaryPool: BinaryPool): Long {
|
||||
return entryKDBX?.getSize(binaryPool) ?: 0L
|
||||
}
|
||||
|
||||
fun containsCustomData(): Boolean {
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
* 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.Parcelable
|
||||
import com.kunzisoft.keepass.stream.readBytes
|
||||
@@ -30,7 +28,7 @@ import java.util.zip.GZIPOutputStream
|
||||
|
||||
class BinaryAttachment : Parcelable {
|
||||
|
||||
var isCompressed: Boolean? = null
|
||||
var isCompressed: Boolean = false
|
||||
private set
|
||||
var isProtected: Boolean = false
|
||||
private set
|
||||
@@ -46,12 +44,12 @@ class BinaryAttachment : Parcelable {
|
||||
* Empty protected binary
|
||||
*/
|
||||
constructor() {
|
||||
this.isCompressed = null
|
||||
this.isCompressed = false
|
||||
this.isProtected = false
|
||||
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.isProtected = enableProtection
|
||||
this.dataFile = dataFile
|
||||
@@ -59,7 +57,7 @@ class BinaryAttachment : Parcelable {
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
val compressedByte = parcel.readByte().toInt()
|
||||
isCompressed = if (compressedByte == 2) null else compressedByte != 0
|
||||
isCompressed = compressedByte != 0
|
||||
isProtected = parcel.readByte().toInt() != 0
|
||||
parcel.readString()?.let {
|
||||
dataFile = File(it)
|
||||
@@ -74,24 +72,44 @@ 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)
|
||||
fun compress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
||||
dataFile?.let { concreteDataFile ->
|
||||
// To compress, create a new binary with file
|
||||
if (isCompressed != true) {
|
||||
if (!isCompressed) {
|
||||
val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
|
||||
var outputStream: GZIPOutputStream? = null
|
||||
var inputStream: InputStream? = null
|
||||
try {
|
||||
outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress))
|
||||
inputStream = getInputDataStream()
|
||||
GZIPOutputStream(FileOutputStream(fileBinaryCompress)).use { outputStream ->
|
||||
getInputDataStream().use { inputStream ->
|
||||
inputStream.readBytes(bufferSize) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
} finally {
|
||||
inputStream?.close()
|
||||
outputStream?.close()
|
||||
|
||||
}
|
||||
}
|
||||
// Remove unGzip file
|
||||
if (concreteDataFile.delete()) {
|
||||
if (fileBinaryCompress.renameTo(concreteDataFile)) {
|
||||
@@ -102,25 +120,19 @@ class BinaryAttachment : Parcelable {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun decompress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
||||
dataFile?.let { concreteDataFile ->
|
||||
if (isCompressed != false) {
|
||||
if (isCompressed) {
|
||||
val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
|
||||
var outputStream: FileOutputStream? = null
|
||||
var inputStream: GZIPInputStream? = null
|
||||
try {
|
||||
outputStream = FileOutputStream(fileBinaryDecompress)
|
||||
inputStream = GZIPInputStream(getInputDataStream())
|
||||
FileOutputStream(fileBinaryDecompress).use { outputStream ->
|
||||
getUnGzipInputDataStream().use { inputStream ->
|
||||
inputStream.readBytes(bufferSize) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
} finally {
|
||||
inputStream?.close()
|
||||
outputStream?.close()
|
||||
|
||||
}
|
||||
}
|
||||
// Remove gzip file
|
||||
if (concreteDataFile.delete()) {
|
||||
if (fileBinaryDecompress.renameTo(concreteDataFile)) {
|
||||
@@ -131,33 +143,6 @@ class BinaryAttachment : Parcelable {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun download(createdFileUri: Uri,
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun clear() {
|
||||
@@ -185,18 +170,22 @@ class BinaryAttachment : Parcelable {
|
||||
override fun hashCode(): Int {
|
||||
|
||||
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 + dataFile!!.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return dataFile.toString()
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
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.writeString(dataFile?.absolutePath)
|
||||
}
|
||||
@@ -19,52 +19,126 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import android.util.SparseArray
|
||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
||||
import java.io.IOException
|
||||
|
||||
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? {
|
||||
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()) {
|
||||
action.invoke(i, pool.get(pool.keyAt(i)))
|
||||
/**
|
||||
* To put a [binaryAttachment] in the pool,
|
||||
* 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)
|
||||
fun clear() {
|
||||
doForEachBinary { _, binary ->
|
||||
binary.clear()
|
||||
fun remove(binaryAttachment: BinaryAttachment) {
|
||||
findKey(binaryAttachment)?.let {
|
||||
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) {
|
||||
pool.put(findUnusedKey(), fileBinary)
|
||||
}
|
||||
}
|
||||
|
||||
fun findUnusedKey(): Int {
|
||||
var unusedKey = pool.size()
|
||||
while (get(unusedKey) != null)
|
||||
/**
|
||||
* Utility method to find an unused key in the pool
|
||||
*/
|
||||
private fun findUnusedKey(): Int {
|
||||
var unusedKey = 0
|
||||
while (pool[unusedKey] != null)
|
||||
unusedKey++
|
||||
return unusedKey
|
||||
}
|
||||
|
||||
fun findKey(pb: BinaryAttachment): Int? {
|
||||
for (i in 0 until pool.size()) {
|
||||
if (pool.get(pool.keyAt(i)) == pb) return i
|
||||
/**
|
||||
* Return key of [binaryAttachmentToRetrieve] or null if not found
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
@@ -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.node.NodeIdInt
|
||||
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.stream.NullOutputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.DigestOutputStream
|
||||
@@ -38,7 +40,7 @@ import kotlin.collections.ArrayList
|
||||
|
||||
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()
|
||||
|
||||
@@ -57,7 +59,14 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
|
||||
// Retrieve backup group in index
|
||||
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?
|
||||
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
|
||||
*/
|
||||
fun ensureRecycleBinExists() {
|
||||
fun ensureBackupExists() {
|
||||
rootGroups.forEach { currentGroup ->
|
||||
if (currentGroup.level == 0
|
||||
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
|
||||
@@ -219,21 +228,25 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
* @param node Node to remove
|
||||
* @return true if node can be recycle, false elsewhere
|
||||
*/
|
||||
// TODO #394 Backup KDB
|
||||
// fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
|
||||
fun canRecycle(): Boolean {
|
||||
fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
|
||||
if (node == backupGroup)
|
||||
return false
|
||||
backupGroup?.let {
|
||||
if (node.isContainedIn(it))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun recycle(group: GroupKDB) {
|
||||
ensureRecycleBinExists()
|
||||
ensureBackupExists()
|
||||
removeGroupFrom(group, group.parent)
|
||||
addGroupTo(group, backupGroup)
|
||||
group.afterAssignNewParent()
|
||||
}
|
||||
|
||||
fun recycle(entry: EntryKDB) {
|
||||
ensureRecycleBinExists()
|
||||
ensureBackupExists()
|
||||
removeEntryFrom(entry, entry.parent)
|
||||
addEntryTo(entry, backupGroup)
|
||||
entry.afterAssignNewParent()
|
||||
@@ -249,6 +262,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
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 {
|
||||
val TYPE = DatabaseKDB::class.java
|
||||
|
||||
|
||||
@@ -29,8 +29,10 @@ import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||
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.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.entry.EntryKDBX
|
||||
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.MemoryProtectionConfig
|
||||
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_4
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import com.kunzisoft.keepass.utils.VariantDictionary
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.Text
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
@@ -173,33 +177,51 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
|
||||
fun changeBinaryCompression(oldCompression: CompressionAlgorithm,
|
||||
newCompression: CompressionAlgorithm) {
|
||||
binaryPool.doForEachBinary { key, binary ->
|
||||
|
||||
try {
|
||||
when (oldCompression) {
|
||||
CompressionAlgorithm.None -> {
|
||||
when (newCompression) {
|
||||
CompressionAlgorithm.None -> {
|
||||
}
|
||||
CompressionAlgorithm.None -> {}
|
||||
CompressionAlgorithm.GZip -> {
|
||||
// To compress, create a new binary with file
|
||||
binary.compress(BUFFER_SIZE_BYTES)
|
||||
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
|
||||
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) {
|
||||
compressAllBinaries()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 -> {
|
||||
// To decompress, create a new binary with file
|
||||
binary.decompress(BUFFER_SIZE_BYTES)
|
||||
decompressAllBinaries()
|
||||
}
|
||||
CompressionAlgorithm.GZip -> {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
if (password == null)
|
||||
return true
|
||||
|
||||
@@ -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.NodeKDBInterface
|
||||
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 kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Structure containing information about one entry.
|
||||
@@ -135,6 +137,29 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
|
||||
override val type: Type
|
||||
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 {
|
||||
|
||||
/** Size of byte buffer needed to hold this struct. */
|
||||
|
||||
@@ -21,7 +21,9 @@ package com.kunzisoft.keepass.database.element.entry
|
||||
|
||||
import android.os.Parcel
|
||||
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.group.GroupKDBX
|
||||
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.NodeKDBXInterface
|
||||
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.utils.ParcelableUtil
|
||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.collections.LinkedHashMap
|
||||
|
||||
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
|
||||
private var customData = LinkedHashMap<String, String>()
|
||||
// TODO Private
|
||||
var fields = LinkedHashMap<String, ProtectedString>()
|
||||
var binaries = LinkedHashMap<String, BinaryAttachment>()
|
||||
var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId>
|
||||
var foregroundColor = ""
|
||||
var backgroundColor = ""
|
||||
var overrideURL = ""
|
||||
@@ -70,8 +74,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
var additional = ""
|
||||
var tags = ""
|
||||
|
||||
val size: Long
|
||||
get() {
|
||||
fun getSize(binaryPool: BinaryPool): Long {
|
||||
var size = FIXED_LENGTH_SIZE
|
||||
|
||||
for (entry in fields.entries) {
|
||||
@@ -79,10 +82,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
size += entry.value.length().toLong()
|
||||
}
|
||||
|
||||
for ((key, value) in binaries) {
|
||||
size += key.length.toLong()
|
||||
size += value.length()
|
||||
}
|
||||
size += getAttachmentsSize(binaryPool)
|
||||
|
||||
size += autoType.defaultSequence.length.toLong()
|
||||
for ((key, value) in autoType.entrySet()) {
|
||||
@@ -91,7 +91,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
}
|
||||
|
||||
for (entry in history) {
|
||||
size += entry.size
|
||||
size += entry.getSize(binaryPool)
|
||||
}
|
||||
|
||||
size += overrideURL.length.toLong()
|
||||
@@ -110,7 +110,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
||||
customData = ParcelableUtil.readStringParcelableMap(parcel)
|
||||
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
|
||||
binaries = ParcelableUtil.readStringParcelableMap(parcel, BinaryAttachment::class.java)
|
||||
binaries = ParcelableUtil.readStringIntMap(parcel)
|
||||
foregroundColor = parcel.readString() ?: foregroundColor
|
||||
backgroundColor = parcel.readString() ?: backgroundColor
|
||||
overrideURL = parcel.readString() ?: overrideURL
|
||||
@@ -128,7 +128,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
dest.writeParcelable(locationChanged, flags)
|
||||
ParcelableUtil.writeStringParcelableMap(dest, customData)
|
||||
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
|
||||
ParcelableUtil.writeStringParcelableMap(dest, flags, binaries)
|
||||
ParcelableUtil.writeStringIntMap(dest, binaries)
|
||||
dest.writeString(foregroundColor)
|
||||
dest.writeString(backgroundColor)
|
||||
dest.writeString(overrideURL)
|
||||
@@ -167,8 +167,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
tags = source.tags
|
||||
}
|
||||
|
||||
fun startToManageFieldReferences(db: DatabaseKDBX) {
|
||||
this.mDatabase = db
|
||||
fun startToManageFieldReferences(database: DatabaseKDBX) {
|
||||
this.mDatabase = database
|
||||
this.mDecodeRef = true
|
||||
}
|
||||
|
||||
@@ -284,14 +284,46 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
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) {
|
||||
binaries.remove(name)
|
||||
fun containsAttachment(): Boolean {
|
||||
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 {
|
||||
return history.size
|
||||
}
|
||||
@@ -308,15 +340,11 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
history.add(entry)
|
||||
}
|
||||
|
||||
fun removeEntryFromHistory(position: Int) {
|
||||
history.removeAt(position)
|
||||
fun removeEntryFromHistory(position: Int): EntryKDBX? {
|
||||
return history.removeAt(position)
|
||||
}
|
||||
|
||||
fun removeAllHistory() {
|
||||
history.clear()
|
||||
}
|
||||
|
||||
fun removeOldestEntryFromHistory() {
|
||||
fun removeOldestEntryFromHistory(): EntryKDBX? {
|
||||
var min: Date? = null
|
||||
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)
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
||||
override fun touch(modified: Boolean, touchParents: Boolean) {
|
||||
|
||||
@@ -192,10 +192,11 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldID == PwDbHeaderV4Fields.EndOfHeader)
|
||||
return true
|
||||
|
||||
if (fieldData != null)
|
||||
when (fieldID) {
|
||||
PwDbHeaderV4Fields.EndOfHeader -> return true
|
||||
|
||||
PwDbHeaderV4Fields.CipherID -> setCipher(fieldData)
|
||||
|
||||
PwDbHeaderV4Fields.CompressionFlags -> setCompressionFlags(fieldData)
|
||||
|
||||
@@ -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.node.NodeIdInt
|
||||
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.exception.*
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||
import com.kunzisoft.keepass.stream.*
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
import org.joda.time.Instant
|
||||
import java.io.*
|
||||
import java.security.*
|
||||
import java.util.*
|
||||
@@ -282,11 +280,9 @@ class DatabaseInputKDB(cacheDirectory: File,
|
||||
0x000E -> {
|
||||
newEntry?.let { entry ->
|
||||
if (fieldSize > 0) {
|
||||
// Generate an unique new file with timestamp
|
||||
val binaryFile = File(cacheDirectory,
|
||||
Instant.now().millis.toString())
|
||||
entry.binaryData = BinaryAttachment(binaryFile)
|
||||
BufferedOutputStream(FileOutputStream(binaryFile)).use { outputStream ->
|
||||
val binaryAttachment = mDatabaseToOpen.buildNewBinary(cacheDirectory)
|
||||
entry.binaryData = binaryAttachment
|
||||
BufferedOutputStream(binaryAttachment.getOutputDataStream()).use { outputStream ->
|
||||
cipherInputStream.readBytes(fieldSize,
|
||||
DatabaseKDB.BUFFER_SIZE_BYTES) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.kunzisoft.keepass.crypto.StreamCipherFactory
|
||||
import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
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.DatabaseKDBX
|
||||
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.node.NodeIdUUID
|
||||
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.exception.*
|
||||
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.XmlPullParserException
|
||||
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.text.ParseException
|
||||
import java.util.*
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import kotlin.math.min
|
||||
@@ -68,9 +71,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
|
||||
private var hashOfHeader: ByteArray? = null
|
||||
|
||||
private val unusedCacheFileName: String
|
||||
get() = mDatabase.binaryPool.findUnusedKey().toString()
|
||||
|
||||
private var readNextNode = true
|
||||
private val ctxGroups = Stack<GroupKDBX>()
|
||||
private var ctxGroup: GroupKDBX? = null
|
||||
@@ -233,9 +233,11 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
|
||||
var data = ByteArray(0)
|
||||
if (size > 0) {
|
||||
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
|
||||
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) {
|
||||
// TODO OOM here
|
||||
data = dataInputStream.readBytes(size)
|
||||
}
|
||||
}
|
||||
|
||||
var result = true
|
||||
when (fieldId) {
|
||||
@@ -249,18 +251,16 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
header.innerRandomStreamKey = data
|
||||
}
|
||||
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
|
||||
val file = File(cacheDirectory, unusedCacheFileName)
|
||||
FileOutputStream(file).use { outputStream ->
|
||||
val protectedFlag = dataInputStream.readBytes(1)[0].toInt() != 0
|
||||
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 ->
|
||||
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)) {
|
||||
val key = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrId)
|
||||
if (key != null) {
|
||||
val pbData = readBinary(xpp)
|
||||
val id = Integer.parseInt(key)
|
||||
mDatabase.binaryPool.put(id, pbData!!)
|
||||
} else {
|
||||
readUnknown(xpp)
|
||||
}
|
||||
readBinary(xpp)
|
||||
} else {
|
||||
readUnknown(xpp)
|
||||
}
|
||||
@@ -766,8 +759,9 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
|
||||
return KdbContext.Entry
|
||||
} else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
|
||||
if (ctxBinaryName != null && ctxBinaryValue != null)
|
||||
ctxEntry?.putProtectedBinary(ctxBinaryName!!, ctxBinaryValue!!)
|
||||
if (ctxBinaryName != null && ctxBinaryValue != null) {
|
||||
ctxEntry?.putAttachment(Attachment(ctxBinaryName!!, ctxBinaryValue!!), mDatabase.binaryPool)
|
||||
}
|
||||
ctxBinaryName = null
|
||||
ctxBinaryValue = null
|
||||
|
||||
@@ -947,15 +941,28 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
|
||||
// Reference Id to a binary already present in binary pool
|
||||
val ref = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrRef)
|
||||
if (ref != null) {
|
||||
xpp.next() // Consume end tag
|
||||
// New id to a binary
|
||||
val key = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrId)
|
||||
|
||||
return when {
|
||||
ref != null -> {
|
||||
xpp.next() // Consume end tag
|
||||
val id = Integer.parseInt(ref)
|
||||
return mDatabase.binaryPool[id]
|
||||
// A ref is not necessarily an index in Database V3.1
|
||||
mDatabase.binaryPool[id]
|
||||
}
|
||||
key != null -> {
|
||||
createBinary(key.toIntOrNull(), xpp)
|
||||
}
|
||||
else -> {
|
||||
// New binary to retrieve
|
||||
createBinary(null, xpp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New binary to retrieve
|
||||
else {
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun createBinary(binaryId: Int?, xpp: XmlPullParser): BinaryAttachment? {
|
||||
var compressed = false
|
||||
var protected = false
|
||||
|
||||
@@ -973,22 +980,15 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
|
||||
val base64 = readString(xpp)
|
||||
if (base64.isEmpty())
|
||||
return BinaryAttachment()
|
||||
return null
|
||||
val data = Base64.decode(base64, BASE_64_FLAG)
|
||||
|
||||
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 {
|
||||
// Build the new binary and compress
|
||||
val binaryAttachment = mDatabase.buildNewBinary(cacheDirectory, protected, compressed, binaryId)
|
||||
binaryAttachment.getOutputDataStream().use { outputStream ->
|
||||
outputStream.write(data)
|
||||
BinaryAttachment(file, protected, compressed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return binaryAttachment
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
|
||||
@@ -47,20 +47,27 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
|
||||
dataOutputStream.writeInt(streamKeySize)
|
||||
dataOutputStream.write(header.innerRandomStreamKey)
|
||||
|
||||
database.binaryPool.doForEachBinary { _, protectedBinary ->
|
||||
database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
|
||||
val protectedBinary = keyBinary.binary
|
||||
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
|
||||
if (protectedBinary.isProtected) {
|
||||
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
|
||||
}
|
||||
|
||||
// Force decompression to add binary in header
|
||||
protectedBinary.decompress()
|
||||
|
||||
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary.toInt())
|
||||
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify
|
||||
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1)
|
||||
dataOutputStream.write(flag.toInt())
|
||||
|
||||
protectedBinary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
||||
// if was compressed in cache, uncompress it
|
||||
protectedBinary.getInputDataStream().use { inputStream ->
|
||||
inputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
||||
dataOutputStream.write(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader.toInt())
|
||||
dataOutputStream.writeInt(0)
|
||||
|
||||
@@ -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.icon.IconImageCustom
|
||||
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.ProtectedString
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
@@ -55,7 +55,6 @@ import java.io.OutputStream
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherOutputStream
|
||||
@@ -422,7 +421,6 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
private fun writeBinary(binary : BinaryAttachment) {
|
||||
val binaryLength = binary.length()
|
||||
if (binaryLength > 0) {
|
||||
|
||||
if (binary.isProtected) {
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
|
||||
|
||||
@@ -433,21 +431,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
xml.text(charArray, 0, charArray.size)
|
||||
}
|
||||
} else {
|
||||
// Force binary compression from database (compression was harmonized during import)
|
||||
if (mDatabaseKDBX.compressionAlgorithm === CompressionAlgorithm.GZip) {
|
||||
if (binary.isCompressed) {
|
||||
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
|
||||
binaryInputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
||||
binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
|
||||
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
|
||||
xml.text(charArray, 0, charArray.size)
|
||||
}
|
||||
@@ -459,10 +447,11 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
private fun writeMetaBinaries() {
|
||||
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.attribute(null, DatabaseKDBXXML.AttrId, key.toString())
|
||||
writeBinary(binary)
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
|
||||
writeBinary(keyBinary.binary)
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
||||
}
|
||||
|
||||
@@ -559,25 +548,24 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeEntryBinaries(binaries: Map<String, BinaryAttachment>) {
|
||||
for ((key, binary) in binaries) {
|
||||
private fun writeEntryBinaries(binaries: LinkedHashMap<String, Int>) {
|
||||
for ((label, poolId) in binaries) {
|
||||
// Retrieve the right index with the poolId, don't use ref because of header in DatabaseV4
|
||||
mDatabaseKDBX.binaryPool.getBinaryIndexFromKey(poolId)?.toString()?.let { indexString ->
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemKey)
|
||||
xml.text(safeXmlString(key))
|
||||
xml.text(safeXmlString(label))
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemKey)
|
||||
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemValue)
|
||||
val ref = mDatabaseKDBX.binaryPool.findKey(binary)
|
||||
if (ref != null) {
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrRef, ref.toString())
|
||||
} else {
|
||||
writeBinary(binary)
|
||||
}
|
||||
// Use only pool data in Meta to save binaries
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrRef, indexString)
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemValue)
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeDeletedObjects(value: List<DeletedObject>) {
|
||||
|
||||
@@ -95,9 +95,9 @@ open class Education(val activity: Activity) {
|
||||
R.string.education_entry_edit_key,
|
||||
R.string.education_password_generator_key,
|
||||
R.string.education_entry_new_field_key,
|
||||
R.string.education_add_attachment_key,
|
||||
R.string.education_setup_OTP_key)
|
||||
|
||||
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -29,6 +29,10 @@ import com.kunzisoft.keepass.R
|
||||
class EntryEditActivityEducation(activity: Activity)
|
||||
: Education(activity) {
|
||||
|
||||
/**
|
||||
* Check and display learning views
|
||||
* Displays the explanation for the password generator
|
||||
*/
|
||||
fun checkAndPerformedGeneratePasswordEducation(educationView: View,
|
||||
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
|
||||
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
|
||||
@@ -56,7 +60,7 @@ class EntryEditActivityEducation(activity: Activity)
|
||||
|
||||
/**
|
||||
* 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,
|
||||
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
|
||||
@@ -83,6 +87,35 @@ class EntryEditActivityEducation(activity: Activity)
|
||||
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
|
||||
* Displays the explanation to setup OTP
|
||||
|
||||
@@ -21,24 +21,25 @@ package com.kunzisoft.keepass.model
|
||||
|
||||
import android.os.Parcel
|
||||
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.writeEnum
|
||||
|
||||
data class EntryAttachment(var name: String,
|
||||
var binaryAttachment: BinaryAttachment,
|
||||
data class EntryAttachmentState(var attachment: Attachment,
|
||||
var streamDirection: StreamDirection,
|
||||
var downloadState: AttachmentState = AttachmentState.NULL,
|
||||
var downloadProgression: Int = 0) : Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString() ?: "",
|
||||
parcel.readParcelable(BinaryAttachment::class.java.classLoader) ?: BinaryAttachment(),
|
||||
parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryAttachment()),
|
||||
parcel.readEnum<StreamDirection>() ?: StreamDirection.DOWNLOAD,
|
||||
parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL,
|
||||
parcel.readInt())
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(name)
|
||||
parcel.writeParcelable(binaryAttachment, flags)
|
||||
parcel.writeParcelable(attachment, flags)
|
||||
parcel.writeEnum(streamDirection)
|
||||
parcel.writeEnum(downloadState)
|
||||
parcel.writeInt(downloadProgression)
|
||||
}
|
||||
@@ -49,26 +50,23 @@ data class EntryAttachment(var name: String,
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is EntryAttachment) return false
|
||||
if (other !is EntryAttachmentState) return false
|
||||
|
||||
if (name != other.name) return false
|
||||
if (binaryAttachment != other.binaryAttachment) return false
|
||||
if (attachment != other.attachment) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + binaryAttachment.hashCode()
|
||||
return result
|
||||
return attachment.hashCode()
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<EntryAttachment> {
|
||||
override fun createFromParcel(parcel: Parcel): EntryAttachment {
|
||||
return EntryAttachment(parcel)
|
||||
companion object CREATOR : Parcelable.Creator<EntryAttachmentState> {
|
||||
override fun createFromParcel(parcel: Parcel): EntryAttachmentState {
|
||||
return EntryAttachmentState(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<EntryAttachment?> {
|
||||
override fun newArray(size: Int): Array<EntryAttachmentState?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.kunzisoft.keepass.model
|
||||
|
||||
enum class StreamDirection {
|
||||
UPLOAD, DOWNLOAD
|
||||
}
|
||||
@@ -28,17 +28,24 @@ import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
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.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.utils.UriUtil
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.BufferedInputStream
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
|
||||
class AttachmentFileNotificationService: LockNotificationService() {
|
||||
|
||||
override val notificationId: Int = 10000
|
||||
private val attachmentNotificationList = CopyOnWriteArrayList<AttachmentNotification>()
|
||||
|
||||
private var mActionTaskBinder = ActionTaskBinder()
|
||||
private var mActionTaskListeners = LinkedList<ActionTaskListener>()
|
||||
@@ -51,33 +58,31 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
|
||||
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||
mActionTaskListeners.add(actionTaskListener)
|
||||
|
||||
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
|
||||
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)
|
||||
attachmentNotificationList.forEach {
|
||||
it.attachmentFileAction?.listener = attachmentFileActionListener
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
|
||||
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
|
||||
entry.value.attachmentTask?.onUpdate = null
|
||||
attachmentNotificationList.forEach {
|
||||
it.attachmentFileAction?.listener = null
|
||||
}
|
||||
})
|
||||
|
||||
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 {
|
||||
fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment)
|
||||
fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
@@ -87,49 +92,29 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val downloadFileUri: Uri? = if (intent?.hasExtra(DOWNLOAD_FILE_URI_KEY) == true) {
|
||||
intent.getParcelableExtra(DOWNLOAD_FILE_URI_KEY)
|
||||
val downloadFileUri: Uri? = if (intent?.hasExtra(FILE_URI_KEY) == true) {
|
||||
intent.getParcelableExtra(FILE_URI_KEY)
|
||||
} else null
|
||||
|
||||
when(intent?.action) {
|
||||
ACTION_ATTACHMENT_FILE_START_UPLOAD -> {
|
||||
actionUploadOrDownload(downloadFileUri,
|
||||
intent,
|
||||
StreamDirection.UPLOAD)
|
||||
}
|
||||
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
|
||||
if (downloadFileUri != null
|
||||
&& intent.hasExtra(ATTACHMENT_KEY)) {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
actionUploadOrDownload(downloadFileUri,
|
||||
intent,
|
||||
StreamDirection.DOWNLOAD)
|
||||
}
|
||||
else -> {
|
||||
if (downloadFileUri != null) {
|
||||
downloadFileUris[downloadFileUri]?.notificationId?.let {
|
||||
notificationManager?.cancel(it)
|
||||
downloadFileUris.remove(downloadFileUri)
|
||||
attachmentNotificationList.firstOrNull { it.uri == downloadFileUri }?.let { elementToRemove ->
|
||||
notificationManager?.cancel(elementToRemove.notificationId)
|
||||
attachmentNotificationList.remove(elementToRemove)
|
||||
}
|
||||
}
|
||||
if (downloadFileUris.isEmpty()) {
|
||||
if (attachmentNotificationList.isEmpty()) {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
@@ -138,25 +123,35 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun checkCurrentAttachmentProgress() {
|
||||
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
|
||||
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
|
||||
attachmentNotificationList.forEach { attachmentNotification ->
|
||||
mActionTaskListeners.forEach { actionListener ->
|
||||
actionListener.onAttachmentProgress(entry.key, entry.value.entryAttachment)
|
||||
actionListener.onAttachmentAction(
|
||||
attachmentNotification.uri,
|
||||
attachmentNotification.entryAttachmentState
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun newNotification(downloadFileUri: Uri,
|
||||
entryAttachment: EntryAttachment,
|
||||
notificationIdAttachment: Int) {
|
||||
@Synchronized
|
||||
fun removeAttachmentAction(entryAttachment: EntryAttachmentState) {
|
||||
attachmentNotificationList.firstOrNull {
|
||||
it.entryAttachmentState == entryAttachment
|
||||
}?.let {
|
||||
attachmentNotificationList.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun newNotification(attachmentNotification: AttachmentNotification) {
|
||||
|
||||
val pendingContentIntent = PendingIntent.getActivity(this,
|
||||
0,
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
setDataAndType(downloadFileUri, contentResolver.getType(downloadFileUri))
|
||||
setDataAndType(attachmentNotification.uri,
|
||||
contentResolver.getType(attachmentNotification.uri))
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}, PendingIntent.FLAG_CANCEL_CURRENT)
|
||||
|
||||
@@ -164,54 +159,84 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
0,
|
||||
Intent(this, AttachmentFileNotificationService::class.java).apply {
|
||||
// No action to delete the service
|
||||
putExtra(DOWNLOAD_FILE_URI_KEY, downloadFileUri)
|
||||
putExtra(FILE_URI_KEY, attachmentNotification.uri)
|
||||
}, PendingIntent.FLAG_CANCEL_CURRENT)
|
||||
|
||||
val fileName = DocumentFile.fromSingleUri(this, downloadFileUri)?.name ?: ""
|
||||
val fileName = DocumentFile.fromSingleUri(this, attachmentNotification.uri)?.name ?: ""
|
||||
|
||||
val builder = buildNewNotification().apply {
|
||||
when (attachmentNotification.entryAttachmentState.streamDirection) {
|
||||
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)
|
||||
when (entryAttachment.downloadState) {
|
||||
when (attachmentNotification.entryAttachmentState.downloadState) {
|
||||
AttachmentState.NULL, AttachmentState.START -> {
|
||||
setContentText(getString(R.string.download_initialization))
|
||||
setOngoing(true)
|
||||
}
|
||||
AttachmentState.IN_PROGRESS -> {
|
||||
if (entryAttachment.downloadProgression > 100) {
|
||||
if (attachmentNotification.entryAttachmentState.downloadProgression > 100) {
|
||||
setContentText(getString(R.string.download_finalization))
|
||||
} else {
|
||||
setProgress(100, entryAttachment.downloadProgression, false)
|
||||
setContentText(getString(R.string.download_progression, entryAttachment.downloadProgression))
|
||||
setProgress(100,
|
||||
attachmentNotification.entryAttachmentState.downloadProgression,
|
||||
false)
|
||||
setContentText(getString(R.string.download_progression,
|
||||
attachmentNotification.entryAttachmentState.downloadProgression))
|
||||
}
|
||||
setOngoing(true)
|
||||
}
|
||||
AttachmentState.COMPLETE, AttachmentState.ERROR -> {
|
||||
AttachmentState.COMPLETE -> {
|
||||
setContentText(getString(R.string.download_complete))
|
||||
when (attachmentNotification.entryAttachmentState.streamDirection) {
|
||||
StreamDirection.UPLOAD -> {
|
||||
|
||||
}
|
||||
StreamDirection.DOWNLOAD -> {
|
||||
setContentIntent(pendingContentIntent)
|
||||
}
|
||||
}
|
||||
setDeleteIntent(pendingDeleteIntent)
|
||||
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() {
|
||||
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
|
||||
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
|
||||
entry.value.attachmentTask?.onUpdate = null
|
||||
notificationManager?.cancel(entry.value.notificationId)
|
||||
attachmentNotificationList.forEach { attachmentNotification ->
|
||||
attachmentNotification.attachmentFileAction?.listener = null
|
||||
notificationManager?.cancel(attachmentNotification.notificationId)
|
||||
}
|
||||
})
|
||||
attachmentNotificationList.clear()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private data class AttachmentNotification(var notificationId: Int,
|
||||
var entryAttachment: EntryAttachment,
|
||||
var attachmentTask: AttachmentFileActionClass? = null) {
|
||||
private data class AttachmentNotification(var uri: Uri,
|
||||
var notificationId: Int,
|
||||
var entryAttachmentState: EntryAttachmentState,
|
||||
var attachmentFileAction: AttachmentFileAction? = null) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
@@ -228,52 +253,85 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
}
|
||||
}
|
||||
|
||||
private class AttachmentFileActionClass(
|
||||
private val fileUri: Uri,
|
||||
private fun actionUploadOrDownload(downloadFileUri: 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 contentResolver: ContentResolver) {
|
||||
|
||||
private val updateMinFrequency = 1000
|
||||
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() {
|
||||
TimeoutHelper.temporarilyDisableTimeout()
|
||||
|
||||
// on pre execute
|
||||
attachmentNotification.attachmentTask = this
|
||||
attachmentNotification.entryAttachment.apply {
|
||||
attachmentNotification.attachmentFileAction = this
|
||||
attachmentNotification.entryAttachmentState.apply {
|
||||
downloadState = AttachmentState.START
|
||||
downloadProgression = 0
|
||||
}
|
||||
onUpdate?.invoke(fileUri,
|
||||
attachmentNotification.entryAttachment,
|
||||
attachmentNotification.notificationId)
|
||||
listener?.onUpdate(attachmentNotification)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
// on Progress with thread
|
||||
val asyncResult: Deferred<Boolean> = async {
|
||||
var progressResult = true
|
||||
try {
|
||||
attachmentNotification.entryAttachment.apply {
|
||||
attachmentNotification.entryAttachmentState.apply {
|
||||
downloadState = AttachmentState.IN_PROGRESS
|
||||
binaryAttachment.download(fileUri, contentResolver, 1024) { percent ->
|
||||
// Publish progress
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (previousSaveTime + updateMinFrequency < currentTime) {
|
||||
attachmentNotification.entryAttachment.apply {
|
||||
downloadState = AttachmentState.IN_PROGRESS
|
||||
downloadProgression = percent
|
||||
|
||||
when (streamDirection) {
|
||||
StreamDirection.UPLOAD -> {
|
||||
uploadToDatabase(
|
||||
attachmentNotification.uri,
|
||||
attachment.binaryAttachment,
|
||||
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) {
|
||||
Log.e(TAG, "Unable to upload or download file", e)
|
||||
progressResult = false
|
||||
}
|
||||
progressResult
|
||||
@@ -282,33 +340,95 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
// on post execute
|
||||
withContext(Dispatchers.Main) {
|
||||
val result = asyncResult.await()
|
||||
attachmentNotification.attachmentTask = null
|
||||
attachmentNotification.entryAttachment.apply {
|
||||
attachmentNotification.attachmentFileAction = null
|
||||
attachmentNotification.entryAttachmentState.apply {
|
||||
downloadState = if (result) AttachmentState.COMPLETE else AttachmentState.ERROR
|
||||
downloadProgression = 100
|
||||
}
|
||||
onUpdate?.invoke(fileUri,
|
||||
attachmentNotification.entryAttachment,
|
||||
attachmentNotification.notificationId)
|
||||
listener?.onUpdate(attachmentNotification)
|
||||
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
private val TAG = AttachmentFileActionClass::class.java.name
|
||||
private val TAG = AttachmentFileAction::class.java.name
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
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 DOWNLOAD_FILE_URI_KEY = "DOWNLOAD_FILE_URI_KEY"
|
||||
const val FILE_URI_KEY = "FILE_URI_KEY"
|
||||
const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
|
||||
|
||||
private val downloadFileUris = HashMap<Uri, AttachmentNotification>()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -150,7 +150,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key))
|
||||
|
||||
// Recycle bin
|
||||
if (mDatabase.allowRecycleBin) {
|
||||
if (mDatabase.allowConfigurableRecycleBin) {
|
||||
val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key))
|
||||
recycleBinEnablePref?.apply {
|
||||
isChecked = mDatabase.isRecycleBinEnabled
|
||||
|
||||
@@ -231,12 +231,6 @@ object PreferencesUtil {
|
||||
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 {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs.getString(context.getString(R.string.sort_node_key),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,9 +28,12 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
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.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
|
||||
|
||||
class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
||||
|
||||
@@ -43,8 +46,18 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
||||
private var mServiceConnection: ServiceConnection? = null
|
||||
|
||||
private val mActionTaskListener = object: AttachmentFileNotificationService.ActionTaskListener {
|
||||
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
|
||||
onActionTaskListener?.onAttachmentProgress(fileUri, attachment)
|
||||
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
|
||||
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
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun consummeAttachmentAction(attachment: EntryAttachmentState) {
|
||||
mBinder?.getService()?.removeAttachmentAction(attachment)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun start(bundle: Bundle? = null, actionTask: String) {
|
||||
activity.stopService(mIntentTask)
|
||||
if (bundle != null)
|
||||
mIntentTask.putExtras(bundle)
|
||||
activity.runOnUiThread {
|
||||
mIntentTask.action = actionTask
|
||||
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,
|
||||
entryAttachment: EntryAttachment) {
|
||||
attachment: Attachment) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(AttachmentFileNotificationService.DOWNLOAD_FILE_URI_KEY, downloadFileUri)
|
||||
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, entryAttachment)
|
||||
putParcelable(AttachmentFileNotificationService.FILE_URI_KEY, downloadFileUri)
|
||||
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
|
||||
}, ACTION_ATTACHMENT_FILE_START_DOWNLOAD)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
fun <V : Parcelable> readStringParcelableMap(
|
||||
parcel: Parcel, vClass: Class<V>): LinkedHashMap<String, V> {
|
||||
@@ -74,6 +83,19 @@ object ParcelableUtil {
|
||||
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
|
||||
fun writeStringParcelableMap(dest: Parcel, map: LinkedHashMap<String, String>) {
|
||||
|
||||
@@ -27,10 +27,7 @@ import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.kunzisoft.keepass.R
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
import java.io.*
|
||||
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)
|
||||
fun getUriInputStream(contentResolver: ContentResolver, fileUri: Uri?): InputStream? {
|
||||
if (fileUri == null)
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
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
|
||||
@@ -28,7 +27,6 @@ import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.database.element.DateInstant
|
||||
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.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.OtpType
|
||||
import java.util.*
|
||||
@@ -52,27 +52,14 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
|
||||
private var fontInVisibility: Boolean = false
|
||||
|
||||
private val userNameContainerView: View
|
||||
private val userNameView: TextView
|
||||
private val userNameActionView: ImageView
|
||||
|
||||
private val passwordContainerView: View
|
||||
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 val userNameFieldView: EntryField
|
||||
private val passwordFieldView: EntryField
|
||||
private val otpFieldView: EntryField
|
||||
private val urlFieldView: EntryField
|
||||
private val notesFieldView: EntryField
|
||||
|
||||
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 extraFieldsListView: ViewGroup
|
||||
|
||||
@@ -84,7 +71,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
|
||||
private val attachmentsContainerView: View
|
||||
private val attachmentsListView: RecyclerView
|
||||
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context, false)
|
||||
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
|
||||
|
||||
private val historyContainerView: View
|
||||
private val historyListView: RecyclerView
|
||||
@@ -93,34 +80,26 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
private val uuidView: TextView
|
||||
private val uuidReferenceView: TextView
|
||||
|
||||
val isUserNamePresent: Boolean
|
||||
get() = userNameContainerView.visibility == View.VISIBLE
|
||||
|
||||
val isPasswordPresent: Boolean
|
||||
get() = passwordContainerView.visibility == View.VISIBLE
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_entry_contents, this)
|
||||
|
||||
userNameContainerView = findViewById(R.id.entry_user_name_container)
|
||||
userNameView = findViewById(R.id.entry_user_name)
|
||||
userNameActionView = findViewById(R.id.entry_user_name_action_image)
|
||||
userNameFieldView = findViewById(R.id.entry_user_name_field)
|
||||
userNameFieldView.setLabel(R.string.entry_user_name)
|
||||
|
||||
passwordContainerView = findViewById(R.id.entry_password_container)
|
||||
passwordView = findViewById(R.id.entry_password)
|
||||
passwordActionView = findViewById(R.id.entry_password_action_image)
|
||||
passwordFieldView = findViewById(R.id.entry_password_field)
|
||||
passwordFieldView.setLabel(R.string.entry_password)
|
||||
|
||||
otpContainerView = findViewById(R.id.entry_otp_container)
|
||||
otpLabelView = findViewById(R.id.entry_otp_label)
|
||||
otpView = findViewById(R.id.entry_otp)
|
||||
otpActionView = findViewById(R.id.entry_otp_action_image)
|
||||
otpFieldView = findViewById(R.id.entry_otp_field)
|
||||
otpFieldView.setLabel(R.string.entry_otp)
|
||||
|
||||
urlContainerView = findViewById(R.id.entry_url_container)
|
||||
urlView = findViewById(R.id.entry_url)
|
||||
urlFieldView = findViewById(R.id.entry_url_field)
|
||||
urlFieldView.setLabel(R.string.entry_url)
|
||||
urlFieldView.setValueAutoLink(true)
|
||||
|
||||
notesContainerView = findViewById(R.id.entry_notes_container)
|
||||
notesView = findViewById(R.id.entry_notes)
|
||||
notesFieldView = findViewById(R.id.entry_notes_field)
|
||||
notesFieldView.setLabel(R.string.entry_notes)
|
||||
notesFieldView.setValueAutoLink(true)
|
||||
|
||||
extraFieldsContainerView = findViewById(R.id.extra_fields_container)
|
||||
extraFieldsListView = findViewById(R.id.extra_fields_list)
|
||||
@@ -128,7 +107,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
attachmentsContainerView = findViewById(R.id.entry_attachments_container)
|
||||
attachmentsListView = findViewById(R.id.entry_attachments_list)
|
||||
attachmentsListView?.apply {
|
||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
|
||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
adapter = attachmentsAdapter
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
}
|
||||
@@ -154,86 +133,52 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
this.fontInVisibility = fontInVisibility
|
||||
}
|
||||
|
||||
fun assignUserName(userName: String?) {
|
||||
fun assignUserName(userName: String?,
|
||||
onClickListener: OnClickListener?) {
|
||||
userNameFieldView.apply {
|
||||
if (userName != null && userName.isNotEmpty()) {
|
||||
userNameContainerView.visibility = View.VISIBLE
|
||||
userNameView.apply {
|
||||
text = userName
|
||||
if (fontInVisibility)
|
||||
applyFontVisibility()
|
||||
}
|
||||
visibility = View.VISIBLE
|
||||
setValue(userName)
|
||||
applyFontVisibility(fontInVisibility)
|
||||
} 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
|
||||
}
|
||||
assignCopyButtonClickListener(onClickListener)
|
||||
}
|
||||
}
|
||||
|
||||
fun atLeastOneFieldProtectedPresent(): Boolean {
|
||||
extraFieldsListView.let {
|
||||
for (i in 0 until it.childCount) {
|
||||
val childCustomView = it.getChildAt(i)
|
||||
if (childCustomView is EntryExtraField)
|
||||
if (childCustomView.isProtected)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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)
|
||||
fun assignPassword(password: String?,
|
||||
allowCopyPassword: Boolean,
|
||||
onClickListener: OnClickListener?) {
|
||||
passwordFieldView.apply {
|
||||
if (password != null && password.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
setValue(password, true)
|
||||
applyFontVisibility(fontInVisibility)
|
||||
activateCopyButton(allowCopyPassword)
|
||||
}else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
assignCopyButtonClickListener(onClickListener)
|
||||
}
|
||||
}
|
||||
|
||||
fun assignOtp(otpElement: OtpElement?,
|
||||
otpProgressView: ProgressBar?,
|
||||
onClickListener: OnClickListener) {
|
||||
otpContainerView.removeCallbacks(otpRunnable)
|
||||
otpFieldView.removeCallbacks(otpRunnable)
|
||||
|
||||
if (otpElement != null) {
|
||||
otpContainerView.visibility = View.VISIBLE
|
||||
otpFieldView.visibility = View.VISIBLE
|
||||
|
||||
if (otpElement.token.isEmpty()) {
|
||||
otpView.text = context.getString(R.string.error_invalid_OTP)
|
||||
otpActionView.setColorFilter(ContextCompat.getColor(context, R.color.grey_dark))
|
||||
assignOtpCopyListener(null)
|
||||
otpFieldView.setValue(R.string.error_invalid_OTP)
|
||||
otpFieldView.activateCopyButton(false)
|
||||
otpFieldView.assignCopyButtonClickListener(null)
|
||||
} else {
|
||||
assignOtpCopyListener(onClickListener)
|
||||
otpView.text = otpElement.token
|
||||
otpLabelView.text = otpElement.type.name
|
||||
otpFieldView.setLabel(otpElement.type.name)
|
||||
otpFieldView.setValue(otpElement.token)
|
||||
otpFieldView.assignCopyButtonClickListener(onClickListener)
|
||||
|
||||
when (otpElement.type) {
|
||||
// Only add token if HOTP
|
||||
@@ -249,47 +194,41 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
otpRunnable = Runnable {
|
||||
if (otpElement.shouldRefreshToken()) {
|
||||
otpView.text = otpElement.token
|
||||
otpFieldView.setValue(otpElement.token)
|
||||
}
|
||||
otpProgressView?.progress = otpElement.secondsRemaining
|
||||
otpContainerView.postDelayed(otpRunnable, 1000)
|
||||
otpFieldView.postDelayed(otpRunnable, 1000)
|
||||
}
|
||||
otpContainerView.post(otpRunnable)
|
||||
otpFieldView.post(otpRunnable)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
otpContainerView.visibility = View.GONE
|
||||
otpFieldView.visibility = View.GONE
|
||||
otpProgressView?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
fun assignOtpCopyListener(onClickListener: OnClickListener?) {
|
||||
otpActionView.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
fun assignURL(url: String?) {
|
||||
urlFieldView.apply {
|
||||
if (url != null && url.isNotEmpty()) {
|
||||
urlContainerView.visibility = View.VISIBLE
|
||||
urlView.text = url
|
||||
visibility = View.VISIBLE
|
||||
setValue(url)
|
||||
} else {
|
||||
urlContainerView.visibility = View.GONE
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun assignComment(comment: String?) {
|
||||
if (comment != null && comment.isNotEmpty()) {
|
||||
notesContainerView.visibility = View.VISIBLE
|
||||
notesView.apply {
|
||||
text = comment
|
||||
if (fontInVisibility)
|
||||
applyFontVisibility()
|
||||
}
|
||||
try {
|
||||
Linkify.addLinks(notesView, Linkify.ALL)
|
||||
} catch (e: Exception) {}
|
||||
fun assignNotes(notes: String?) {
|
||||
notesFieldView.apply {
|
||||
if (notes != null && notes.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
setValue(notes)
|
||||
applyFontVisibility(fontInVisibility)
|
||||
} else {
|
||||
notesContainerView.visibility = View.GONE
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,6 +261,19 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
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
|
||||
* -------------
|
||||
@@ -334,14 +286,15 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
fun addExtraField(title: String,
|
||||
value: ProtectedString,
|
||||
allowCopy: Boolean,
|
||||
onActionClickListener: OnClickListener?) {
|
||||
onCopyButtonClickListener: OnClickListener?) {
|
||||
|
||||
val entryCustomField: EntryExtraField? = EntryExtraField(context, attrs, defStyle)
|
||||
val entryCustomField: EntryField? = EntryField(context, attrs, defStyle)
|
||||
entryCustomField?.apply {
|
||||
setLabel(title)
|
||||
setValue(value.toString(), value.isProtected)
|
||||
activateActionButton(allowCopy)
|
||||
assignActionButtonClickListener(onActionClickListener)
|
||||
setValueAutoLink(true)
|
||||
activateCopyButton(allowCopy)
|
||||
assignCopyButtonClickListener(onCopyButtonClickListener)
|
||||
applyFontVisibility(fontInVisibility)
|
||||
}
|
||||
entryCustomField?.let {
|
||||
@@ -364,17 +317,18 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
fun assignAttachments(attachments: ArrayList<EntryAttachment>,
|
||||
onAttachmentClicked: (attachment: EntryAttachment)->Unit) {
|
||||
fun assignAttachments(attachments: Set<Attachment>,
|
||||
streamDirection: StreamDirection,
|
||||
onAttachmentClicked: (attachment: Attachment)->Unit) {
|
||||
showAttachments(attachments.isNotEmpty())
|
||||
attachmentsAdapter.assignItems(attachments)
|
||||
attachmentsAdapter.assignItems(attachments.map { EntryAttachmentState(it, streamDirection) })
|
||||
attachmentsAdapter.onItemClickListener = { item ->
|
||||
onAttachmentClicked.invoke(item)
|
||||
onAttachmentClicked.invoke(item.attachment)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAttachmentDownloadProgress(attachmentToDownload: EntryAttachment) {
|
||||
attachmentsAdapter.updateProgress(attachmentToDownload)
|
||||
fun putAttachment(attachmentToDownload: EntryAttachmentState) {
|
||||
attachmentsAdapter.putItem(attachmentToDownload)
|
||||
}
|
||||
|
||||
/* -------------
|
||||
|
||||
@@ -33,14 +33,16 @@ import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||
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.icon.IconImage
|
||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
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.FocusedEditField
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
import org.joda.time.Duration
|
||||
import org.joda.time.Instant
|
||||
|
||||
@@ -68,7 +70,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
||||
private val attachmentsListView: RecyclerView
|
||||
|
||||
private val extraFieldsAdapter = EntryExtraFieldsItemsAdapter(context)
|
||||
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context, true)
|
||||
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
|
||||
|
||||
private var iconColor: Int = 0
|
||||
private var expiresInstant: DateInstant = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
|
||||
@@ -124,7 +126,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
attachmentsListView?.apply {
|
||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
|
||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
adapter = attachmentsAdapter
|
||||
(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
|
||||
}
|
||||
|
||||
@@ -254,10 +256,13 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
||||
/**
|
||||
* 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
|
||||
// Reinit focused field
|
||||
extraFieldsAdapter.assignItems(fields, focusedExtraField)
|
||||
extraFieldsAdapter.onEditButtonClickListener = onEditButtonClickListener
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,18 +278,72 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
||||
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
|
||||
* -------------
|
||||
*/
|
||||
|
||||
fun assignAttachments(attachments: ArrayList<EntryAttachment>,
|
||||
onDeleteItem: (attachment: EntryAttachment)->Unit) {
|
||||
attachmentsContainerView.visibility = if (attachments.isEmpty()) View.GONE else View.VISIBLE
|
||||
attachmentsAdapter.assignItems(attachments)
|
||||
attachmentsAdapter.onDeleteButtonClickListener = { item ->
|
||||
onDeleteItem.invoke(item)
|
||||
fun getAttachments(): List<Attachment> {
|
||||
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
|
||||
attachmentsAdapter.assignItems(attachments.map { EntryAttachmentState(it, streamDirection) })
|
||||
attachmentsAdapter.onDeleteButtonClickListener = { 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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
125
app/src/main/java/com/kunzisoft/keepass/view/EntryField.kt
Normal file
125
app/src/main/java/com/kunzisoft/keepass/view/EntryField.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -44,12 +44,14 @@ fun TextView.applyFontVisibility() {
|
||||
typeface = typeFace
|
||||
}
|
||||
|
||||
fun TextView.applyHiddenStyle(hide: Boolean) {
|
||||
fun TextView.applyHiddenStyle(hide: Boolean, changeMaxLines: Boolean = true) {
|
||||
if (hide) {
|
||||
transformationMethod = PasswordTransformationMethod.getInstance()
|
||||
if (changeMaxLines)
|
||||
maxLines = 1
|
||||
} else {
|
||||
transformationMethod = null
|
||||
if (changeMaxLines)
|
||||
maxLines = 800
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,12 @@ class FileDatabaseInfo : Serializable {
|
||||
|
||||
fun getModificationString(): String? {
|
||||
return documentFile?.lastModified()?.let {
|
||||
if (it != 0L) {
|
||||
DateFormat.getDateTimeInstance()
|
||||
.format(Date(it))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +78,6 @@ class FileDatabaseInfo : Serializable {
|
||||
fun retrieveDatabaseAlias(alias: String): String? {
|
||||
return when {
|
||||
alias.isNotEmpty() -> alias
|
||||
PreferencesUtil.isFullFilePathEnable(context) -> fileUri?.path
|
||||
else -> if (exists) documentFile?.name else fileUri?.path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<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>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:drawable="@drawable/ic_arrow_up_white_24dp"
|
||||
android:fromDegrees="180"
|
||||
android:toDegrees="180"
|
||||
android:visible="true" />
|
||||
@@ -1,9 +1,5 @@
|
||||
<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="#FFFFFFFF"
|
||||
android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z"/>
|
||||
</vector>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:drawable="@drawable/ic_arrow_left_white_24dp"
|
||||
android:fromDegrees="180"
|
||||
android:toDegrees="180"
|
||||
android:visible="true" />
|
||||
7
app/src/main/res/drawable/ic_file_stream_white_24dp.xml
Normal file
7
app/src/main/res/drawable/ic_file_stream_white_24dp.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/ic_file_upload_white_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_file_upload_white_24dp.xml
Normal 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" />
|
||||
9
app/src/main/res/drawable/ic_info_hint_color_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_info_hint_color_24dp.xml
Normal 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>
|
||||
10
app/src/main/res/drawable/ic_more_white_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_more_white_24dp.xml
Normal 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>
|
||||
7
app/src/main/res/drawable/ic_visibility_state.xml
Normal file
7
app/src/main/res/drawable/ic_visibility_state.xml
Normal 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>
|
||||
@@ -69,9 +69,6 @@
|
||||
android:id="@+id/entry_edit_bottom_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:theme="@style/Widget.MaterialComponents.BottomAppBar"
|
||||
android:backgroundTint="?attr/colorAccent"
|
||||
app:backgroundTint="?attr/colorAccent"
|
||||
app:fabAlignmentMode="center"
|
||||
app:hideOnScroll="false"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
|
||||
@@ -124,26 +124,13 @@
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:theme="?attr/toolbarAppearance"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="@drawable/ic_info_white_24dp"
|
||||
app:navigationContentDescription="@string/about"
|
||||
android:elevation="4dp"
|
||||
app:layout_collapseMode="pin"
|
||||
android:theme="?attr/toolbarHomeAppearance"
|
||||
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.AppBarLayout>
|
||||
|
||||
@@ -156,8 +156,7 @@
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:theme="?attr/actionToolbarAppearance"
|
||||
android:background="?attr/colorAccent" />
|
||||
style="?attr/actionToolbarAppearance" />
|
||||
|
||||
<include
|
||||
layout="@layout/view_button_lock"
|
||||
|
||||
@@ -34,9 +34,11 @@
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/entry_custom_field_label_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
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
|
||||
android:id="@+id/entry_custom_field_label"
|
||||
@@ -49,6 +51,16 @@
|
||||
android:maxLines="1" />
|
||||
</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
|
||||
android:id="@+id/entry_custom_field_protection"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Small"
|
||||
|
||||
@@ -47,5 +47,7 @@
|
||||
android:scrollbars="vertical"
|
||||
android:layout_width="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>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:targetApi="p"
|
||||
android:id="@+id/item_attachment_container"
|
||||
android:focusable="false"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
@@ -31,7 +32,6 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
|
||||
@@ -70,40 +70,44 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp">
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/item_attachment_delete_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/content_description_remove_field"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_content_delete_white_24dp"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple" />
|
||||
<FrameLayout
|
||||
android:id="@+id/item_attachment_progress_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:layout_gravity="center">
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/item_attachment_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_file_download_white_24dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_file_stream_white_24dp"
|
||||
android:contentDescription="@string/download"
|
||||
android:tint="?attr/colorAccent" />
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple" />
|
||||
<ProgressBar
|
||||
android:id="@+id/item_attachment_progress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
style="@style/KeepassDXStyle.ProgressBar.Circle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:max="100"
|
||||
android:progress="60" />
|
||||
</FrameLayout>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="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
|
||||
android:id="@+id/entry_extra_field_value"
|
||||
@@ -46,14 +46,15 @@
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<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_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:src="@drawable/ic_content_delete_white_24dp"
|
||||
android:contentDescription="@string/menu_delete"
|
||||
android:src="@drawable/ic_more_white_24dp"
|
||||
android:contentDescription="@string/menu_edit"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -24,26 +24,34 @@
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:id="@+id/entry_field_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/action_image"
|
||||
app:layout_constraintEnd_toStartOf="@+id/entry_field_show"
|
||||
tools:text="title"
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
|
||||
<TextView
|
||||
android:id="@+id/value"
|
||||
android:id="@+id/entry_field_value"
|
||||
android:layout_width="0dp"
|
||||
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_constraintEnd_toStartOf="@+id/action_image"
|
||||
android:textIsSelectable="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/entry_field_show"
|
||||
tools:text="value"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
|
||||
<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_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
@@ -176,38 +176,49 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/file_precise_info_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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_height="match_parent"
|
||||
tools:text="3:40:14 PM"
|
||||
android:textColor="?android:attr/textColorHintInverse"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/default_margin"
|
||||
android:paddingLeft="@dimen/default_margin"
|
||||
android:paddingEnd="@dimen/default_margin"
|
||||
android:paddingRight="@dimen/default_margin"
|
||||
android:paddingBottom="12dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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
|
||||
android:id="@+id/file_size"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="end|bottom"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end|center_vertical"
|
||||
android:textColor="?android:attr/textColorHintInverse"
|
||||
android:paddingBottom="12dp"
|
||||
app:layout_constraintTop_toTopOf="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"
|
||||
android:layout_marginStart="@dimen/default_margin"
|
||||
android:layout_marginEnd="@dimen/default_margin"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.Selectable.Item">
|
||||
<RelativeLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
@@ -46,20 +46,26 @@
|
||||
android:layout_marginEnd="@dimen/image_list_margin"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_blank_32dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent" />
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_toRightOf="@+id/node_icon"
|
||||
android:layout_toEndOf="@+id/node_icon">
|
||||
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"
|
||||
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
|
||||
android:id="@+id/node_text"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -78,5 +84,19 @@
|
||||
android:singleLine="true"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle" />
|
||||
</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>
|
||||
35
app/src/main/res/layout/view_edit_text_visibility.xml
Normal file
35
app/src/main/res/layout/view_edit_text_visibility.xml
Normal 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>
|
||||
@@ -40,153 +40,41 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Username -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/entry_user_name_container"
|
||||
<com.kunzisoft.keepass.view.EntryField
|
||||
android:id="@+id/entry_user_name_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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>
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Password -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/entry_password_container"
|
||||
<com.kunzisoft.keepass.view.EntryField
|
||||
android:id="@+id/entry_password_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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>
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- OTP -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/entry_otp_container"
|
||||
<com.kunzisoft.keepass.view.EntryField
|
||||
android:id="@+id/entry_otp_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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>
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- URL -->
|
||||
<LinearLayout
|
||||
android:id="@+id/entry_url_container"
|
||||
<com.kunzisoft.keepass.view.EntryField
|
||||
android:id="@+id/entry_url_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
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>
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Comment -->
|
||||
<LinearLayout
|
||||
android:id="@+id/entry_notes_container"
|
||||
<!-- Notes -->
|
||||
<com.kunzisoft.keepass.view.EntryField
|
||||
android:id="@+id/entry_notes_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
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>
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
||||
@@ -190,7 +190,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:maxLines="20"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textMultiLine"
|
||||
@@ -214,6 +213,7 @@
|
||||
android:id="@+id/extra_fields_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:layout_margin="@dimen/card_view_padding"
|
||||
android:orientation="vertical">
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
@@ -247,7 +247,8 @@
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/entry_attachments_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="afterDescendants" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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"
|
||||
android:icon="@drawable/ic_mode_edit_white_24dp"
|
||||
android:title="@string/menu_edit"
|
||||
|
||||
@@ -26,13 +26,11 @@
|
||||
android:title="@string/entry_add_field"
|
||||
android:orderInCategory="92"
|
||||
app:showAsAction="always" />
|
||||
<!--
|
||||
<item android:id="@+id/menu_add_attachment"
|
||||
android:icon="@drawable/ic_attach_file_white_24dp"
|
||||
android:title="@string/entry_add_attachment"
|
||||
android:orderInCategory="93"
|
||||
app:showAsAction="always" />
|
||||
-->
|
||||
<item android:id="@+id/menu_add_otp"
|
||||
android:icon="@drawable/ic_otp_white_24dp"
|
||||
android:title="@string/entry_setup_otp"
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<string name="entry_title">العنوان</string>
|
||||
<string name="entry_url">رابط</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_no_name">ادخل اسمًا.</string>
|
||||
<string name="error_pass_match">كلمتا السر غير متطابقتين.</string>
|
||||
@@ -189,8 +189,6 @@
|
||||
<string name="unavailable_feature_version">نسخة الاندرويد %1$s لا تحقق ادنى متطلبات السنخة %2$s.</string>
|
||||
<string name="file_name">اسم الملف</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="open_biometric_prompt_unlock_database">فحص البصمة</string>
|
||||
<string name="biometric_invalid_key">لا يمكن قراءة مفتاح البصمة.
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="entry_user_name">Usuari</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_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_path">Assegureu-vos que el camí eś correcte.</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="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="full_file_path_enable_title">Ruta del fitxer</string>
|
||||
<string name="assign_master_key">Assigna una contrasenya mestra</string>
|
||||
<string name="path">Camí</string>
|
||||
<string name="file_name">Nom de fitxer</string>
|
||||
|
||||
@@ -59,8 +59,8 @@
|
||||
<string name="entry_user_name">Uživatelské jméno</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_file_not_create">Soubor se nedaří vytvořit:</string>
|
||||
<string name="error_invalid_db">Nelze přečíst databázi.</string>
|
||||
<string name="error_file_not_create">Soubor se nedaří vytvořit</string>
|
||||
<string name="error_invalid_db">Databázi nelze číst.</string>
|
||||
<string name="error_invalid_path">Neplatná cesta.</string>
|
||||
<string name="error_no_name">Vložte jméno.</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_paste">Vložit</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_open_file_read_and_write">Čtení a zápis</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="clipboard">Schránka</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="lock">Zamknout</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_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_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="file_name">Název souboru</string>
|
||||
<string name="path">Cesta</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="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_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>
|
||||
@@ -251,7 +249,7 @@
|
||||
<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="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_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>
|
||||
@@ -269,8 +267,8 @@
|
||||
\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_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_summary">Propojte své heslo a otisk prstu pro rychlé odemykání databáze.</string>
|
||||
<string name="education_biometric_title">Odemknutí databáze biometricky</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_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>
|
||||
@@ -419,7 +417,7 @@
|
||||
<string name="database_custom_color_title">Vlastní barva databáze</string>
|
||||
<string name="compression">Komprese</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="error_save_database">Nebylo možno 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_progression">Probíhá: %1$d%%</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_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="contribution">Příspěvky</string>
|
||||
<string name="feedback">Feedback</string>
|
||||
<string name="auto_focus_search_title">Snadné hledání</string>
|
||||
<string name="auto_focus_search_summary">Při otevření databáze žádat hledání</string>
|
||||
<string name="remember_database_locations_title">Uložit umístění databází</string>
|
||||
<string name="remember_database_locations_summary">Pamatovat si umístění databází</string>
|
||||
<string name="remember_keyfile_locations_title">Uložit 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_database_locations_title">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">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_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>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<string name="entry_user_name">Brugernavn</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_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_path">Sørg for, at stien er korrekt.</string>
|
||||
<string name="error_no_name">Indtast et navn.</string>
|
||||
@@ -231,8 +231,6 @@
|
||||
<string name="path">Sti</string>
|
||||
<string name="assign_master_key">Tildel en hovednøgle</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_summary">Flyt grupper og poster til gruppen \"Papirkurven\" før den slettes</string>
|
||||
<string name="monospace_font_fields_enable_title">Feltskrifttype</string>
|
||||
@@ -268,7 +266,7 @@
|
||||
\nGrupper (~mapper) organiserer poster i databasen.</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_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_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>
|
||||
@@ -419,7 +417,7 @@
|
||||
<string name="database_custom_color_title">Brugerdefineret databasefarve</string>
|
||||
<string name="compression">Komprimering</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="error_save_database">Databasen kunne ikke gemmes.</string>
|
||||
<string name="menu_save_database">Gem database</string>
|
||||
@@ -440,9 +438,9 @@
|
||||
<string name="download_initialization">Initialiserer…</string>
|
||||
<string name="download_progression">I gang: %1$d%%</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_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="contribution">Bidrag</string>
|
||||
<string name="feedback">Tilbagemelding</string>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<string name="entry_user_name">Benutzername</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_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_path">Sicherstellen, dass der Pfad korrekt ist.</string>
|
||||
<string name="error_no_name">Namen eingeben.</string>
|
||||
@@ -170,8 +170,8 @@
|
||||
<string name="menu_appearance_settings">Aussehen</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="clipboard_notifications_title">Zwischenablagenbenachrichtigungen</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_title">Zwischenablage-Benachrichtigung</string>
|
||||
<string name="clipboard_notifications_summary">Benachrichtigungen zur Zwischenablage anzeigen, um beim Betrachten eines Eintrags Felder kopieren zu können</string>
|
||||
<string name="lock_database_screen_off_title">Bildschirmsperre</string>
|
||||
<string name="lock_database_screen_off_summary">Datenbank sperren, wenn der Bildschirm ausgeschaltet wird</string>
|
||||
<string name="create_keepass_file">Neue Datenbank erstellen</string>
|
||||
@@ -198,7 +198,7 @@
|
||||
<string name="key_derivation_function">Schlüsselableitungsfunktion</string>
|
||||
<string name="extended_ASCII">Erweiterte ASCII</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="menu_form_filling_settings">Formularausfüllung</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_access_time">Zugriffsdatum</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_sign_in_prompt">Mit KeePassDX anmelden</string>
|
||||
<string name="set_autofill_service_title">Autofill-Dienstvorgabe festlegen</string>
|
||||
<string name="autofill_explanation_summary">Autofill aktivieren, um automatisch Eingabefelder in anderen Apps auszufüllen</string>
|
||||
<string name="set_autofill_service_title">Standarddienst für automatisches Ausfüllen festlegen</string>
|
||||
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen</string>
|
||||
<string name="clipboard">Zwischenablage</string>
|
||||
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
|
||||
<string name="biometric_delete_all_key_summary">Alle 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="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="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_summary">Verschiebt Gruppen oder Einträge in den Papierkorb, bevor sie gelöscht werden.</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>
|
||||
<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_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_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>
|
||||
@@ -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="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_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_summary">Bedienelemente hervorheben, um die Funktionsweise der App zu lernen</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_sound_title">Hörbare Tastendrücke</string>
|
||||
<string name="selection_mode">Auswahlmodus</string>
|
||||
<string name="remember_database_locations_title">Speicherort der Datenbanken</string>
|
||||
<string name="remember_database_locations_summary">Speicherort der Datenbanken merken</string>
|
||||
<string name="remember_keyfile_locations_title">Speicherort der Schlüsseldateien</string>
|
||||
<string name="remember_keyfile_locations_summary">Speicherort der Schlüssel zu Datenbanken merken</string>
|
||||
<string name="remember_database_locations_title">Datenbank-Speicherorte merken</string>
|
||||
<string name="remember_database_locations_summary">Verfolgt, wo Datenbanken gespeichert sind</string>
|
||||
<string name="remember_keyfile_locations_title">Schlüsseldatei-Speicherorte 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_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</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_add_entry">Eintrag 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="entry_password_generator">Passwort-Generator</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_summary">Anzahl der Einträge in einer Gruppe anzeigen</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="content_description_node_children">Untergeordneter Knotenpunkt</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="compression">Kompression</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="error_save_database">Die Datenbank konnte nicht gespeichert werden.</string>
|
||||
<string name="menu_save_database">Datenbank speichern</string>
|
||||
@@ -456,9 +454,9 @@
|
||||
<string name="download_initialization">Initialisieren…</string>
|
||||
<string name="download_progression">Fortschritt: %1$d%%</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_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_summary">App-Design, das in der App genutzt wird</string>
|
||||
<string-array name="list_style_names">
|
||||
@@ -472,27 +470,27 @@
|
||||
</string-array>
|
||||
<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_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="entry_add_attachment">Anhang hinzufügen</string>
|
||||
<string name="discard">Verwerfen</string>
|
||||
<string name="discard_changes">Änderungen verwerfen\?</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="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="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="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_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">Automatisches Füllen blockieren</string>
|
||||
<string name="autofill_web_domain_blocklist_summary">Sperrliste, die das automatische Einsetzen von Web-Domains verhindert</string>
|
||||
<string name="autofill_web_domain_blocklist_title">Web-Domain-Sperrliste</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Sperrliste, die das automatische Füllen von Apps verhindert</string>
|
||||
<string name="autofill_application_id_blocklist_title">Anwendungs-Sperrliste</string>
|
||||
<string name="autofill_block">Automatisches Ausfüllen sperren</string>
|
||||
<string name="autofill_web_domain_blocklist_summary">Liste der Domains, auf denen ein automatisches Ausfüllen unterbunden wird</string>
|
||||
<string name="autofill_web_domain_blocklist_title">Liste gesperrter Web-Domains</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Liste der Apps, in denen ein automatisches Ausfüllen unterbunden wird</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_title">Subdomain-Suche</string>
|
||||
<string name="error_string_type">Dieser Text stimmt nicht mit dem angeforderten Element überein.</string>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<string name="entry_user_name">Όνομα Χρήστη</string>
|
||||
<string name="error_arc4">Η ροή κρυπτογράφησης Arcfour δεν υποστηρίζεται.</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_path">Βεβαιωθείτε ότι η διαδρομή είναι σωστή.</string>
|
||||
<string name="error_no_name">Εισαγάγετε ένα όνομα.</string>
|
||||
@@ -203,8 +203,6 @@
|
||||
<string name="path">Διαδρομή</string>
|
||||
<string name="assign_master_key">Ορίστε ένα κύριο κλειδί</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_summary">Μετακίνηση ομάδων και καταχωρίσεων στην ομάδα \"Κάδο ανακύκλωσης\" πριν την διαγραφή</string>
|
||||
<string name="monospace_font_fields_enable_title">Γραμματοσειρά πεδίου</string>
|
||||
@@ -418,7 +416,7 @@
|
||||
<string name="database_custom_color_title">Προσαρμοσμένο χρώμα βάσης δεδομένων</string>
|
||||
<string name="compression">Συμπίεση</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="device_keyboard_setting_title">Ρυθμίσεις πληκτρολογίου συσκευής</string>
|
||||
<string name="education_biometric_title">Ξεκλείδωμα Βάσης Δεδομένων με βιομετρικά στοιχεία</string>
|
||||
@@ -442,7 +440,7 @@
|
||||
<string name="download_initialization">Αρχικοποίηση…</string>
|
||||
<string name="download_progression">Σε εξέλιξη: %1$d%%</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_summary">Οι καταχωρίσεις που έχουν λήξει είναι κρυμμένες</string>
|
||||
<string name="show_recent_files_title">Εμφάνιση πρόσφατων αρχείων</string>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<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_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_path">Asegúrese de que la ruta sea correcta.</string>
|
||||
<string name="error_no_name">Proporcione un nombre.</string>
|
||||
@@ -220,8 +220,6 @@
|
||||
<string name="path">Ruta</string>
|
||||
<string name="assign_master_key">Asignar una clave maestra</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_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>
|
||||
@@ -397,7 +395,7 @@
|
||||
<string name="error_create_database">No fue posible crear el archivo de base de datos.</string>
|
||||
<string name="html_about_contribution">Parar lograr <strong>mantener nuestra libertad</strong>, <strong>corregir errores</strong>, <strong>agregar características</strong> y <strong>siempre estar activos</strong>, contamos con tu <strong>contribución</strong>.</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_progression">En progreso: %1$d%%</string>
|
||||
<string name="download_initialization">Inicializando…</string>
|
||||
@@ -407,7 +405,7 @@
|
||||
<string name="autofill_block">Bloquear autocompletado</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="compression_gzip">gzip</string>
|
||||
<string name="compression_gzip">Gzip</string>
|
||||
<string name="compression_none">Ninguna</string>
|
||||
<string name="compression">Compresión</string>
|
||||
<string name="database_default_username_title">Nombre de usuario predeterminado</string>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<string name="entry_user_name">Erabiltzaile izena</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_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_path">Fitxategirako bide baliogabea.</string>
|
||||
<string name="error_no_name">Izen bat behar da.</string>
|
||||
|
||||
261
app/src/main/res/values-fa/strings.xml
Normal file
261
app/src/main/res/values-fa/strings.xml
Normal 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">به منظور <strong>نگاه آزادی ما</strong> ، <strong>نقش های فیکسی</strong> ، <strong>خدمات مضاعف</strong> و <strong> همیشه فعال باشد</strong> ، ما در <strong>مسابق</strong> حساب می </strong>.</string>
|
||||
<string name="html_about_licence">KeePassDX © ٪1 $d Kunzisoft است <strong>منا منبع باز</strong> و <strong>با تبلیغات</strong>.
|
||||
\nاین است که ارائه شده است، تحت <strong>GPLv3</strong>، بدون هیچ گونه گارانتی.</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>
|
||||
@@ -61,7 +61,7 @@
|
||||
<string name="entry_user_name">Käyttäjänimi</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_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_path">Varmista että polku on oikein.</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="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="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="path">Polku</string>
|
||||
<string name="file_name">Tiedostonimi</string>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<string name="entry_user_name">Nom d’utilisateur</string>
|
||||
<string name="error_arc4">Le chiffrement de flux Arcfour n’est pas pris en charge.</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_path">Vérifier la validité du chemin d’accès.</string>
|
||||
<string name="error_no_name">Saisir un nom.</string>
|
||||
@@ -201,8 +201,6 @@
|
||||
<string name="path">Chemin d’accès</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="full_file_path_enable_title">Chemin d’accès du fichier</string>
|
||||
<string name="full_file_path_enable_summary">Affiche le chemin d’accès complet du fichier</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="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="compression">Compression</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 l’appareil</string>
|
||||
<string name="error_save_database">Impossible d’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_progression">En cours : %1$d%%</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_summary">Les entrées expirées sont cachées</string>
|
||||
<string name="contact">Contact</string>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<string name="entry_url">यू.आर.एल</string>
|
||||
<string name="entry_user_name">उपयोगकर्ता का नाम</string>
|
||||
<string name="error_can_not_handle_uri">KeePassDX में इस URI को संभाल नहीं सका।</string>
|
||||
<string name="error_file_not_create">फाइल नहीं बना सका:</string>
|
||||
<string name="error_file_not_create">फाइल नहीं बना सका</string>
|
||||
<string name="error_invalid_db">डाटाबेस नहीं पढ़ सका।</string>
|
||||
<string name="error_invalid_path">सुनिश्चित करें कि रास्ता सही है।</string>
|
||||
<string name="error_no_name">एक नाम दर्ज करें।</string>
|
||||
|
||||
@@ -205,8 +205,6 @@
|
||||
<string name="file_name">Ime datoteke</string>
|
||||
<string name="path">Putanja</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_summary">Komprimiranje podataka smanjuje veličinu baze podataka.</string>
|
||||
<string name="max_history_items_title">Maksimalni broj</string>
|
||||
@@ -233,12 +231,12 @@
|
||||
<string name="other">Ostalo</string>
|
||||
<string name="compression">Komprimiranje</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="content_description_node_children">Pod-čvor</string>
|
||||
<string name="entry_accessed">Pristupljeno</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_path">Provjeri putanju do datoteke.</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_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_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_database_screen_off_title">Zaključavanje ekrana</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_sound_title">Zvuk tipki</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_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>
|
||||
@@ -339,7 +337,7 @@
|
||||
<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_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="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>
|
||||
@@ -350,8 +348,8 @@
|
||||
<string name="discard_changes">Odbaciti promjene\?</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="homepage">Početna stranica</string>
|
||||
<string name="remember_keyfile_locations_title">Spremi 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="remember_keyfile_locations_title">Zapamti mjesto datoteke ključa</string>
|
||||
<string name="unavailable_feature_version">Uređaj koristi Android verziju %1$s, ali potrebna je verzija %2$s ili novija.</string>
|
||||
<string name="autofill_auto_search_summary">Automatski predloži rezultate pretrage od web domene ili ID-a aplikacije</string>
|
||||
<string name="hide_broken_locations_summary">Sakrij pokvarene poveznice u popisu nedavnih baza podataka</string>
|
||||
<string name="html_text_dev_feature">Ova se funkcija nalazi <strong>u razvoju</strong> i treba tvoj <strong>doprinos</strong> 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="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="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="contribution">Doprinos</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="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="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.
|
||||
\n
|
||||
\nKoristi preferirani način ispunjavanja obrazaca.</string>
|
||||
@@ -418,7 +416,7 @@
|
||||
<string name="autofill_block">Blokiranje automatskog ispunjavanja</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="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="open_biometric_prompt_unlock_database">Za otključavanje baze podataka, otvori biometrijsku prijavu</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>
|
||||
<string name="configure_biometric">Biometrijska prijava je podržana, ali nije postavljena.</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="download_finalization">Završavanje …</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="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 <strong>bez oglasa</strong>, <strong>copylefted slobodan softver</strong> i ne prikuplja osobne podatke na svojim poslužiteljima, bez obzira na korištenu verziju.</string>
|
||||
<string name="rounds">Transformacijski prolazi</string>
|
||||
<string name="download_initialization">Inicijaliziranje …</string>
|
||||
@@ -469,7 +467,7 @@
|
||||
\n
|
||||
\nGrupe (~mape) organiziraju unose u bazi podataka.</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_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>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<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_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_path">Győződjön meg róla, hogy az útvonal helyes.</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="path">Útvonal</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_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>
|
||||
@@ -384,7 +382,7 @@
|
||||
<string name="contact">Kapcsolat</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="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_progression">Folyamatban: %1$d%%</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="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="compression_gzip">gzip</string>
|
||||
<string name="compression_gzip">Gzip</string>
|
||||
<string name="compression_none">Nincs</string>
|
||||
<string name="compression">Tömörítés</string>
|
||||
<string name="database_custom_color_title">Egyéni adatbázisszín</string>
|
||||
|
||||
179
app/src/main/res/values-id/strings.xml
Normal file
179
app/src/main/res/values-id/strings.xml
Normal 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 <strong>menjaga kebebasan kami</strong>, <strong>memperbaiki bug</strong>, <strong> menambah fitur</strong> dan <strong>agar selalu aktif</strong>, kami mengandalkan <strong> kontribusi</strong>.</string>
|
||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft <strong> open source </strong> dan <strong> tanpa iklan </strong>.<br>Tersedia apa adanya, di bawah lisensi <strong> GPLv3 </strong>, 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>
|
||||
@@ -37,7 +37,7 @@
|
||||
<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_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="retrieving_db_key">Creazione file chiave database…</string>
|
||||
<string name="database">Banca dati</string>
|
||||
@@ -63,7 +63,7 @@
|
||||
<string name="entry_user_name">Nome utente</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_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_path">Assicurati che il percorso sia corretto.</string>
|
||||
<string name="error_no_name">Inserisci un nome.</string>
|
||||
@@ -228,8 +228,6 @@
|
||||
<string name="path">Percorso</string>
|
||||
<string name="assign_master_key">Assegna una chiave master</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_summary">Sposta i gruppi e le voci nel gruppo \"Cestino\" prima di eliminarlo</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_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_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="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>
|
||||
@@ -448,7 +446,7 @@
|
||||
<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="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_progression">Avanzamento %1$d%%</string>
|
||||
<string name="download_initialization">Inizializzazione…</string>
|
||||
@@ -456,12 +454,12 @@
|
||||
<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_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="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="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">Compressione</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_title">Ricerca per sotto-dominio</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>
|
||||
@@ -60,7 +60,7 @@
|
||||
<string name="entry_user_name">שם משתמש</string>
|
||||
<string name="error_arc4">צופן זרם Arcfour אינו נתמך.</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_path">נתיב לא חוקי.</string>
|
||||
<string name="error_no_name">שם נדרש.</string>
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
<string name="add_group">グループを追加</string>
|
||||
<string name="encryption_algorithm">暗号化アルゴリズム</string>
|
||||
<string name="app_timeout">アプリのタイムアウト</string>
|
||||
<string name="app_timeout_summary">アプリがアイドル状態になってからデータベースをロックするまでの時間</string>
|
||||
<string name="app_timeout_summary">この期間アプリの操作がなかった場合、データベースをロックします</string>
|
||||
<string name="application">アプリ</string>
|
||||
<string name="menu_app_settings">アプリの設定</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_timeout">クリップボードのタイムアウト</string>
|
||||
<string name="clipboard_timeout_summary">クリップボード内での保存期間(デバイスが対応している場合)</string>
|
||||
@@ -39,7 +39,7 @@
|
||||
<string name="decrypting_db">データベースの内容を復号しています…</string>
|
||||
<string name="default_checkbox">デフォルトのデータベースとして使用</string>
|
||||
<string name="digits">数字</string>
|
||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft は<strong>オープンソース</strong>で<strong>広告はありません</strong>。
|
||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft は<strong>オープンソース</strong>/<strong>広告なし</strong>です。
|
||||
\nそのままの状態で、<strong>GPLv3</strong> ライセンスの下、いかなる保証もなく提供されます。</string>
|
||||
<string name="select_database_file">既存のデータベースを開く</string>
|
||||
<string name="entry_accessed">アクセス日時</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="entry_user_name">ユーザー名</string>
|
||||
<string name="error_arc4">Arcfour ストリーム暗号には対応していません。</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_path">パスが正しいことを確認してください。</string>
|
||||
<string name="error_no_name">名前を入力してください。</string>
|
||||
@@ -65,7 +65,7 @@
|
||||
<string name="error_out_of_memory">データベース全体を読み込むメモリがありません。</string>
|
||||
<string name="error_pass_gen_type">少なくとも 1 つのパスワード生成タイプを選択する必要があります。</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="file_browser">ファイル マネージャー</string>
|
||||
<string name="generate_password">パスワードを生成</string>
|
||||
@@ -118,13 +118,13 @@
|
||||
<string name="special">特殊文字</string>
|
||||
<string name="search">検索</string>
|
||||
<string name="encryption_twofish">Twofish</string>
|
||||
<string name="underline">下線</string>
|
||||
<string name="underline">アンダースコア</string>
|
||||
<string name="unsupported_db_version">対応していないバージョンのデータベースです。</string>
|
||||
<string name="uppercase">大文字</string>
|
||||
<string name="version_label">バージョン %1$s</string>
|
||||
<string name="education_unlock_summary">データベースのロックを解除するには、パスワードまたはキーファイル、またはその両方を入力します。
|
||||
\n
|
||||
\nデータベース ファイルは変更するたびに、安全な場所にバックアップしてください。</string>
|
||||
\nデータベース ファイルは変更するたびに安全な場所へバックアップしてください。</string>
|
||||
<string-array name="timeout_options">
|
||||
<item>5秒</item>
|
||||
<item>10秒</item>
|
||||
@@ -168,7 +168,7 @@
|
||||
<string name="menu_move">移動</string>
|
||||
<string name="menu_paste">貼り付け</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_open_file_read_and_write">変更可能</string>
|
||||
<string name="create_keepass_file">新しいデータベースを作成</string>
|
||||
@@ -190,7 +190,7 @@
|
||||
<string name="content_description_add_group">グループを追加</string>
|
||||
<string name="content_description_file_information">ファイル情報</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="entry_add_field">フィールドを追加</string>
|
||||
<string name="content_description_remove_field">フィールドを削除</string>
|
||||
@@ -199,12 +199,12 @@
|
||||
<string name="configure_biometric">生体認証プロンプト対応端末ですが未設定です。</string>
|
||||
<string name="master_key">マスターキー</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_algorithm">アルゴリズム</string>
|
||||
<string name="entry_otp">OTP</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="creating_database">データベースを作成しています…</string>
|
||||
<string name="menu_security_settings">セキュリティの設定</string>
|
||||
@@ -225,7 +225,7 @@
|
||||
<string name="error_create_database">データベース ファイルを作成できません。</string>
|
||||
<string name="error_label_exists">このラベルはすでに存在します。</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="entry_setup_otp">ワンタイム パスワードを設定</string>
|
||||
<string name="entry_attachments">添付ファイル</string>
|
||||
@@ -244,7 +244,7 @@
|
||||
<string name="open_biometric_prompt_store_credential">生体認証プロンプトを開き認証情報を保存します</string>
|
||||
<string name="open_biometric_prompt_unlock_database">生体認証プロンプトを開きロックを解除します</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_database_read_only">データベースの変更を保存するために、ファイル書き込みアクセスを許可します</string>
|
||||
<string name="search_results">検索結果</string>
|
||||
@@ -259,10 +259,10 @@
|
||||
<string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string>
|
||||
<string name="hide_broken_locations_title">データベースへの壊れたリンクを非表示にする</string>
|
||||
<string name="show_recent_files_summary">最近使ったデータベースの場所を表示します</string>
|
||||
<string name="remember_keyfile_locations_summary">データベースのキーファイルの場所を記憶します</string>
|
||||
<string name="remember_keyfile_locations_title">キーファイルの場所を保存</string>
|
||||
<string name="remember_database_locations_summary">データベースの場所を記憶します</string>
|
||||
<string name="remember_database_locations_title">データベースの場所を保存</string>
|
||||
<string name="remember_keyfile_locations_summary">キーファイルの保存先を追跡します</string>
|
||||
<string name="remember_keyfile_locations_title">キーファイルの場所を記憶</string>
|
||||
<string name="remember_database_locations_summary">データベースの保存先を追跡します</string>
|
||||
<string name="remember_database_locations_title">データベースの場所を記憶</string>
|
||||
<string name="selection_mode">選択モード</string>
|
||||
<string name="contains_duplicate_uuid_procedure">重複したエントリーに対する新しい UUID を生成して、問題を解決し続行しますか?</string>
|
||||
<string name="contains_duplicate_uuid">データベースには重複する UUID が含まれています。</string>
|
||||
@@ -272,7 +272,7 @@
|
||||
<string name="error_string_type">このテキストは指定された項目と整合しません。</string>
|
||||
<string name="error_otp_counter">カウンターは %1$d から %2$d の間でなければなりません。</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_update_from_list">更新</string>
|
||||
<string name="entry_add_attachment">添付ファイルを追加</string>
|
||||
@@ -287,21 +287,19 @@
|
||||
<string name="education_generate_password_summary">エントリーに関連付ける強力なパスワードを生成します。フォームの基準に従って定義することは簡単で、安全なパスワードを忘れることはありません。</string>
|
||||
<string name="monospace_font_fields_enable_summary">フィールド内で使用するフォントを変更して、文字を見やすくします</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="education_generate_password_title">強力なパスワードを作成</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_delete_all_key_warning">生体認証に関するすべての暗号化キーを削除しますか?</string>
|
||||
<string name="biometric_delete_all_key_summary">生体認証に関するすべての暗号化キーを削除します</string>
|
||||
<string name="biometric_delete_all_key_warning">生体認証に関するすべての暗号鍵を削除しますか?</string>
|
||||
<string name="biometric_delete_all_key_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_sort_summary">エントリーとグループの並べ替え方法を選択します。</string>
|
||||
<string name="education_search_summary">パスワードを取得するには、タイトル、ユーザー名、または他のフィールドの内容を入力します。</string>
|
||||
<string name="education_new_node_summary">エントリーはデジタル アイデンティティの管理に役立ちます。
|
||||
<string name="education_new_node_summary">エントリーはデジタル ID の管理に役立ちます。
|
||||
\n
|
||||
\nグループ(≒フォルダ)はデータベース内のエントリーを整理します。</string>
|
||||
<string name="education_search_title">エントリーを検索</string>
|
||||
@@ -313,14 +311,14 @@
|
||||
<string name="education_select_database_title">既存のデータペースを開く</string>
|
||||
<string name="list_password_generator_options_title">パスワードの文字種</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="enable_auto_save_database_title">データベースを自動保存</string>
|
||||
<string name="enable_read_only_summary">デフォルトではデータベースを読み取り専用で開きます</string>
|
||||
<string name="enable_read_only_title">書き込み禁止</string>
|
||||
<string name="delete_entered_password_summary">入力されたパスワードをデータベースへの接続試行後に削除します</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">自動入力をブロック</string>
|
||||
<string name="autofill_web_domain_blocklist_title">ウェブドメインのブロックリスト</string>
|
||||
@@ -343,7 +341,7 @@
|
||||
<string name="lock_database_screen_off_title">画面ロック</string>
|
||||
<string name="lock">ロック</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">クリップボード</string>
|
||||
<string name="database_opened">データベースが開かれています</string>
|
||||
@@ -363,9 +361,9 @@
|
||||
<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="biometric_invalid_key">生体鍵が読み取れません。削除して生体認証の手順を繰り返してください。</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">この<strong>ビジュアル スタイル</strong>はあなたの厚意により利用可能となります。</string>
|
||||
<string name="subdomain_search_summary">サブドメインの制約つきでウェブドメインを検索します</string>
|
||||
<string name="lock_database_back_root_summary">ユーザーがルート画面上で戻るボタンをタップしたとき、データベースをロックします</string>
|
||||
@@ -380,7 +378,7 @@
|
||||
<string name="education_biometric_summary">スキャンした生体情報にパスワードをリンクして、データベースのロックをすばやく解除します。</string>
|
||||
<string name="reset_education_screens_text">教育的なヒントをリセットしました</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="keyboard_previous_fill_in_summary">自動キーアクションの実行後、切り替え前のキーボードへ自動的に戻します</string>
|
||||
<string name="keyboard_previous_fill_in_title">自動キーアクション</string>
|
||||
@@ -396,7 +394,7 @@
|
||||
<string name="device_keyboard_setting_title">デバイス キーボードの設定</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">次回マスターキーの変更を必須にします(1 回のみ)</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="download_finalization">終了しています…</string>
|
||||
<string name="download_initialization">初期化しています…</string>
|
||||
@@ -407,7 +405,7 @@
|
||||
<string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string>
|
||||
<string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</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_attachment">%1$s をダウンロード</string>
|
||||
<string name="contribute">貢献</string>
|
||||
@@ -422,7 +420,7 @@
|
||||
<string name="allow_no_password_title">空のマスターキーを許可</string>
|
||||
<string name="autofill_auto_search_title">自動検索</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">圧縮</string>
|
||||
<string name="other">その他</string>
|
||||
@@ -436,13 +434,13 @@
|
||||
<string name="enable">有効にする</string>
|
||||
<string name="allow_copy_password_warning">警告:クリップボードはすべてのアプリで共有されます。機密データがコピーされた場合、他のソフトウェアがデータを復元する可能性があります。</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_field_copy_title">フィールドをコピー</string>
|
||||
<string name="education_read_only_title">データベースの書き込みを禁止</string>
|
||||
<string name="education_entry_new_field_summary">追加フィールドを登録し、値を追加し、必要に応じて保護します。</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_biometric_title">生体認証によるロック解除</string>
|
||||
<string name="education_create_database_summary">最初のパスワード管理ファイルを作成します。</string>
|
||||
@@ -456,8 +454,8 @@
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="list_password_generator_options_summary">パスワード ジェネレータが用いる文字種を設定します</string>
|
||||
<string name="keyboard_previous_database_credentials_title">データベースの認証画面</string>
|
||||
<string name="list_password_generator_options_summary">パスワードの生成に使う文字種を設定します</string>
|
||||
<string name="keyboard_previous_database_credentials_title">データベース認証情報の画面</string>
|
||||
<string name="keyboard_change">キーボードの切り替え</string>
|
||||
<string name="keyboard_keys_category">キー</string>
|
||||
<string name="keyboard_theme_title">キーボードのテーマ</string>
|
||||
@@ -479,7 +477,7 @@
|
||||
<string name="database_data_compression_title">データ圧縮</string>
|
||||
<string name="file_name">ファイル名</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="biometric_unlock_enable_summary">生体情報をスキャンしてデータベースを開くことができるようにします</string>
|
||||
<string name="biometric_unlock_enable_title">生体認証によるロック解除</string>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<string name="entry_user_name">아이디</string>
|
||||
<string name="error_arc4">Arcfour 스트림 암호는 지원되지 않습니다.</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_path">경로가 확실한지 확인하십시오.</string>
|
||||
<string name="error_no_name">이름을 입력하십시오.</string>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<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_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_path">Nederīgs ceļš.</string>
|
||||
<string name="error_no_name">Vajag ievadīt faila nosaukumu</string>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<string name="error_save_database">ഡാറ്റാബേസ് സംരക്ഷിക്കാൻ കഴിഞ്ഞില്ല.</string>
|
||||
<string name="error_pass_match">പാസ്വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല.</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="entry_user_name">ഉപയോക്തൃനാമം</string>
|
||||
<string name="entry_url">URL</string>
|
||||
@@ -154,12 +154,12 @@
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="encryption_twofish">Twofish</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="autofill_auto_search_title">സ്വയം തിരയൽ</string>
|
||||
<string name="keyboard_change">കീബോർഡ് മാറ്റുക</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="max_history_items_title">പരമാവധി നമ്പർ</string>
|
||||
<string name="recycle_bin_title">റീസൈക്കിൾ ബിനിൻ്റെ ഉപയോഗം</string>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<string name="entry_user_name">Brukernavn</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_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_path">Ugyldig sti.</string>
|
||||
<string name="error_no_name">Et navn er påkrevd.</string>
|
||||
@@ -213,8 +213,6 @@
|
||||
<string name="path">Sti</string>
|
||||
<string name="assign_master_key">Tildel en hovednøkkel</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_summary">Flytt en gruppe eller oppføring til \"Papirkurv\" før sletting</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="compression">Komprimering</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="menu_empty_recycle_bin">Tøm papirkurven</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_progression">Underveis: %1$d%%</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="auto_focus_search_title">Hurtigsøk</string>
|
||||
<string name="entry_add_attachment">Legg til vedlegg</string>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<string name="entry_user_name">Gebruikersnaam</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_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_path">Zorg ervoor dat het pad juist is.</string>
|
||||
<string name="error_no_name">Voer een naam in.</string>
|
||||
@@ -238,8 +238,6 @@
|
||||
<string name="path">Pad</string>
|
||||
<string name="assign_master_key">Hoofdsleutel toewijzen</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_summary">Verplaatst groepen en items naar \"Prullenbak\" voordat ze worden verwijderd</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="compression">Compressie</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="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>
|
||||
@@ -433,7 +431,7 @@
|
||||
<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_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_progression">Voortgang: %1$d%%</string>
|
||||
<string name="download_initialization">Initialiseren…</string>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<string name="entry_user_name">Brukaramn</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_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_path">Ugyldig stig.</string>
|
||||
<string name="error_no_name">Treng eit namn.</string>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<string name="icon_pack_choose_title">ਆਈਕਾਨ ਪੈਕ</string>
|
||||
<string name="style_choose_summary">ਐਪ ਵਿੱਚ ਵਰਤਿਆ ਥੀਮ</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_progression">ਜਾਰੀ ਹੈ: %1$d%%</string>
|
||||
<string name="download_initialization">…ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string>
|
||||
@@ -22,7 +22,6 @@
|
||||
<string name="clear_clipboard_notification_title">ਬੰਦ ਕਰਨ ਉੱਤੇ ਸਾਫ਼ ਕਰੋ</string>
|
||||
<string name="disable">ਅਸਮਰੱਥ</string>
|
||||
<string name="enable">ਸਮਰੱਥ</string>
|
||||
<string name="full_file_path_enable_title">ਫ਼ਾਇਲ ਦਾ ਮਾਰਗ</string>
|
||||
<string name="assign_master_key">ਮਾਸਟਰ ਕੁੰਜੀ ਦਿਓ</string>
|
||||
<string name="path">ਮਾਰਗ</string>
|
||||
<string name="file_name">ਫ਼ਾਇਲ ਦਾ ਨਾਂ</string>
|
||||
@@ -156,7 +155,7 @@
|
||||
<string name="error_invalid_OTP">ਗ਼ਲਤ OTP ਭੇਤ ਹੈ।</string>
|
||||
<string name="error_invalid_path">ਪਾਥ ਦੇ ਠੀਕ ਹੋਣ ਨੂੰ ਯਕੀਨੀ ਬਣਾਓ।</string>
|
||||
<string name="error_invalid_db">ਡਾਟਾਬੇਸ ਪੜ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ।</string>
|
||||
<string name="error_file_not_create">ਫ਼ਾਇਲ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ:</string>
|
||||
<string name="error_file_not_create">ਫ਼ਾਇਲ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ</string>
|
||||
<string name="error_can_not_handle_uri">ਇਹ URI KeePassDX ਵਿੱਚ ਹੈਂਡਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।</string>
|
||||
<string name="error_arc4">Arcfour ਸਟਰੀਮ ਸੀਫ਼ਰ ਸਹਾਇਕ ਨਹੀਂ ਹੈ।</string>
|
||||
<string name="entry_user_name">ਵਰਤੋਂਕਾਰ-ਨਾਂ</string>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<string name="entry_user_name">Nazwa użytkownika</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_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_path">Upewnij się, że ścieżka jest prawidłowa.</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_no_encryption_key">Kontynuować bez klucza szyfrowania\?</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="sort_groups_before">Grupy poprzednie</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="clipboard">Schowek</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="lock">Blokada</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_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_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="file_name">Nazwa pliku</string>
|
||||
<string name="path">Ścieżka</string>
|
||||
<string name="assign_master_key">Przypisz klucz główny</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_summary">Przenosi grupy i wpisy do grupy \"Kosz\" przed usunięciem</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_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_summary">Włącz przycisk \"Otwórz\", jeśli nie wybrano uwierzytelnień</string>
|
||||
<string name="allow_no_password_summary">Umożliwia naciśnięcie przycisku \"Otwórz\", jeśli nie wybrano żadnych poświadczeń</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_education_screens_title">Wskazówki edukacyjne</string>
|
||||
@@ -271,7 +269,7 @@
|
||||
\nGrupy (~ foldery) organizują wpisy w bazie danych.</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_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_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>
|
||||
@@ -405,7 +403,7 @@
|
||||
<string name="database_custom_color_title">Niestandardowy kolor bazy danych</string>
|
||||
<string name="compression">Kompresja</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="error_invalid_OTP">Nieprawidłowy klucz tajny OTP.</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_progression">W trakcie realizacji: %1$d%%</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_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="html_about_contribution">Aby <strong>zachować naszą wolność</strong>, <strong>sprawdzać błędy</strong>, <strong>dodać funkcje</strong> i <strong>by być zawsze aktywnym</strong>, liczymy na twój <strong>wkład</strong>.</string>
|
||||
<string name="auto_focus_search_title">Szybkie wyszukiwanie</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_summary">Zapamiętaj lokalizację baz danych</string>
|
||||
<string name="remember_keyfile_locations_title">Zapisz lokalizację plików kluczy</string>
|
||||
<string name="remember_keyfile_locations_summary">Zapamiętaj lokalizację plików kluczy baz danych</string>
|
||||
<string name="remember_database_locations_title">Zapamiętaj lokalizacje baz danych</string>
|
||||
<string name="remember_database_locations_summary">Śledzi, gdzie przechowywane są bazy danych</string>
|
||||
<string name="remember_keyfile_locations_title">Zapamiętaj lokalizacje plików kluczy</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_summary">Pokaż lokalizacje najnowszych baz danych</string>
|
||||
<string name="hide_broken_locations_title">Ukryj uszkodzone łącza do bazy danych</string>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<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_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_path">Certifique-se de que o caminho está correto.</string>
|
||||
<string name="error_no_name">Digite um nome.</string>
|
||||
@@ -229,8 +229,6 @@
|
||||
<string name="path">Caminho</string>
|
||||
<string name="assign_master_key">Defina uma chave mestre</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_summary">Mover grupos e entradas para o grupo \"Lixeira\" antes de apagar</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="compression">Compressão</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="error_save_database">Não foi possível salvar no 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="hide_expired_entries_summary">Entradas expeiradas foram escondidas</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_progression">Em progresso: %1$d%%</string>
|
||||
<string name="download_initialization">Inicializando…</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user