Merge branch 'release/2.8.4'

This commit is contained in:
J-Jamet
2020-09-18 14:05:24 +02:00
77 changed files with 1835 additions and 1054 deletions

View File

@@ -1,3 +1,11 @@
KeePassDX(2.8.4)
* Fix incomplete attachment deletion #684
* Fix opening database v1 without backup folder #692
* Fix ANR during first entry education #685
* Entry edition as fragment and manual views to fix focus #686
* Fix opening database with corrupted attachment #691
* Manage empty keyfile #679
KeePassDX(2.8.3) KeePassDX(2.8.3)
* Upload attachments * Upload attachments
* Visibility button for each hidden field * Visibility button for each hidden field

View File

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

View File

@@ -39,13 +39,13 @@ import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
@@ -309,9 +309,11 @@ class EntryActivity : LockingActivity() {
entryContentsView?.assignNotes(entry.notes) entryContentsView?.assignNotes(entry.notes)
// Assign custom fields // Assign custom fields
if (entry.allowCustomFields()) { if (mDatabase?.allowEntryCustomFields() == true) {
entryContentsView?.clearExtraFields() entryContentsView?.clearExtraFields()
for ((label, value) in entry.customFields) { entry.getExtraFields().forEach { field ->
val label = field.name
val value = field.protectedValue
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
if (allowCopyProtectedField) { if (allowCopyProtectedField) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) { entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
@@ -427,7 +429,7 @@ class EntryActivity : LockingActivity() {
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation, private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
menu: Menu) { menu: Menu) {
val entryFieldCopyView = findViewById<View>(R.id.entry_field_copy) val entryFieldCopyView = entryContentsView?.firstEntryFieldCopyView()
val entryCopyEducationPerformed = entryFieldCopyView != null val entryCopyEducationPerformed = entryFieldCopyView != null
&& entryActivityEducation.checkAndPerformedEntryCopyEducation( && entryActivityEducation.checkAndPerformedEntryCopyEducation(
entryFieldCopyView, entryFieldCopyView,

View File

@@ -35,7 +35,9 @@ import android.widget.TimePicker
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.* import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
@@ -44,12 +46,13 @@ import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.education.EntryEditActivityEducation import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.model.* import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
@@ -60,11 +63,12 @@ import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.EntryEditContentsView import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionError import com.kunzisoft.keepass.view.showActionError
import com.kunzisoft.keepass.view.updateLockPaddingLeft import com.kunzisoft.keepass.view.updateLockPaddingLeft
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class EntryEditActivity : LockingActivity(), class EntryEditActivity : LockingActivity(),
IconPickerDialogFragment.IconPickerListener, IconPickerDialogFragment.IconPickerListener,
@@ -81,24 +85,21 @@ class EntryEditActivity : LockingActivity(),
// Refs of an entry and group in database, are not modifiable // Refs of an entry and group in database, are not modifiable
private var mEntry: Entry? = null private var mEntry: Entry? = null
private var mParent: Group? = null private var mParent: Group? = null
// New or copy of mEntry in the database to be modifiable
private var mNewEntry: Entry? = null
private var mIsNew: Boolean = false private var mIsNew: Boolean = false
// Views // Views
private var coordinatorLayout: CoordinatorLayout? = null private var coordinatorLayout: CoordinatorLayout? = null
private var scrollView: NestedScrollView? = null private var scrollView: NestedScrollView? = null
private var entryEditContentsView: EntryEditContentsView? = null private var entryEditFragment: EntryEditFragment? = null
private var entryEditAddToolBar: Toolbar? = null private var entryEditAddToolBar: Toolbar? = null
private var validateButton: View? = null private var validateButton: View? = null
private var lockView: View? = null private var lockView: View? = null
private var mFocusedEditExtraField: FocusedEditField? = null
// To manage attachments // To manage attachments
private var mSelectFileHelper: SelectFileHelper? = null private var mSelectFileHelper: SelectFileHelper? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAllowMultipleAttachments: Boolean = false private var mAllowMultipleAttachments: Boolean = false
private var mTempAttachments = ArrayList<Attachment>()
// Education // Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null private var entryEditActivityEducation: EntryEditActivityEducation? = null
@@ -118,22 +119,6 @@ class EntryEditActivity : LockingActivity(),
scrollView = findViewById(R.id.entry_edit_scroll) scrollView = findViewById(R.id.entry_edit_scroll)
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
entryEditContentsView = findViewById(R.id.entry_edit_contents)
entryEditContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryEditContentsView?.onDateClickListener = View.OnClickListener {
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
val dateTime = DateTime(expiresDate)
val defaultYear = dateTime.year
val defaultMonth = dateTime.monthOfYear-1
val defaultDay = dateTime.dayOfMonth
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
.show(supportFragmentManager, "DatePickerFragment")
}
}
entryEditContentsView?.entryPasswordGeneratorView?.setOnClickListener {
openPasswordGenerator()
}
lockView = findViewById(R.id.lock_button) lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener { lockView?.setOnClickListener {
lockAndExit() lockAndExit()
@@ -148,6 +133,8 @@ class EntryEditActivity : LockingActivity(),
// Likely the app has been killed exit the activity // Likely the app has been killed exit the activity
mDatabase = Database.getInstance() mDatabase = Database.getInstance()
var tempEntryInfo: EntryInfo? = null
// Entry is retrieve, it's an entry to update // Entry is retrieve, it's an entry to update
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
mIsNew = false mIsNew = false
@@ -163,70 +150,77 @@ class EntryEditActivity : LockingActivity(),
entry.parent = mParent entry.parent = mParent
} }
} }
tempEntryInfo = mEntry?.getEntryInfo(mDatabase, true)
// Create the new entry from the current one
if (savedInstanceState?.containsKey(KEY_NEW_ENTRY) != true) {
mEntry?.let { entry ->
// Create a copy to modify
mNewEntry = Entry(entry).also { newEntry ->
// WARNING Remove the parent to keep memory with parcelable
newEntry.removeParent()
}
}
}
} }
// Parent is retrieve, it's a new entry to create // Parent is retrieve, it's a new entry to create
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
mIsNew = true mIsNew = true
// Create an empty new entry
if (savedInstanceState?.containsKey(KEY_NEW_ENTRY) != true) {
mNewEntry = mDatabase?.createEntry()
}
mParent = mDatabase?.getGroupById(it) mParent = mDatabase?.getGroupById(it)
// Add the default icon from parent if not a folder // Add the default icon from parent if not a folder
val parentIcon = mParent?.icon val parentIcon = mParent?.icon
tempEntryInfo = mDatabase?.createEntry()?.getEntryInfo(mDatabase, true)
// Set default icon
if (parentIcon != null if (parentIcon != null
&& parentIcon.iconId != IconImage.UNKNOWN_ID && parentIcon.iconId != IconImage.UNKNOWN_ID
&& parentIcon.iconId != IconImageStandard.FOLDER) { && parentIcon.iconId != IconImageStandard.FOLDER) {
temporarilySaveAndShowSelectedIcon(parentIcon) tempEntryInfo?.icon = parentIcon
} else {
mDatabase?.drawFactory?.let { iconFactory ->
entryEditContentsView?.setDefaultIcon(iconFactory)
} }
// Set default username
tempEntryInfo?.username = mDatabase?.defaultUsername ?: ""
}
// Build fragment to manage entry modification
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
if (entryEditFragment == null) {
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo)
}
supportFragmentManager.beginTransaction()
.replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG)
.commit()
entryEditFragment?.apply {
drawFactory = mDatabase?.drawFactory
setOnDateClickListener = View.OnClickListener {
expiryTime.date.let { expiresDate ->
val dateTime = DateTime(expiresDate)
val defaultYear = dateTime.year
val defaultMonth = dateTime.monthOfYear-1
val defaultDay = dateTime.dayOfMonth
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
.show(supportFragmentManager, "DatePickerFragment")
}
}
setOnPasswordGeneratorClickListener = View.OnClickListener {
openPasswordGenerator()
}
// Add listener to the icon
setOnIconViewClickListener = View.OnClickListener {
IconPickerDialogFragment.launch(this@EntryEditActivity)
}
setOnRemoveAttachment = { attachment ->
mAttachmentFileBinderManager?.removeBinaryAttachment(attachment)
removeAttachment(EntryAttachmentState(attachment, StreamDirection.DOWNLOAD))
}
setOnEditCustomField = { field ->
editCustomField(field)
} }
} }
// Retrieve the new entry after an orientation change // Retrieve temp attachments in case of deletion
if (savedInstanceState?.containsKey(KEY_NEW_ENTRY) == true) { if (savedInstanceState?.containsKey(TEMP_ATTACHMENTS) == true) {
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY) mTempAttachments = savedInstanceState.getParcelableArrayList(TEMP_ATTACHMENTS) ?: mTempAttachments
} }
if (savedInstanceState?.containsKey(EXTRA_FIELD_FOCUSED_ENTRY) == true) {
mFocusedEditExtraField = savedInstanceState.getParcelable(EXTRA_FIELD_FOCUSED_ENTRY)
}
// Close the activity if entry or parent can't be retrieve
if (mNewEntry == null || mParent == null) {
finish()
return
}
populateViewsWithEntry(mNewEntry!!)
// Assign title // Assign title
title = if (mIsNew) getString(R.string.add_entry) else getString(R.string.edit_entry) title = if (mIsNew) getString(R.string.add_entry) else getString(R.string.edit_entry)
// Add listener to the icon
entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
// Bottom Bar // Bottom Bar
entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar) entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
entryEditAddToolBar?.apply { entryEditAddToolBar?.apply {
menuInflater.inflate(R.menu.entry_edit, menu) menuInflater.inflate(R.menu.entry_edit, menu)
menu.findItem(R.id.menu_add_field).apply { menu.findItem(R.id.menu_add_field).apply {
val allowCustomField = mNewEntry?.allowCustomFields() == true val allowCustomField = mDatabase?.allowEntryCustomFields() == true
isEnabled = allowCustomField isEnabled = allowCustomField
isVisible = allowCustomField isVisible = allowCustomField
} }
@@ -278,10 +272,24 @@ class EntryEditActivity : LockingActivity(),
when (actionTask) { when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK, ACTION_DATABASE_CREATE_ENTRY_TASK,
ACTION_DATABASE_UPDATE_ENTRY_TASK -> { ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
if (result.isSuccess) try {
if (result.isSuccess) {
var newNodes: List<Node> = ArrayList()
result.data?.getBundle(DatabaseTaskNotificationService.NEW_NODES_KEY)?.let { newNodesBundle ->
mDatabase?.let { database ->
newNodes = DatabaseTaskNotificationService.getListNodesFromBundle(database, newNodesBundle)
}
}
if (newNodes.size == 1) {
mEntry = newNodes[0] as Entry?
finish() finish()
} }
} }
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve entry after database action", e)
}
}
}
coordinatorLayout?.showActionError(result) coordinatorLayout?.showActionError(result)
} }
} }
@@ -305,13 +313,12 @@ class EntryEditActivity : LockingActivity(),
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) { override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
when (entryAttachmentState.downloadState) { when (entryAttachmentState.downloadState) {
AttachmentState.START -> { AttachmentState.START -> {
entryEditContentsView?.apply { entryEditFragment?.apply {
// When only one attachment is allowed // When only one attachment is allowed
if (!mAllowMultipleAttachments) { if (!mAllowMultipleAttachments) {
clearAttachments() clearAttachments()
} }
putAttachment(entryAttachmentState) putAttachment(entryAttachmentState)
requestLayout()
// Scroll to the attachment position // Scroll to the attachment position
getAttachmentViewPosition(entryAttachmentState) { getAttachmentViewPosition(entryAttachmentState) {
scrollView?.smoothScrollTo(0, it.toInt()) scrollView?.smoothScrollTo(0, it.toInt())
@@ -319,10 +326,10 @@ class EntryEditActivity : LockingActivity(),
} }
} }
AttachmentState.IN_PROGRESS -> { AttachmentState.IN_PROGRESS -> {
entryEditContentsView?.putAttachment(entryAttachmentState) entryEditFragment?.putAttachment(entryAttachmentState)
} }
AttachmentState.COMPLETE -> { AttachmentState.COMPLETE -> {
entryEditContentsView?.apply { entryEditFragment?.apply {
putAttachment(entryAttachmentState) putAttachment(entryAttachmentState)
// Scroll to the attachment position // Scroll to the attachment position
getAttachmentViewPosition(entryAttachmentState) { getAttachmentViewPosition(entryAttachmentState) {
@@ -331,8 +338,10 @@ class EntryEditActivity : LockingActivity(),
} }
} }
AttachmentState.ERROR -> { AttachmentState.ERROR -> {
mDatabase?.removeAttachmentIfNotUsed(entryAttachmentState.attachment) entryEditFragment?.removeAttachment(entryAttachmentState)
entryEditContentsView?.removeAttachment(entryAttachmentState) coordinatorLayout?.let {
Snackbar.make(it, R.string.error_file_not_create, Snackbar.LENGTH_LONG).asError().show()
}
} }
else -> {} else -> {}
} }
@@ -347,79 +356,6 @@ class EntryEditActivity : LockingActivity(),
super.onPause() super.onPause()
} }
private fun populateViewsWithEntry(newEntry: Entry) {
// Don't start the field reference manager, we want to see the raw ref
mDatabase?.stopManageEntry(newEntry)
// Set info in temp parameters
temporarilySaveAndShowSelectedIcon(newEntry.icon)
// Set info in view
entryEditContentsView?.apply {
title = newEntry.title
username = if (mIsNew && newEntry.username.isEmpty())
mDatabase?.defaultUsername ?: ""
else
newEntry.username
url = newEntry.url
password = newEntry.password
expires = newEntry.expires
if (expires)
expiresDate = newEntry.expiryTime
notes = newEntry.notes
assignExtraFields(newEntry.customFields.mapTo(ArrayList()) {
Field(it.key, it.value)
}, {
editCustomField(it)
}, mFocusedEditExtraField)
mDatabase?.binaryPool?.let { binaryPool ->
assignAttachments(newEntry.getAttachments(binaryPool).toSet(), StreamDirection.UPLOAD) { attachment ->
newEntry.removeAttachment(attachment)
}
}
}
}
private fun populateEntryWithViews(newEntry: Entry) {
mDatabase?.startManageEntry(newEntry)
newEntry.apply {
// Build info from view
entryEditContentsView?.let { entryView ->
removeAllFields()
title = entryView.title
username = entryView.username
url = entryView.url
password = entryView.password
expires = entryView.expires
if (entryView.expires) {
expiryTime = entryView.expiresDate
}
notes = entryView.notes
entryView.getExtraFields().forEach { customField ->
putExtraField(customField.name, customField.protectedValue)
}
mDatabase?.binaryPool?.let { binaryPool ->
entryView.getAttachments().forEach {
putAttachment(it, binaryPool)
}
}
mFocusedEditExtraField = entryView.getExtraFieldFocused()
}
}
mDatabase?.stopManageEntry(newEntry)
}
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
mNewEntry?.icon = icon
mDatabase?.drawFactory?.let { iconDrawFactory ->
entryEditContentsView?.setIcon(iconDrawFactory, icon)
}
}
/** /**
* Open the password generator fragment * Open the password generator fragment
*/ */
@@ -439,20 +375,17 @@ class EntryEditActivity : LockingActivity(),
} }
override fun onNewCustomFieldApproved(newField: Field) { override fun onNewCustomFieldApproved(newField: Field) {
entryEditContentsView?.apply { entryEditFragment?.apply {
putExtraField(newField) putExtraField(newField)
getExtraFieldViewPosition(newField) { position ->
scrollView?.smoothScrollTo(0, position.toInt())
}
} }
} }
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) { override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
entryEditContentsView?.replaceExtraField(oldField, newField) entryEditFragment?.replaceExtraField(oldField, newField)
} }
override fun onDeleteCustomFieldApproved(oldField: Field) { override fun onDeleteCustomFieldApproved(oldField: Field) {
entryEditContentsView?.removeExtraField(oldField) entryEditFragment?.removeExtraField(oldField)
} }
/** /**
@@ -469,8 +402,15 @@ class EntryEditActivity : LockingActivity(),
} }
override fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?) { override fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?) {
startUploadAttachment(attachmentToUploadUri, attachment)
}
private fun startUploadAttachment(attachmentToUploadUri: Uri?, attachment: Attachment?) {
if (attachmentToUploadUri != null && attachment != null) { if (attachmentToUploadUri != null && attachment != null) {
// Start uploading in service
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, attachment) mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, attachment)
// Add in temp list
mTempAttachments.add(attachment)
} }
} }
@@ -479,12 +419,12 @@ class EntryEditActivity : LockingActivity(),
mDatabase?.buildNewBinary(applicationContext.filesDir, false, compression)?.let { binaryAttachment -> mDatabase?.buildNewBinary(applicationContext.filesDir, false, compression)?.let { binaryAttachment ->
val entryAttachment = Attachment(fileName, binaryAttachment) val entryAttachment = Attachment(fileName, binaryAttachment)
// Ask to replace the current attachment // Ask to replace the current attachment
if ((mDatabase?.allowMultipleAttachments != true && entryEditContentsView?.containsAttachment() == true) || if ((mDatabase?.allowMultipleAttachments != true && entryEditFragment?.containsAttachment() == true) ||
entryEditContentsView?.containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD)) == true) { entryEditFragment?.containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD)) == true) {
ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment) ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
.show(supportFragmentManager, "replacementFileFragment") .show(supportFragmentManager, "replacementFileFragment")
} else { } else {
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, entryAttachment) startUploadAttachment(attachmentToUploadUri, entryAttachment)
} }
} }
} }
@@ -515,7 +455,7 @@ class EntryEditActivity : LockingActivity(),
private fun setupOTP() { private fun setupOTP() {
// Retrieve the current otpElement if exists // Retrieve the current otpElement if exists
// and open the dialog to set up the OTP // and open the dialog to set up the OTP
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel) SetOTPDialogFragment.build(entryEditFragment?.getEntryInfo()?.otpModel)
.show(supportFragmentManager, "addOTPDialog") .show(supportFragmentManager, "addOTPDialog")
} }
@@ -523,18 +463,30 @@ class EntryEditActivity : LockingActivity(),
* Saves the new entry or update an existing entry in the database * Saves the new entry or update an existing entry in the database
*/ */
private fun saveEntry() { private fun saveEntry() {
// Launch a validation and show the error if present // Get the temp entry
if (entryEditContentsView?.isValid() == true) { entryEditFragment?.getEntryInfo()?.let { newEntryInfo ->
// Clone the entry
mNewEntry?.let { newEntry ->
// WARNING Add the parent previously deleted if (mIsNew) {
newEntry.parent = mEntry?.parent // Create new one
mDatabase?.createEntry()
} else {
// Create a clone
Entry(mEntry!!)
}?.let { newEntry ->
newEntry.setEntryInfo(mDatabase, newEntryInfo)
// Build info // Build info
newEntry.lastAccessTime = DateInstant() newEntry.lastAccessTime = DateInstant()
newEntry.lastModificationTime = DateInstant() newEntry.lastModificationTime = DateInstant()
populateEntryWithViews(newEntry) // Delete temp attachment if not used
mTempAttachments.forEach {
mDatabase?.binaryPool?.let { binaryPool ->
if (!newEntry.getAttachments(binaryPool).contains(it)) {
mDatabase?.removeAttachmentIfNotUsed(it)
}
}
}
// Open a progress dialog and save entry // Open a progress dialog and save entry
if (mIsNew) { if (mIsNew) {
@@ -567,30 +519,23 @@ class EntryEditActivity : LockingActivity(),
menu.findItem(R.id.menu_save_database)?.isVisible = false menu.findItem(R.id.menu_save_database)?.isVisible = false
MenuUtil.contributionMenuInflater(inflater, menu) MenuUtil.contributionMenuInflater(inflater, menu)
entryEditActivityEducation?.let {
Handler().post { performedNextEducation(it) }
}
return true return true
} }
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
val passwordGeneratorView: View? = entryEditContentsView?.entryPasswordGeneratorView override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
val generatePasswordEducationPerformed = passwordGeneratorView != null entryEditActivityEducation?.let {
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation( Handler().post { performedNextEducation(it) }
passwordGeneratorView,
{
openPasswordGenerator()
},
{
performedNextEducation(entryEditActivityEducation)
} }
) return super.onPrepareOptionsMenu(menu)
if (!generatePasswordEducationPerformed) { }
fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
if (entryEditFragment?.generatePasswordEducationPerformed(entryEditActivityEducation) != true) {
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field) val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
val addNewFieldEducationPerformed = mNewEntry != null val addNewFieldEducationPerformed = mDatabase?.allowEntryCustomFields() == true
&& mNewEntry!!.allowCustomFields() && addNewFieldView != null && addNewFieldView != null
&& addNewFieldView.visibility == View.VISIBLE && addNewFieldView.isVisible
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation( && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView, addNewFieldView,
{ {
@@ -602,7 +547,8 @@ class EntryEditActivity : LockingActivity(),
) )
if (!addNewFieldEducationPerformed) { if (!addNewFieldEducationPerformed) {
val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment) val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment)
val addAttachmentEducationPerformed = attachmentView != null && attachmentView.visibility == View.VISIBLE val addAttachmentEducationPerformed = attachmentView != null
&& attachmentView.isVisible
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation( && entryEditActivityEducation.checkAndPerformedAttachmentEducation(
attachmentView, attachmentView,
{ {
@@ -614,7 +560,8 @@ class EntryEditActivity : LockingActivity(),
) )
if (!addAttachmentEducationPerformed) { if (!addAttachmentEducationPerformed) {
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp) val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
setupOtpView != null && setupOtpView.visibility == View.VISIBLE setupOtpView != null
&& setupOtpView.isVisible
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation( && entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
setupOtpView, setupOtpView,
{ {
@@ -644,21 +591,28 @@ class EntryEditActivity : LockingActivity(),
} }
override fun onOtpCreated(otpElement: OtpElement) { override fun onOtpCreated(otpElement: OtpElement) {
// Update the otp field with otpauth:// url var titleOTP: String? = null
val otpField = OtpEntryFields.buildOtpField(otpElement, var usernameOTP: String? = null
mEntry?.title, mEntry?.username) // Build a temp entry to get title and username (by ref)
mEntry?.putExtraField(otpField.name, otpField.protectedValue) entryEditFragment?.getEntryInfo()?.let { entryInfo ->
entryEditContentsView?.apply { val entryTemp = mDatabase?.createEntry()
putExtraField(otpField) entryTemp?.setEntryInfo(mDatabase, entryInfo)
getExtraFieldViewPosition(otpField) { position -> mDatabase?.startManageEntry(entryTemp)
scrollView?.smoothScrollTo(0, position.toInt()) titleOTP = entryTemp?.title
usernameOTP = entryTemp?.username
mDatabase?.stopManageEntry(mEntry)
} }
// Update the otp field with otpauth:// url
val otpField = OtpEntryFields.buildOtpField(otpElement, titleOTP, usernameOTP)
mEntry?.putExtraField(Field(otpField.name, otpField.protectedValue))
entryEditFragment?.apply {
putExtraField(otpField)
} }
} }
override fun iconPicked(bundle: Bundle) { override fun iconPicked(bundle: Bundle) {
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon -> IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
temporarilySaveAndShowSelectedIcon(icon) entryEditFragment?.icon = icon
} }
} }
@@ -666,9 +620,9 @@ class EntryEditActivity : LockingActivity(),
// To fix android 4.4 issue // To fix android 4.4 issue
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice // https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
if (datePicker?.isShown == true) { if (datePicker?.isShown == true) {
entryEditContentsView?.expiresDate?.date?.let { expiresDate -> entryEditFragment?.expiryTime?.date?.let { expiresDate ->
// Save the date // Save the date
entryEditContentsView?.expiresDate = entryEditFragment?.expiryTime =
DateInstant(DateTime(expiresDate) DateInstant(DateTime(expiresDate)
.withYear(year) .withYear(year)
.withMonthOfYear(month + 1) .withMonthOfYear(month + 1)
@@ -685,9 +639,9 @@ class EntryEditActivity : LockingActivity(),
} }
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) { override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
entryEditContentsView?.expiresDate?.date?.let { expiresDate -> entryEditFragment?.expiryTime?.date?.let { expiresDate ->
// Save the date // Save the date
entryEditContentsView?.expiresDate = entryEditFragment?.expiryTime =
DateInstant(DateTime(expiresDate) DateInstant(DateTime(expiresDate)
.withHourOfDay(hours) .withHourOfDay(hours)
.withMinuteOfHour(minutes) .withMinuteOfHour(minutes)
@@ -696,21 +650,15 @@ class EntryEditActivity : LockingActivity(),
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
mNewEntry?.let {
populateEntryWithViews(it)
outState.putParcelable(KEY_NEW_ENTRY, it)
}
mFocusedEditExtraField?.let { outState.putParcelableArrayList(TEMP_ATTACHMENTS, mTempAttachments)
outState.putParcelable(EXTRA_FIELD_FOCUSED_ENTRY, it)
}
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
override fun acceptPassword(bundle: Bundle) { override fun acceptPassword(bundle: Bundle) {
bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID)?.let { bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID)?.let {
entryEditContentsView?.password = it entryEditFragment?.password = it
} }
entryEditActivityEducation?.let { entryEditActivityEducation?.let {
@@ -734,10 +682,10 @@ class EntryEditActivity : LockingActivity(),
override fun finish() { override fun finish() {
// Assign entry callback as a result in all case // Assign entry callback as a result in all case
try { try {
mNewEntry?.let { mEntry?.let { entry ->
val bundle = Bundle() val bundle = Bundle()
val intentEntry = Intent() val intentEntry = Intent()
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mNewEntry) bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry)
intentEntry.putExtras(bundle) intentEntry.putExtras(bundle)
if (mIsNew) { if (mIsNew) {
setResult(ADD_ENTRY_RESULT_CODE, intentEntry) setResult(ADD_ENTRY_RESULT_CODE, intentEntry)
@@ -761,8 +709,7 @@ class EntryEditActivity : LockingActivity(),
const val KEY_PARENT = "parent" const val KEY_PARENT = "parent"
// SaveInstanceState // SaveInstanceState
const val KEY_NEW_ENTRY = "new_entry" const val TEMP_ATTACHMENTS = "TEMP_ATTACHMENTS"
const val EXTRA_FIELD_FOCUSED_ENTRY = "EXTRA_FIELD_FOCUSED_ENTRY"
// Keys for callback // Keys for callback
const val ADD_ENTRY_RESULT_CODE = 31 const val ADD_ENTRY_RESULT_CODE = 31
@@ -770,6 +717,8 @@ class EntryEditActivity : LockingActivity(),
const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129 const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129
const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY" const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
const val ENTRY_EDIT_FRAGMENT_TAG = "ENTRY_EDIT_FRAGMENT_TAG"
/** /**
* Launch EntryEditActivity to update an existing entry * Launch EntryEditActivity to update an existing entry
* *

View File

@@ -0,0 +1,535 @@
/*
* 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
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
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.education.EntryEditActivityEducation
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.applyFontVisibility
import com.kunzisoft.keepass.view.collapse
import com.kunzisoft.keepass.view.expand
class EntryEditFragment: StylishFragment() {
private lateinit var entryTitleLayoutView: TextInputLayout
private lateinit var entryTitleView: EditText
private lateinit var entryIconView: ImageView
private lateinit var entryUserNameView: EditText
private lateinit var entryUrlView: EditText
private lateinit var entryPasswordLayoutView: TextInputLayout
private lateinit var entryPasswordView: EditText
private lateinit var entryPasswordGeneratorView: View
private lateinit var entryExpiresCheckBox: CompoundButton
private lateinit var entryExpiresTextView: TextView
private lateinit var entryNotesView: EditText
private lateinit var extraFieldsContainerView: View
private lateinit var extraFieldsListView: ViewGroup
private lateinit var attachmentsContainerView: View
private lateinit var attachmentsListView: RecyclerView
private lateinit var attachmentsAdapter: EntryAttachmentsItemsAdapter
private var fontInVisibility: Boolean = false
private var iconColor: Int = 0
private var expiresInstant: DateInstant = DateInstant.IN_ONE_MONTH
var drawFactory: IconDrawableFactory? = null
var setOnDateClickListener: View.OnClickListener? = null
var setOnPasswordGeneratorClickListener: View.OnClickListener? = null
var setOnIconViewClickListener: View.OnClickListener? = null
var setOnEditCustomField: ((Field) -> Unit)? = null
var setOnRemoveAttachment: ((Attachment) -> Unit)? = null
// Elements to modify the current entry
private var mEntryInfo = EntryInfo()
private var mLastFocusedEditField: FocusedEditField? = null
private var mExtraViewToRequestFocus: EditText? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val rootView = inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_entry_edit_contents, container, false)
fontInVisibility = PreferencesUtil.fieldFontIsInVisibility(requireContext())
entryTitleLayoutView = rootView.findViewById(R.id.entry_edit_container_title)
entryTitleView = rootView.findViewById(R.id.entry_edit_title)
entryIconView = rootView.findViewById(R.id.entry_edit_icon_button)
entryIconView.setOnClickListener {
setOnIconViewClickListener?.onClick(it)
}
entryUserNameView = rootView.findViewById(R.id.entry_edit_user_name)
entryUrlView = rootView.findViewById(R.id.entry_edit_url)
entryPasswordLayoutView = rootView.findViewById(R.id.entry_edit_container_password)
entryPasswordView = rootView.findViewById(R.id.entry_edit_password)
entryPasswordGeneratorView = rootView.findViewById(R.id.entry_edit_password_generator_button)
entryPasswordGeneratorView.setOnClickListener {
setOnPasswordGeneratorClickListener?.onClick(it)
}
entryExpiresCheckBox = rootView.findViewById(R.id.entry_edit_expires_checkbox)
entryExpiresTextView = rootView.findViewById(R.id.entry_edit_expires_text)
entryExpiresTextView.setOnClickListener {
if (entryExpiresCheckBox.isChecked)
setOnDateClickListener?.onClick(it)
}
entryNotesView = rootView.findViewById(R.id.entry_edit_notes)
extraFieldsContainerView = rootView.findViewById(R.id.extra_fields_container)
extraFieldsListView = rootView.findViewById(R.id.extra_fields_list)
attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container)
attachmentsListView = rootView.findViewById(R.id.entry_attachments_list)
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true)
} else if (previousSize == 0 && newSize == 1) {
attachmentsContainerView.expand(true)
}
}
attachmentsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
entryExpiresCheckBox.setOnCheckedChangeListener { _, _ ->
assignExpiresDateText()
}
// Retrieve the textColor to tint the icon
val taIconColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
iconColor = taIconColor?.getColor(0, Color.WHITE) ?: Color.WHITE
taIconColor?.recycle()
// Retrieve the new entry after an orientation change
if (arguments?.containsKey(KEY_TEMP_ENTRY_INFO) == true)
mEntryInfo = arguments?.getParcelable(KEY_TEMP_ENTRY_INFO) ?: mEntryInfo
else if (savedInstanceState?.containsKey(KEY_TEMP_ENTRY_INFO) == true) {
mEntryInfo = savedInstanceState.getParcelable(KEY_TEMP_ENTRY_INFO) ?: mEntryInfo
}
if (savedInstanceState?.containsKey(KEY_LAST_FOCUSED_FIELD) == true) {
mLastFocusedEditField = savedInstanceState.getParcelable(KEY_LAST_FOCUSED_FIELD) ?: mLastFocusedEditField
}
populateViewsWithEntry()
return rootView
}
override fun onDetach() {
super.onDetach()
drawFactory = null
setOnDateClickListener = null
setOnPasswordGeneratorClickListener = null
setOnIconViewClickListener = null
setOnRemoveAttachment = null
setOnEditCustomField = null
}
fun getEntryInfo(): EntryInfo? {
populateEntryWithViews()
return mEntryInfo
}
fun generatePasswordEducationPerformed(entryEditActivityEducation: EntryEditActivityEducation): Boolean {
return entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
entryPasswordGeneratorView,
{
GeneratePasswordDialogFragment().show(parentFragmentManager, "PasswordGeneratorFragment")
},
{
try {
(activity as? EntryEditActivity?)?.performedNextEducation(entryEditActivityEducation)
} catch (ignore: Exception) {}
}
)
}
private fun populateViewsWithEntry() {
// Set info in view
icon = mEntryInfo.icon
title = mEntryInfo.title
username = mEntryInfo.username
url = mEntryInfo.url
password = mEntryInfo.password
expires = mEntryInfo.expires
expiryTime = mEntryInfo.expiryTime
notes = mEntryInfo.notes
assignExtraFields(mEntryInfo.customFields) { fields ->
setOnEditCustomField?.invoke(fields)
}
assignAttachments(mEntryInfo.attachments, StreamDirection.UPLOAD) { attachment ->
setOnRemoveAttachment?.invoke(attachment)
}
}
private fun populateEntryWithViews() {
// Icon already populate
mEntryInfo.title = title
mEntryInfo.username = username
mEntryInfo.url = url
mEntryInfo.password = password
mEntryInfo.expires = expires
mEntryInfo.expiryTime = expiryTime
mEntryInfo.notes = notes
mEntryInfo.customFields = getExtraFields()
mEntryInfo.otpModel = OtpEntryFields.parseFields { key ->
getExtraFields().firstOrNull { it.name == key }?.protectedValue?.toString()
}?.otpModel
mEntryInfo.attachments = getAttachments()
}
var title: String
get() {
return entryTitleView.text.toString()
}
set(value) {
entryTitleView.setText(value)
if (fontInVisibility)
entryTitleView.applyFontVisibility()
}
var icon: IconImage
get() {
return mEntryInfo.icon
}
set(value) {
mEntryInfo.icon = value
drawFactory?.let { drawFactory ->
entryIconView.assignDatabaseIcon(drawFactory, value, iconColor)
}
}
var username: String
get() {
return entryUserNameView.text.toString()
}
set(value) {
entryUserNameView.setText(value)
if (fontInVisibility)
entryUserNameView.applyFontVisibility()
}
var url: String
get() {
return entryUrlView.text.toString()
}
set(value) {
entryUrlView.setText(value)
if (fontInVisibility)
entryUrlView.applyFontVisibility()
}
var password: String
get() {
return entryPasswordView.text.toString()
}
set(value) {
entryPasswordView.setText(value)
if (fontInVisibility) {
entryPasswordView.applyFontVisibility()
}
}
private fun assignExpiresDateText() {
entryExpiresTextView.text = if (entryExpiresCheckBox.isChecked) {
entryExpiresTextView.setOnClickListener(setOnDateClickListener)
expiresInstant.getDateTimeString(resources)
} else {
entryExpiresTextView.setOnClickListener(null)
resources.getString(R.string.never)
}
if (fontInVisibility)
entryExpiresTextView.applyFontVisibility()
}
var expires: Boolean
get() {
return entryExpiresCheckBox.isChecked
}
set(value) {
if (!value) {
expiresInstant = DateInstant.IN_ONE_MONTH
}
entryExpiresCheckBox.isChecked = value
assignExpiresDateText()
}
var expiryTime: DateInstant
get() {
return if (expires)
expiresInstant
else
DateInstant.NEVER_EXPIRE
}
set(value) {
if (expires)
expiresInstant = value
assignExpiresDateText()
}
var notes: String
get() {
return entryNotesView.text.toString()
}
set(value) {
entryNotesView.setText(value)
if (fontInVisibility)
entryNotesView.applyFontVisibility()
}
/* -------------
* Extra Fields
* -------------
*/
private var mExtraFieldsList: MutableList<Field> = ArrayList()
private var mOnEditButtonClickListener: ((item: Field)->Unit)? = null
private fun buildViewFromField(extraField: Field): View? {
val inflater = context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
val itemView: View? = inflater?.inflate(R.layout.item_entry_edit_extra_field, extraFieldsListView, false)
itemView?.id = View.NO_ID
val extraFieldValueContainer: TextInputLayout? = itemView?.findViewById(R.id.entry_extra_field_value_container)
extraFieldValueContainer?.isPasswordVisibilityToggleEnabled = extraField.protectedValue.isProtected
extraFieldValueContainer?.hint = extraField.name
extraFieldValueContainer?.id = View.NO_ID
val extraFieldValue: TextInputEditText? = itemView?.findViewById(R.id.entry_extra_field_value)
extraFieldValue?.apply {
if (extraField.protectedValue.isProtected) {
inputType = extraFieldValue.inputType or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
}
setText(extraField.protectedValue.toString())
if (fontInVisibility)
applyFontVisibility()
}
extraFieldValue?.id = View.NO_ID
extraFieldValue?.tag = "FIELD_VALUE_TAG"
if (mLastFocusedEditField?.field == extraField) {
mExtraViewToRequestFocus = extraFieldValue
}
val extraFieldEditButton: View? = itemView?.findViewById(R.id.entry_extra_field_edit)
extraFieldEditButton?.setOnClickListener {
mOnEditButtonClickListener?.invoke(extraField)
}
extraFieldEditButton?.id = View.NO_ID
return itemView
}
fun getExtraFields(): List<Field> {
mLastFocusedEditField = null
for (index in 0 until extraFieldsListView.childCount) {
val extraFieldValue: EditText = extraFieldsListView.getChildAt(index)
.findViewWithTag("FIELD_VALUE_TAG")
val extraField = mExtraFieldsList[index]
extraField.protectedValue.stringValue = extraFieldValue.text?.toString() ?: ""
if (extraFieldValue.isFocused) {
mLastFocusedEditField = FocusedEditField().apply {
field = extraField
cursorSelectionStart = extraFieldValue.selectionStart
cursorSelectionEnd = extraFieldValue.selectionEnd
}
}
}
return mExtraFieldsList
}
/**
* Remove all children and add new views for each field
*/
fun assignExtraFields(fields: List<Field>,
onEditButtonClickListener: ((item: Field)->Unit)?) {
extraFieldsContainerView.visibility = if (fields.isEmpty()) View.GONE else View.VISIBLE
// Reinit focused field
mExtraFieldsList.clear()
mExtraFieldsList.addAll(fields)
extraFieldsListView.removeAllViews()
fields.forEach {
extraFieldsListView.addView(buildViewFromField(it))
}
// Request last focus
mLastFocusedEditField?.let { focusField ->
mExtraViewToRequestFocus?.apply {
requestFocus()
setSelection(focusField.cursorSelectionStart,
focusField.cursorSelectionEnd)
}
}
mLastFocusedEditField = null
mOnEditButtonClickListener = onEditButtonClickListener
}
/**
* Update an extra field or create a new one if doesn't exists
*/
fun putExtraField(extraField: Field) {
extraFieldsContainerView.visibility = View.VISIBLE
val oldField = mExtraFieldsList.firstOrNull { it.name == extraField.name }
oldField?.let {
val index = mExtraFieldsList.indexOf(oldField)
mExtraFieldsList.removeAt(index)
mExtraFieldsList.add(index, extraField)
extraFieldsListView.removeViewAt(index)
val newView = buildViewFromField(extraField)
extraFieldsListView.addView(newView, index)
newView?.requestFocus()
} ?: kotlin.run {
mExtraFieldsList.add(extraField)
val newView = buildViewFromField(extraField)
extraFieldsListView.addView(newView)
newView?.requestFocus()
}
}
fun replaceExtraField(oldExtraField: Field, newExtraField: Field) {
extraFieldsContainerView.visibility = View.VISIBLE
val index = mExtraFieldsList.indexOf(oldExtraField)
mExtraFieldsList.removeAt(index)
mExtraFieldsList.add(index, newExtraField)
extraFieldsListView.removeViewAt(index)
extraFieldsListView.addView(buildViewFromField(newExtraField), index)
}
fun removeExtraField(oldExtraField: Field) {
val previousSize = mExtraFieldsList.size
val index = mExtraFieldsList.indexOf(oldExtraField)
extraFieldsListView.getChildAt(index)?.let {
it.collapse(true) {
mExtraFieldsList.removeAt(index)
extraFieldsListView.removeViewAt(index)
val newSize = mExtraFieldsList.size
if (previousSize > 0 && newSize == 0) {
extraFieldsContainerView.collapse(true)
} else if (previousSize == 0 && newSize == 1) {
extraFieldsContainerView.expand(true)
}
}
}
}
/* -------------
* Attachments
* -------------
*/
fun getAttachments(): List<Attachment> {
return attachmentsAdapter.itemsList.map { it.attachment }
}
fun assignAttachments(attachments: List<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)
}
override fun onSaveInstanceState(outState: Bundle) {
populateEntryWithViews()
outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo)
outState.putParcelable(KEY_LAST_FOCUSED_FIELD, mLastFocusedEditField)
super.onSaveInstanceState(outState)
}
companion object {
const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO"
const val KEY_LAST_FOCUSED_FIELD = "KEY_LAST_FOCUSED_FIELD"
fun getInstance(entryInfo: EntryInfo?): EntryEditFragment {
return EntryEditFragment().apply {
arguments = Bundle().apply {
putParcelable(KEY_TEMP_ENTRY_INFO, entryInfo)
}
}
}
}
}

View File

@@ -97,6 +97,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
} }
} }
override fun onDetach() {
nodeClickListener = null
onScrollListener = null
super.onDetach()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@@ -26,15 +26,18 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.SpannableStringBuilder
import android.text.TextWatcher import android.text.TextWatcher
import android.view.View import android.view.View
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView import com.kunzisoft.keepass.view.KeyFileSelectionView
class AssignMasterKeyDialogFragment : DialogFragment() { class AssignMasterKeyDialogFragment : DialogFragment() {
@@ -58,6 +61,10 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var mSelectFileHelper: SelectFileHelper? = null private var mSelectFileHelper: SelectFileHelper? = null
private var mEmptyPasswordConfirmationDialog: AlertDialog? = null
private var mNoKeyConfirmationDialog: AlertDialog? = null
private var mEmptyKeyFileConfirmationDialog: AlertDialog? = null
private val passwordTextWatcher = object : TextWatcher { private val passwordTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
@@ -85,6 +92,17 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mListener = null
mEmptyPasswordConfirmationDialog?.dismiss()
mEmptyPasswordConfirmationDialog = null
mNoKeyConfirmationDialog?.dismiss()
mNoKeyConfirmationDialog = null
mEmptyKeyFileConfirmationDialog?.dismiss()
mEmptyKeyFileConfirmationDialog = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
@@ -99,11 +117,15 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
rootView = inflater.inflate(R.layout.fragment_set_password, null) rootView = inflater.inflate(R.layout.fragment_set_password, null)
builder.setView(rootView) builder.setView(rootView)
.setTitle(R.string.assign_master_key)
// Add action buttons // Add action buttons
.setPositiveButton(android.R.string.ok) { _, _ -> } .setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
val credentialsInfo: ImageView? = rootView?.findViewById(R.id.credentials_information)
credentialsInfo?.setOnClickListener {
UriUtil.gotoUrl(activity, R.string.credentials_explanation_url)
}
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox) passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout) passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
passwordView = rootView?.findViewById(R.id.pass_password) passwordView = rootView?.findViewById(R.id.pass_password)
@@ -129,7 +151,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
mMasterPassword = "" mMasterPassword = ""
mKeyFile = null mKeyFile = null
var error = verifyPassword() || verifyFile() var error = verifyPassword() || verifyKeyFile()
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) { if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
error = true error = true
if (allowNoMasterKey) if (allowNoMasterKey)
@@ -199,7 +221,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
return error return error
} }
private fun verifyFile(): Boolean { private fun verifyKeyFile(): Boolean {
var error = false var error = false
if (keyFileCheckBox != null if (keyFileCheckBox != null
&& keyFileCheckBox!!.isChecked) { && keyFileCheckBox!!.isChecked) {
@@ -219,7 +241,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
val builder = AlertDialog.Builder(it) val builder = AlertDialog.Builder(it)
builder.setMessage(R.string.warning_empty_password) builder.setMessage(R.string.warning_empty_password)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
if (!verifyFile()) { if (!verifyKeyFile()) {
mListener?.onAssignKeyDialogPositiveClick( mListener?.onAssignKeyDialogPositiveClick(
passwordCheckBox!!.isChecked, mMasterPassword, passwordCheckBox!!.isChecked, mMasterPassword,
keyFileCheckBox!!.isChecked, mKeyFile) keyFileCheckBox!!.isChecked, mKeyFile)
@@ -227,7 +249,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
} }
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
builder.create().show() mEmptyPasswordConfirmationDialog = builder.create()
mEmptyPasswordConfirmationDialog?.show()
} }
} }
@@ -242,7 +265,28 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
this@AssignMasterKeyDialogFragment.dismiss() this@AssignMasterKeyDialogFragment.dismiss()
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
builder.create().show() mNoKeyConfirmationDialog = builder.create()
mNoKeyConfirmationDialog?.show()
}
}
private fun showEmptyKeyFileConfirmationDialog() {
activity?.let {
val builder = AlertDialog.Builder(it)
builder.setMessage(SpannableStringBuilder().apply {
append(getString(R.string.warning_empty_keyfile))
append("\n\n")
append(getString(R.string.warning_empty_keyfile_explanation))
append("\n\n")
append(getString(R.string.warning_sure_add_file))
})
.setPositiveButton(android.R.string.ok) { _, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ ->
keyFileCheckBox?.isChecked = false
keyFileSelectionView?.uri = null
}
mEmptyKeyFileConfirmationDialog = builder.create()
mEmptyKeyFileConfirmationDialog?.show()
} }
} }
@@ -251,8 +295,14 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri -> mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
uri?.let { pathUri -> uri?.let { pathUri ->
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
keyFileSelectionView?.error = null
keyFileCheckBox?.isChecked = true keyFileCheckBox?.isChecked = true
keyFileSelectionView?.uri = pathUri keyFileSelectionView?.uri = pathUri
if (lengthFile <= 0L) {
showEmptyKeyFileConfirmationDialog()
}
}
} }
} }
} }

View File

@@ -24,6 +24,11 @@ class DatePickerFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Create a new instance of DatePickerDialog and return it // Create a new instance of DatePickerDialog and return it
return context?.let { return context?.let {

View File

@@ -46,6 +46,11 @@ class DeleteNodesDialogFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
arguments?.apply { arguments?.apply {

View File

@@ -60,6 +60,11 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
} }
} }
override fun onDetach() {
entryCustomFieldListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_entry_new_field, null) val root = activity.layoutInflater.inflate(R.layout.fragment_entry_new_field, null)

View File

@@ -46,6 +46,11 @@ class FileTooBigDialogFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mActionChooseListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
// Use the Builder class for convenient dialog construction // Use the Builder class for convenient dialog construction

View File

@@ -64,6 +64,11 @@ class GeneratePasswordDialogFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)

View File

@@ -73,7 +73,11 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
throw ClassCastException(context.toString() throw ClassCastException(context.toString()
+ " must implement " + GroupEditDialogFragment::class.java.name) + " must implement " + GroupEditDialogFragment::class.java.name)
} }
}
override fun onDetach() {
editGroupListener = null
super.onDetach()
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

View File

@@ -34,6 +34,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.icons.IconPack import com.kunzisoft.keepass.icons.IconPack
@@ -56,6 +57,11 @@ class IconPickerDialogFragment : DialogFragment() {
} }
} }
override fun onDetach() {
iconPickerListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
@@ -132,7 +138,7 @@ class IconPickerDialogFragment : DialogFragment() {
return bundle.getParcelable(KEY_ICON_STANDARD) return bundle.getParcelable(KEY_ICON_STANDARD)
} }
fun launch(activity: AppCompatActivity) { fun launch(activity: FragmentActivity) {
// Create an instance of the dialog fragment and show it // Create an instance of the dialog fragment and show it
val dialog = IconPickerDialogFragment() val dialog = IconPickerDialogFragment()
dialog.show(activity.supportFragmentManager, "IconPickerDialogFragment") dialog.show(activity.supportFragmentManager, "IconPickerDialogFragment")

View File

@@ -21,20 +21,51 @@ package com.kunzisoft.keepass.activities.dialogs
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.Context
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
class PasswordEncodingDialogFragment : DialogFragment() { class PasswordEncodingDialogFragment : DialogFragment() {
var positiveButtonClickListener: DialogInterface.OnClickListener? = null private var mListener: Listener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as Listener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + Listener::class.java.name)
}
}
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val databaseUri: Uri? = savedInstanceState?.getParcelable(DATABASE_URI_KEY)
val masterPasswordChecked: Boolean = savedInstanceState?.getBoolean(MASTER_PASSWORD_CHECKED_KEY) ?: false
val masterPassword: String? = savedInstanceState?.getString(MASTER_PASSWORD_KEY)
val keyFileChecked: Boolean = savedInstanceState?.getBoolean(KEY_FILE_CHECKED_KEY) ?: false
val keyFile: Uri? = savedInstanceState?.getParcelable(KEY_FILE_URI_KEY)
activity?.let { activity -> activity?.let { activity ->
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning) builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener) builder.setPositiveButton(android.R.string.ok) { _, _ ->
mListener?.onPasswordEncodingValidateListener(
databaseUri,
masterPasswordChecked,
masterPassword,
keyFileChecked,
keyFile
)
}
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() } builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
return builder.create() return builder.create()
@@ -42,5 +73,36 @@ class PasswordEncodingDialogFragment : DialogFragment() {
return super.onCreateDialog(savedInstanceState) return super.onCreateDialog(savedInstanceState)
} }
interface Listener {
fun onPasswordEncodingValidateListener(databaseUri: Uri?,
masterPasswordChecked: Boolean,
masterPassword: String?,
keyFileChecked: Boolean,
keyFile: Uri?)
}
companion object {
private const val DATABASE_URI_KEY = "DATABASE_URI_KEY"
private const val MASTER_PASSWORD_CHECKED_KEY = "MASTER_PASSWORD_CHECKED_KEY"
private const val MASTER_PASSWORD_KEY = "MASTER_PASSWORD_KEY"
private const val KEY_FILE_CHECKED_KEY = "KEY_FILE_CHECKED_KEY"
private const val KEY_FILE_URI_KEY = "KEY_FILE_URI_KEY"
fun getInstance(databaseUri: Uri,
masterPasswordChecked: Boolean,
masterPassword: String?,
keyFileChecked: Boolean,
keyFile: Uri?): SortDialogFragment {
val fragment = SortDialogFragment()
fragment.arguments = Bundle().apply {
putParcelable(DATABASE_URI_KEY, databaseUri)
putBoolean(MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
putString(MASTER_PASSWORD_KEY, masterPassword)
putBoolean(KEY_FILE_CHECKED_KEY, keyFileChecked)
putParcelable(KEY_FILE_URI_KEY, keyFile)
}
return fragment
}
}
} }

View File

@@ -47,6 +47,11 @@ class ReplaceFileDialogFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mActionChooseListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
// Use the Builder class for convenient dialog construction // Use the Builder class for convenient dialog construction

View File

@@ -107,6 +107,11 @@ class SetOTPDialogFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mCreateOTPElementListener = null
super.onDetach()
}
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

View File

@@ -54,6 +54,11 @@ class SortDialogFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)

View File

@@ -25,6 +25,11 @@ class TimePickerFragment : DialogFragment() {
} }
} }
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Create a new instance of DatePickerDialog and return it // Create a new instance of DatePickerDialog and return it
return context?.let { return context?.let {

View File

@@ -20,7 +20,10 @@
package com.kunzisoft.keepass.adapters package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
import android.content.res.TypedArray
import android.graphics.Color
import android.text.format.Formatter import android.text.format.Formatter
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
@@ -33,11 +36,24 @@ import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
class EntryAttachmentsItemsAdapter(context: Context) class EntryAttachmentsItemsAdapter(context: Context)
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) { : AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
private var mTitleColor: Int
init {
// Get the primary text color of the theme
val typedValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true)
val typedArray: TypedArray = context.obtainStyledAttributes(typedValue.data, intArrayOf(
android.R.attr.textColor))
mTitleColor = typedArray.getColor(0, -1)
typedArray.recycle()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false)) return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
} }
@@ -46,7 +62,20 @@ class EntryAttachmentsItemsAdapter(context: Context)
val entryAttachmentState = itemsList[position] val entryAttachmentState = itemsList[position]
holder.itemView.visibility = View.VISIBLE holder.itemView.visibility = View.VISIBLE
holder.binaryFileBroken.apply {
setColorFilter(Color.RED)
visibility = if (entryAttachmentState.attachment.binaryAttachment.isCorrupted) {
View.VISIBLE
} else {
View.GONE
}
}
holder.binaryFileTitle.text = entryAttachmentState.attachment.name holder.binaryFileTitle.text = entryAttachmentState.attachment.name
if (entryAttachmentState.attachment.binaryAttachment.isCorrupted) {
holder.binaryFileTitle.setTextColor(Color.RED)
} else {
holder.binaryFileTitle.setTextColor(mTitleColor)
}
holder.binaryFileSize.text = Formatter.formatFileSize(context, holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachmentState.attachment.binaryAttachment.length()) entryAttachmentState.attachment.binaryAttachment.length())
holder.binaryFileCompression.apply { holder.binaryFileCompression.apply {
@@ -107,6 +136,7 @@ class EntryAttachmentsItemsAdapter(context: Context)
class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var binaryFileBroken: ImageView = itemView.findViewById(R.id.item_attachment_broken)
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title) var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size) var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression) var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)

View File

@@ -1,180 +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.adapters
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.model.FocusedEditField
import com.kunzisoft.keepass.view.EditTextSelectable
import com.kunzisoft.keepass.view.applyFontVisibility
class EntryExtraFieldsItemsAdapter(context: Context)
: AnimatedItemsAdapter<Field, EntryExtraFieldsItemsAdapter.EntryExtraFieldViewHolder>(context) {
var applyFontVisibility = false
set(value) {
field = value
notifyDataSetChanged()
}
private var mValueViewInputType: Int = 0
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)
)
mValueViewInputType = view.extraFieldValue.inputType
return view
}
override fun onBindViewHolder(holder: EntryExtraFieldViewHolder, position: Int) {
val extraField = itemsList[position]
holder.itemView.visibility = View.VISIBLE
if (extraField.protectedValue.isProtected) {
holder.extraFieldValueContainer.isPasswordVisibilityToggleEnabled = true
holder.extraFieldValue.inputType = EditorInfo.TYPE_TEXT_VARIATION_PASSWORD or mValueViewInputType
} else {
holder.extraFieldValueContainer.isPasswordVisibilityToggleEnabled = false
holder.extraFieldValue.inputType = mValueViewInputType
}
holder.extraFieldValueContainer.hint = extraField.name
holder.extraFieldValue.apply {
setText(extraField.protectedValue.toString())
// To Fix focus in RecyclerView
setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
setFocusField(extraField, selectionStart, selectionEnd)
} else {
// request focus on last text focused
if (focusedTimestampNotExpired())
requestFocusField(this, extraField, false)
else
removeFocusField(extraField)
}
}
addOnSelectionChangedListener(object: EditTextSelectable.OnSelectionChangedListener {
override fun onSelectionChanged(start: Int, end: Int) {
mLastFocusedEditField.apply {
cursorSelectionStart = start
cursorSelectionEnd = end
}
}
})
requestFocusField(this, extraField, true)
doOnTextChanged { text, _, _, _ ->
extraField.protectedValue.stringValue = text.toString()
}
if (applyFontVisibility)
applyFontVisibility()
}
holder.extraFieldEditButton.setOnClickListener {
onEditButtonClickListener?.invoke(extraField)
}
performDeletion(holder, extraField)
}
fun assignItems(items: List<Field>, focusedEditField: FocusedEditField?) {
focusedEditField?.let {
setFocusField(it, true)
}
super.assignItems(items)
}
private fun setFocusField(field: Field,
selectionStart: Int,
selectionEnd: Int,
force: Boolean = false) {
mLastFocusedEditField.apply {
this.field = field
this.cursorSelectionStart = selectionStart
this.cursorSelectionEnd = selectionEnd
}
setFocusField(mLastFocusedEditField, force)
}
private fun setFocusField(field: FocusedEditField, force: Boolean = false) {
mLastFocusedEditField = field
mLastFocusedTimestamp = if (force) 0L else System.currentTimeMillis()
}
private fun removeFocusField(field: Field? = null) {
if (field == null || mLastFocusedEditField.field == field) {
mLastFocusedEditField.destroy()
}
}
private fun requestFocusField(editText: EditText, field: Field, setSelection: Boolean) {
if (field == mLastFocusedEditField.field) {
editText.apply {
post {
if (setSelection) {
setEditTextSelection(editText)
}
requestFocus()
removeFocusField(field)
}
}
}
}
private fun setEditTextSelection(editText: EditText) {
try {
var newCursorPositionStart = mLastFocusedEditField.cursorSelectionStart
var newCursorPositionEnd = mLastFocusedEditField.cursorSelectionEnd
// Cursor at end if 0 or less
if (newCursorPositionStart < 0 || newCursorPositionEnd < 0) {
newCursorPositionStart = (editText.text?:"").length
newCursorPositionEnd = newCursorPositionStart
}
editText.setSelection(newCursorPositionStart, newCursorPositionEnd)
} catch (ignoredException: Exception) {}
}
private fun focusedTimestampNotExpired(): Boolean {
return mLastFocusedTimestamp == 0L || (mLastFocusedTimestamp + FOCUS_TIMESTAMP) > System.currentTimeMillis()
}
fun getFocusedField(): FocusedEditField {
return mLastFocusedEditField
}
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 extraFieldEditButton: View = itemView.findViewById(R.id.entry_extra_field_edit)
}
companion object {
// time to focus element when a keyboard appears
private const val FOCUS_TIMESTAMP = 400L
}
}

View File

@@ -23,7 +23,6 @@ import android.content.*
import android.content.Context.BIND_ABOVE_CLIENT import android.content.Context.BIND_ABOVE_CLIENT
import android.content.Context.BIND_NOT_FOREGROUND import android.content.Context.BIND_NOT_FOREGROUND
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@@ -46,6 +45,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
@@ -463,6 +463,13 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
, ACTION_DATABASE_UPDATE_COMPRESSION_TASK) , ACTION_DATABASE_UPDATE_COMPRESSION_TASK)
} }
fun startDatabaseRemoveUnlinkedData(save: Boolean) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK)
}
fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int, fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int,
newMaxHistoryItems: Int, newMaxHistoryItems: Int,
save: Boolean) { save: Boolean) {

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
class RemoveUnlinkedDataDatabaseRunnable (
context: Context,
database: Database,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
override fun onActionRun() {
try {
database.removeUnlinkedAttachments()
} catch (e: Exception) {
setError(e)
}
super.onActionRun()
}
}

View File

@@ -461,12 +461,13 @@ class Database {
fun removeAttachmentIfNotUsed(attachment: Attachment) { fun removeAttachmentIfNotUsed(attachment: Attachment) {
// No need in KDB database because unique attachment by entry // No need in KDB database because unique attachment by entry
mDatabaseKDBX?.removeAttachmentIfNotUsed(attachment) // Don't clear to fix upload multiple times
mDatabaseKDBX?.removeUnlinkedAttachment(attachment.binaryAttachment, false)
} }
fun removeUnlinkedAttachments() { fun removeUnlinkedAttachments() {
// No check in database KDB because unique attachment by entry // No check in database KDB because unique attachment by entry
mDatabaseKDBX?.removeUnlinkedAttachments() mDatabaseKDBX?.removeUnlinkedAttachments(true)
} }
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
@@ -822,18 +823,25 @@ class Database {
} }
} }
fun startManageEntry(entry: Entry) { fun startManageEntry(entry: Entry?) {
mDatabaseKDBX?.let { mDatabaseKDBX?.let {
entry.startToManageFieldReferences(it) entry?.startToManageFieldReferences(it)
} }
} }
fun stopManageEntry(entry: Entry) { fun stopManageEntry(entry: Entry?) {
mDatabaseKDBX?.let { mDatabaseKDBX?.let {
entry.stopToManageFieldReferences() entry?.stopToManageFieldReferences()
} }
} }
/**
* @return true if database allows custom field
*/
fun allowEntryCustomFields(): Boolean {
return mDatabaseKDBX != null
}
/** /**
* Remove oldest history for each entry if more than max items or max memory * Remove oldest history for each entry if more than max items or max memory
*/ */

View File

@@ -23,6 +23,8 @@ import android.content.res.Resources
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import org.joda.time.Duration
import org.joda.time.Instant
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@@ -95,6 +97,7 @@ class DateInstant : Parcelable {
companion object { companion object {
val NEVER_EXPIRE = neverExpire val NEVER_EXPIRE = neverExpire
val IN_ONE_MONTH = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
private val dateFormat = SimpleDateFormat.getDateTimeInstance() private val dateFormat = SimpleDateFormat.getDateTimeInstance()
private val neverExpire: DateInstant private val neverExpire: DateInstant

View File

@@ -33,7 +33,6 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
@@ -284,38 +283,44 @@ class Entry : Node, EntryVersionedInterface<Group> {
} }
/** /**
* Retrieve custom fields to show, key is the label, value is the value of field (protected or not) * Retrieve extra fields to show, key is the label, value is the value of field (protected or not)
* @return Map of label/value * @return Map of label/value
*/ */
val customFields: HashMap<String, ProtectedString> fun getExtraFields(): List<Field> {
get() = entryKDBX?.customFields ?: HashMap() val extraFields = ArrayList<Field>()
entryKDBX?.let {
/** for (field in it.customFields) {
* To redefine if version of entry allow custom field, extraFields.add(Field(field.key, field.value))
* @return true if entry allows custom field
*/
fun allowCustomFields(): Boolean {
return entryKDBX?.allowCustomFields() ?: false
} }
}
fun removeAllFields() { return extraFields
entryKDBX?.removeAllFields()
} }
/** /**
* Update or add an extra field to the list (standard or custom) * Update or add an extra field to the list (standard or custom)
* @param label Label of field, must be unique
* @param value Value of field
*/ */
fun putExtraField(label: String, value: ProtectedString) { fun putExtraField(field: Field) {
entryKDBX?.putExtraField(label, value) entryKDBX?.putExtraField(field.name, field.protectedValue)
}
private fun addExtraFields(fields: List<Field>) {
fields.forEach {
putExtraField(it)
}
}
private fun removeAllFields() {
entryKDBX?.removeAllFields()
} }
fun getOtpElement(): OtpElement? { fun getOtpElement(): OtpElement? {
entryKDBX?.let {
return OtpEntryFields.parseFields { key -> return OtpEntryFields.parseFields { key ->
customFields[key]?.toString() it.customFields[key]?.toString()
} }
} }
return null
}
fun startToManageFieldReferences(database: DatabaseKDBX) { fun startToManageFieldReferences(database: DatabaseKDBX) {
entryKDBX?.startToManageFieldReferences(database) entryKDBX?.startToManageFieldReferences(database)
@@ -341,16 +346,27 @@ class Entry : Node, EntryVersionedInterface<Group> {
|| entryKDBX?.containsAttachment() == true || entryKDBX?.containsAttachment() == true
} }
fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) { private fun addAttachments(binaryPool: BinaryPool, attachments: List<Attachment>) {
entryKDB?.putAttachment(attachment) attachments.forEach {
entryKDBX?.putAttachment(attachment, binaryPool) putAttachment(it, binaryPool)
}
} }
fun removeAttachment(attachment: Attachment) { private fun removeAttachment(attachment: Attachment) {
entryKDB?.removeAttachment(attachment) entryKDB?.removeAttachment(attachment)
entryKDBX?.removeAttachment(attachment) entryKDBX?.removeAttachment(attachment)
} }
private fun removeAllAttachments() {
entryKDB?.removeAttachment()
entryKDBX?.removeAttachments()
}
private fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
entryKDB?.putAttachment(attachment)
entryKDBX?.putAttachment(attachment, binaryPool)
}
fun getHistory(): ArrayList<Entry> { fun getHistory(): ArrayList<Entry> {
val history = ArrayList<Entry>() val history = ArrayList<Entry>()
val entryKDBXHistory = entryKDBX?.history ?: ArrayList() val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
@@ -404,26 +420,54 @@ class Entry : Node, EntryVersionedInterface<Group> {
database?.stopManageEntry(this) database?.stopManageEntry(this)
else else
database?.startManageEntry(this) database?.startManageEntry(this)
entryInfo.id = nodeId.toString() entryInfo.id = nodeId.toString()
entryInfo.title = title entryInfo.title = title
entryInfo.icon = icon entryInfo.icon = icon
entryInfo.username = username entryInfo.username = username
entryInfo.password = password entryInfo.password = password
entryInfo.expires = expires
entryInfo.expiryTime = expiryTime
entryInfo.url = url entryInfo.url = url
entryInfo.notes = notes entryInfo.notes = notes
for (entry in customFields.entries) { entryInfo.customFields = getExtraFields()
entryInfo.customFields.add(
Field(entry.key, entry.value))
}
// Add otpElement to generate token // Add otpElement to generate token
entryInfo.otpModel = getOtpElement()?.otpModel entryInfo.otpModel = getOtpElement()?.otpModel
if (!raw) {
// Replace parameter fields by generated OTP fields // Replace parameter fields by generated OTP fields
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields) entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
}
database?.binaryPool?.let { binaryPool ->
entryInfo.attachments = getAttachments(binaryPool)
}
if (!raw) if (!raw)
database?.stopManageEntry(this) database?.stopManageEntry(this)
return entryInfo return entryInfo
} }
fun setEntryInfo(database: Database?, newEntryInfo: EntryInfo) {
database?.startManageEntry(this)
removeAllFields()
removeAllAttachments()
// NodeId stay as is
title = newEntryInfo.title
icon = newEntryInfo.icon
username = newEntryInfo.username
password = newEntryInfo.password
expires = newEntryInfo.expires
expiryTime = newEntryInfo.expiryTime
url = newEntryInfo.url
notes = newEntryInfo.notes
addExtraFields(newEntryInfo.customFields)
database?.binaryPool?.let { binaryPool ->
addAttachments(binaryPool, newEntryInfo.attachments)
}
database?.stopManageEntry(this)
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false

View File

@@ -32,6 +32,7 @@ class BinaryAttachment : Parcelable {
private set private set
var isProtected: Boolean = false var isProtected: Boolean = false
private set private set
var isCorrupted: Boolean = false
private var dataFile: File? = null private var dataFile: File? = null
fun length(): Long { fun length(): Long {
@@ -43,11 +44,7 @@ class BinaryAttachment : Parcelable {
/** /**
* Empty protected binary * Empty protected binary
*/ */
constructor() { constructor()
this.isCompressed = false
this.isProtected = false
this.dataFile = null
}
constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean = false) { constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean = false) {
this.isCompressed = compressed this.isCompressed = compressed
@@ -59,6 +56,7 @@ class BinaryAttachment : Parcelable {
val compressedByte = parcel.readByte().toInt() val compressedByte = parcel.readByte().toInt()
isCompressed = compressedByte != 0 isCompressed = compressedByte != 0
isProtected = parcel.readByte().toInt() != 0 isProtected = parcel.readByte().toInt() != 0
isCorrupted = parcel.readByte().toInt() != 0
parcel.readString()?.let { parcel.readString()?.let {
dataFile = File(it) dataFile = File(it)
} }
@@ -164,6 +162,7 @@ class BinaryAttachment : Parcelable {
return isCompressed == other.isCompressed return isCompressed == other.isCompressed
&& isProtected == other.isProtected && isProtected == other.isProtected
&& isCorrupted == other.isCorrupted
&& sameData && sameData
} }
@@ -172,6 +171,7 @@ class BinaryAttachment : Parcelable {
var result = 0 var result = 0
result = 31 * result + if (isCompressed) 1 else 0 result = 31 * result + if (isCompressed) 1 else 0
result = 31 * result + if (isProtected) 1 else 0 result = 31 * result + if (isProtected) 1 else 0
result = 31 * result + if (isCorrupted) 1 else 0
result = 31 * result + dataFile!!.hashCode() result = 31 * result + dataFile!!.hashCode()
return result return result
} }
@@ -187,6 +187,7 @@ class BinaryAttachment : Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed) 1 else 0).toByte()) dest.writeByte((if (isCompressed) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte()) dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
dest.writeString(dataFile?.absolutePath) dest.writeString(dataFile?.absolutePath)
} }

View File

@@ -60,8 +60,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
// Retrieve backup group in index // Retrieve backup group in index
val backupGroup: GroupKDB? val backupGroup: GroupKDB?
get() { get() {
if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
ensureBackupExists()
return if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID) return if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
null null
else else
@@ -186,6 +184,9 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
override fun isInRecycleBin(group: GroupKDB): Boolean { override fun isInRecycleBin(group: GroupKDB): Boolean {
var currentGroup: GroupKDB? = group var currentGroup: GroupKDB? = group
if (backupGroup == null)
return false
if (currentGroup == backupGroup) if (currentGroup == backupGroup)
return true return true
@@ -229,6 +230,8 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
* @return true if node can be recycle, false elsewhere * @return true if node can be recycle, false elsewhere
*/ */
fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean { fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
if (backupGroup == null)
ensureBackupExists()
if (node == backupGroup) if (node == backupGroup)
return false return false
backupGroup?.let { backupGroup?.let {
@@ -239,14 +242,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
} }
fun recycle(group: GroupKDB) { fun recycle(group: GroupKDB) {
ensureBackupExists()
removeGroupFrom(group, group.parent) removeGroupFrom(group, group.parent)
addGroupTo(group, backupGroup) addGroupTo(group, backupGroup)
group.afterAssignNewParent() group.afterAssignNewParent()
} }
fun recycle(entry: EntryKDB) { fun recycle(entry: EntryKDB) {
ensureBackupExists()
removeEntryFrom(entry, entry.parent) removeEntryFrom(entry, entry.parent)
addEntryTo(entry, backupGroup) addEntryTo(entry, backupGroup)
entry.afterAssignNewParent() entry.afterAssignNewParent()

View File

@@ -30,9 +30,9 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
@@ -42,7 +42,6 @@ import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
@@ -570,12 +569,17 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return binaryAttachment return binaryAttachment
} }
fun removeAttachmentIfNotUsed(attachment: Attachment) { fun removeUnlinkedAttachment(binary: BinaryAttachment, clear: Boolean) {
// Remove attachment from pool val listBinaries = ArrayList<BinaryAttachment>()
removeUnlinkedAttachments(attachment.binaryAttachment) listBinaries.add(binary)
removeUnlinkedAttachments(listBinaries, clear)
} }
fun removeUnlinkedAttachments(vararg binaries: BinaryAttachment) { fun removeUnlinkedAttachments(clear: Boolean) {
removeUnlinkedAttachments(emptyList(), clear)
}
private fun removeUnlinkedAttachments(binaries: List<BinaryAttachment>, clear: Boolean) {
// Build binaries to remove with all binaries known // Build binaries to remove with all binaries known
val binariesToRemove = ArrayList<BinaryAttachment>() val binariesToRemove = ArrayList<BinaryAttachment>()
if (binaries.isEmpty()) { if (binaries.isEmpty()) {
@@ -598,6 +602,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
binariesToRemove.forEach { binariesToRemove.forEach {
try { try {
binaryPool.remove(it) binaryPool.remove(it)
if (clear)
it.clear()
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Unable to clean binaries", e) Log.w(TAG, "Unable to clean binaries", e)
} }
@@ -632,7 +638,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
private const val KeyElementName = "Key" private const val KeyElementName = "Key"
private const val KeyDataElementName = "Data" private const val KeyDataElementName = "Data"
const val BASE_64_FLAG = Base64.NO_WRAP const val BASE_64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP
const val BUFFER_SIZE_BYTES = 3 * 128 const val BUFFER_SIZE_BYTES = 3 * 128
} }

View File

@@ -27,7 +27,6 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.KeyFileEmptyDatabaseException
import java.io.* import java.io.*
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
@@ -136,7 +135,6 @@ abstract class DatabaseVersioned<
} }
when (keyData.size.toLong()) { when (keyData.size.toLong()) {
0L -> throw KeyFileEmptyDatabaseException()
32L -> return keyData 32L -> return keyData
64L -> try { 64L -> try {
return hexStringToByteArray(String(keyData)) return hexStringToByteArray(String(keyData))

View File

@@ -153,8 +153,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
this.binaryData = attachment.binaryAttachment this.binaryData = attachment.binaryAttachment
} }
fun removeAttachment(attachment: Attachment) { fun removeAttachment(attachment: Attachment? = null) {
if (this.binaryDescription == attachment.name) { if (attachment == null || this.binaryDescription == attachment.name) {
this.binaryDescription = "" this.binaryDescription = ""
this.binaryData = null this.binaryData = null
} }

View File

@@ -38,7 +38,6 @@ import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedLong import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashSet
import kotlin.collections.LinkedHashMap import kotlin.collections.LinkedHashMap
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface { class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
@@ -272,10 +271,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return field return field
} }
fun allowCustomFields(): Boolean {
return true
}
fun removeAllFields() { fun removeAllFields() {
fields.clear() fields.clear()
} }
@@ -314,6 +309,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
binaries.remove(attachment.name) binaries.remove(attachment.name)
} }
fun removeAttachments() {
binaries.clear()
}
private fun getAttachmentsSize(binaryPool: BinaryPool): Long { private fun getAttachmentsSize(binaryPool: BinaryPool): Long {
var size = 0L var size = 0L
for ((label, poolId) in binaries) { for ((label, poolId) in binaries) {
@@ -323,11 +322,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return size return size
} }
// TODO Remove ?
fun sizeOfHistory(): Int {
return history.size
}
override fun putCustomData(key: String, value: String) { override fun putCustomData(key: String, value: String) {
customData[key] = value customData[key] = value
} }

View File

@@ -116,13 +116,6 @@ class InvalidCredentialsDatabaseException : LoadDatabaseException {
constructor(exception: Throwable) : super(exception) constructor(exception: Throwable) : super(exception)
} }
class KeyFileEmptyDatabaseException : LoadDatabaseException {
@StringRes
override var errorId: Int = R.string.keyfile_is_empty
constructor() : super()
constructor(exception: Throwable) : super(exception)
}
class NoMemoryDatabaseException: LoadDatabaseException { class NoMemoryDatabaseException: LoadDatabaseException {
@StringRes @StringRes
override var errorId: Int = R.string.error_out_of_memory override var errorId: Int = R.string.error_out_of_memory

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.file.input package com.kunzisoft.keepass.database.file.input
import android.util.Base64 import android.util.Base64
import android.util.Log
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.StreamCipherFactory import com.kunzisoft.keepass.crypto.StreamCipherFactory
@@ -742,8 +743,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
if (entryInHistory) { if (entryInHistory) {
ctxEntry = ctxHistoryBase ctxEntry = ctxHistoryBase
return KdbContext.EntryHistory return KdbContext.EntryHistory
} } else if (ctxEntry != null) {
else if (ctxEntry != null) {
// Add entry to the index only when close the XML element // Add entry to the index only when close the XML element
mDatabase.addEntryIndex(ctxEntry!!) mDatabase.addEntryIndex(ctxEntry!!)
} }
@@ -879,9 +879,14 @@ class DatabaseInputKDBX(cacheDirectory: File,
if (encoded.isEmpty()) { if (encoded.isEmpty()) {
return DatabaseVersioned.UUID_ZERO return DatabaseVersioned.UUID_ZERO
} }
val buf = Base64.decode(encoded, BASE_64_FLAG)
return bytes16ToUuid(buf) return try {
val buf = Base64.decode(encoded, BASE_64_FLAG)
bytes16ToUuid(buf)
} catch (e: Exception) {
Log.e(TAG, "Unable to read base 64 UUID, create a random one", e)
UUID.randomUUID()
}
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
@@ -981,12 +986,19 @@ class DatabaseInputKDBX(cacheDirectory: File,
val base64 = readString(xpp) val base64 = readString(xpp)
if (base64.isEmpty()) if (base64.isEmpty())
return null return null
val data = Base64.decode(base64, BASE_64_FLAG)
// Build the new binary and compress // Build the new binary and compress
val binaryAttachment = mDatabase.buildNewBinary(cacheDirectory, protected, compressed, binaryId) val binaryAttachment = mDatabase.buildNewBinary(cacheDirectory, protected, compressed, binaryId)
try {
binaryAttachment.getOutputDataStream().use { outputStream -> binaryAttachment.getOutputDataStream().use { outputStream ->
outputStream.write(data) outputStream.write(Base64.decode(base64, BASE_64_FLAG))
}
} catch (e: Exception) {
Log.e(TAG, "Unable to read base 64 attachment", e)
binaryAttachment.isCorrupted = true
binaryAttachment.getOutputDataStream().use { outputStream ->
outputStream.write(base64.toByteArray())
}
} }
return binaryAttachment return binaryAttachment
} }
@@ -1045,6 +1057,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
companion object { companion object {
private val TAG = DatabaseInputKDBX::class.java.name
private val DEFAULT_HISTORY_DAYS = UnsignedInt(365) private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
@Throws(XmlPullParserException::class) @Throws(XmlPullParserException::class)

View File

@@ -21,7 +21,10 @@ package com.kunzisoft.keepass.model
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import java.util.* import java.util.*
@@ -30,12 +33,15 @@ class EntryInfo : Parcelable {
var id: String = "" var id: String = ""
var title: String = "" var title: String = ""
var icon: IconImage? = null var icon: IconImage = IconImageStandard()
var username: String = "" var username: String = ""
var password: String = "" var password: String = ""
var expires: Boolean = false
var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH
var url: String = "" var url: String = ""
var notes: String = "" var notes: String = ""
var customFields: MutableList<Field> = ArrayList() var customFields: List<Field> = ArrayList()
var attachments: List<Attachment> = ArrayList()
var otpModel: OtpModel? = null var otpModel: OtpModel? = null
constructor() constructor()
@@ -43,12 +49,15 @@ class EntryInfo : Parcelable {
private constructor(parcel: Parcel) { private constructor(parcel: Parcel) {
id = parcel.readString() ?: id id = parcel.readString() ?: id
title = parcel.readString() ?: title title = parcel.readString() ?: title
icon = parcel.readParcelable(IconImage::class.java.classLoader) icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
username = parcel.readString() ?: username username = parcel.readString() ?: username
password = parcel.readString() ?: password password = parcel.readString() ?: password
expires = parcel.readInt() != 0
expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
url = parcel.readString() ?: url url = parcel.readString() ?: url
notes = parcel.readString() ?: notes notes = parcel.readString() ?: notes
parcel.readList(customFields as List<Field>, Field::class.java.classLoader) parcel.readList(customFields, Field::class.java.classLoader)
parcel.readList(attachments, Attachment::class.java.classLoader)
otpModel = parcel.readParcelable(OtpModel::class.java.classLoader) ?: otpModel otpModel = parcel.readParcelable(OtpModel::class.java.classLoader) ?: otpModel
} }
@@ -62,9 +71,12 @@ class EntryInfo : Parcelable {
parcel.writeParcelable(icon, flags) parcel.writeParcelable(icon, flags)
parcel.writeString(username) parcel.writeString(username)
parcel.writeString(password) parcel.writeString(password)
parcel.writeInt(if (expires) 1 else 0)
parcel.writeParcelable(expiryTime, flags)
parcel.writeString(url) parcel.writeString(url)
parcel.writeString(notes) parcel.writeString(notes)
parcel.writeArray(customFields.toTypedArray()) parcel.writeArray(customFields.toTypedArray())
parcel.writeArray(attachments.toTypedArray())
parcel.writeParcelable(otpModel, flags) parcel.writeParcelable(otpModel, flags)
} }

View File

@@ -58,15 +58,9 @@ class AttachmentFileNotificationService: LockNotificationService() {
fun addActionTaskListener(actionTaskListener: ActionTaskListener) { fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.add(actionTaskListener) mActionTaskListeners.add(actionTaskListener)
attachmentNotificationList.forEach {
it.attachmentFileAction?.listener = attachmentFileActionListener
}
} }
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) { fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
attachmentNotificationList.forEach {
it.attachmentFileAction?.listener = null
}
mActionTaskListeners.remove(actionTaskListener) mActionTaskListeners.remove(actionTaskListener)
} }
} }
@@ -107,6 +101,13 @@ class AttachmentFileNotificationService: LockNotificationService() {
intent, intent,
StreamDirection.DOWNLOAD) StreamDirection.DOWNLOAD)
} }
ACTION_ATTACHMENT_REMOVE -> {
intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
attachmentNotificationList.firstOrNull { it.entryAttachmentState.attachment == entryAttachment }?.let { elementToRemove ->
attachmentNotificationList.remove(elementToRemove)
}
}
}
else -> { else -> {
if (downloadFileUri != null) { if (downloadFileUri != null) {
attachmentNotificationList.firstOrNull { it.uri == downloadFileUri }?.let { elementToRemove -> attachmentNotificationList.firstOrNull { it.uri == downloadFileUri }?.let { elementToRemove ->
@@ -265,6 +266,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
?.notificationId ?: notificationId) + 1 ?.notificationId ?: notificationId) + 1
val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection) val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection)
val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState) val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState)
// Add action to the list on start
attachmentNotificationList.add(attachmentNotification) attachmentNotificationList.add(attachmentNotification)
mainScope.launch { mainScope.launch {
@@ -293,15 +296,18 @@ class AttachmentFileNotificationService: LockNotificationService() {
} }
suspend fun executeAction() { suspend fun executeAction() {
TimeoutHelper.temporarilyDisableTimeout()
// on pre execute // on pre execute
attachmentNotification.attachmentFileAction = this CoroutineScope(Dispatchers.Main).launch {
TimeoutHelper.temporarilyDisableTimeout()
attachmentNotification.attachmentFileAction = this@AttachmentFileAction
attachmentNotification.entryAttachmentState.apply { attachmentNotification.entryAttachmentState.apply {
downloadState = AttachmentState.START downloadState = AttachmentState.START
downloadProgression = 0 downloadProgression = 0
} }
listener?.onUpdate(attachmentNotification) listener?.onUpdate(attachmentNotification)
}
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
// on Progress with thread // on Progress with thread
@@ -426,6 +432,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD" const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD"
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD" const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
const val ACTION_ATTACHMENT_REMOVE = "ACTION_ATTACHMENT_REMOVE"
const val FILE_URI_KEY = "FILE_URI_KEY" const val FILE_URI_KEY = "FILE_URI_KEY"
const val ATTACHMENT_KEY = "ATTACHMENT_KEY" const val ATTACHMENT_KEY = "ATTACHMENT_KEY"

View File

@@ -141,6 +141,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent) ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent)
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent) ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent)
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent) ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent)
ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK -> buildDatabaseRemoveUnlinkedDataActionTask(intent)
ACTION_DATABASE_UPDATE_NAME_TASK, ACTION_DATABASE_UPDATE_NAME_TASK,
ACTION_DATABASE_UPDATE_DESCRIPTION_TASK, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK,
ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK, ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK,
@@ -711,6 +712,22 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
} }
} }
private fun buildDatabaseRemoveUnlinkedDataActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
return RemoveUnlinkedDataDatabaseRunnable(this,
mDatabase,
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
).apply {
mAfterSaveDatabase = { result ->
result.data = intent.extras
}
}
} else {
null
}
}
private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? { private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(SAVE_DATABASE_KEY)) { return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
return SaveDatabaseRunnable(this, return SaveDatabaseRunnable(this,
@@ -760,6 +777,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK" const val ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK"
const val ACTION_DATABASE_UPDATE_COLOR_TASK = "ACTION_DATABASE_UPDATE_COLOR_TASK" const val ACTION_DATABASE_UPDATE_COLOR_TASK = "ACTION_DATABASE_UPDATE_COLOR_TASK"
const val ACTION_DATABASE_UPDATE_COMPRESSION_TASK = "ACTION_DATABASE_UPDATE_COMPRESSION_TASK" const val ACTION_DATABASE_UPDATE_COMPRESSION_TASK = "ACTION_DATABASE_UPDATE_COMPRESSION_TASK"
const val ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK = "ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK"
const val ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK" const val ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK"
const val ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK" const val ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK"
const val ACTION_DATABASE_UPDATE_ENCRYPTION_TASK = "ACTION_DATABASE_UPDATE_ENCRYPTION_TASK" const val ACTION_DATABASE_UPDATE_ENCRYPTION_TASK = "ACTION_DATABASE_UPDATE_ENCRYPTION_TASK"

View File

@@ -347,7 +347,7 @@ object OtpEntryFields {
* Build new generated fields in a new list from [fieldsToParse] in parameter, * Build new generated fields in a new list from [fieldsToParse] in parameter,
* Remove parameters fields use to generate auto fields * Remove parameters fields use to generate auto fields
*/ */
fun generateAutoFields(fieldsToParse: MutableList<Field>): MutableList<Field> { fun generateAutoFields(fieldsToParse: List<Field>): MutableList<Field> {
val newCustomFields: MutableList<Field> = ArrayList(fieldsToParse) val newCustomFields: MutableList<Field> = ArrayList(fieldsToParse)
// Remove parameter fields // Remove parameter fields
val otpField = Field(OTP_FIELD) val otpField = Field(OTP_FIELD)

View File

@@ -40,6 +40,11 @@ class MainPreferenceFragment : PreferenceFragmentCompat() {
} }
} }
override fun onDetach() {
mCallback = null
super.onDetach()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey) setPreferencesFromResource(R.xml.preferences, rootKey)

View File

@@ -135,7 +135,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
findPreference<Preference>(getString(R.string.database_version_key)) findPreference<Preference>(getString(R.string.database_version_key))
?.summary = mDatabase.version ?.summary = mDatabase.version
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_compression_key)) val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_data_key))
// Database compression // Database compression
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key)) dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
@@ -482,6 +482,9 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
getString(R.string.database_data_compression_key) -> { getString(R.string.database_data_compression_key) -> {
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key) dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
} }
getString(R.string.database_data_remove_unlinked_attachments_key) -> {
dialogFragment = DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat.newInstance(preference.key)
}
getString(R.string.max_history_items_key) -> { getString(R.string.max_history_items_key) -> {
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key) dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
} }

View File

@@ -42,7 +42,8 @@ import com.kunzisoft.keepass.view.showActionError
open class SettingsActivity open class SettingsActivity
: LockingActivity(), : LockingActivity(),
MainPreferenceFragment.Callback, MainPreferenceFragment.Callback,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener { AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
PasswordEncodingDialogFragment.Listener {
private var backupManager: BackupManager? = null private var backupManager: BackupManager? = null
@@ -50,23 +51,6 @@ open class SettingsActivity
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
private var lockView: View? = null private var lockView: View? = null
companion object {
private const val SHOW_LOCK = "SHOW_LOCK"
private const val TAG_NESTED = "TAG_NESTED"
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
val intent = Intent(activity, SettingsActivity::class.java)
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
intent.putExtra(TIMEOUT_ENABLE_KEY, timeoutEnable)
if (!timeoutEnable) {
activity.startActivity(intent)
} else if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
activity.startActivity(intent)
}
}
}
/** /**
* Retrieve the main fragment to show in first * Retrieve the main fragment to show in first
* @return The main fragment * @return The main fragment
@@ -83,7 +67,11 @@ open class SettingsActivity
coordinatorLayout = findViewById(R.id.toolbar_coordinator) coordinatorLayout = findViewById(R.id.toolbar_coordinator)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty())
toolbar?.setTitle(R.string.settings) toolbar?.setTitle(R.string.settings)
else
toolbar?.title = savedInstanceState?.getString(TITLE_KEY)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
@@ -128,6 +116,22 @@ open class SettingsActivity
super.onStop() super.onStop()
} }
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
masterPasswordChecked: Boolean,
masterPassword: String?,
keyFileChecked: Boolean,
keyFile: Uri?) {
databaseUri?.let {
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
masterPasswordChecked,
masterPassword,
keyFileChecked,
keyFile
)
}
}
override fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, override fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean,
masterPassword: String?, masterPassword: String?,
keyFileChecked: Boolean, keyFileChecked: Boolean,
@@ -144,18 +148,12 @@ open class SettingsActivity
keyFile keyFile
) )
} else { } else {
PasswordEncodingDialogFragment().apply { PasswordEncodingDialogFragment.getInstance(databaseUri,
positiveButtonClickListener = DialogInterface.OnClickListener { _, _ ->
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
masterPasswordChecked, masterPasswordChecked,
masterPassword, masterPassword,
keyFileChecked, keyFileChecked,
keyFile keyFile
) ).show(supportFragmentManager, "passwordEncodingTag")
}
show(supportFragmentManager, "passwordEncodingTag")
}
} }
} }
} }
@@ -164,8 +162,7 @@ open class SettingsActivity
override fun onAssignKeyDialogNegativeClick(masterPasswordChecked: Boolean, override fun onAssignKeyDialogNegativeClick(masterPasswordChecked: Boolean,
masterPassword: String?, masterPassword: String?,
keyFileChecked: Boolean, keyFileChecked: Boolean,
keyFile: Uri?) { keyFile: Uri?) {}
}
private fun hideOrShowLockButton(key: NestedSettingsFragment.Screen) { private fun hideOrShowLockButton(key: NestedSettingsFragment.Screen) {
if (PreferencesUtil.showLockDatabaseButton(this)) { if (PreferencesUtil.showLockDatabaseButton(this)) {
@@ -220,5 +217,24 @@ open class SettingsActivity
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putBoolean(SHOW_LOCK, lockView?.visibility == View.VISIBLE) outState.putBoolean(SHOW_LOCK, lockView?.visibility == View.VISIBLE)
outState.putString(TITLE_KEY, toolbar?.title?.toString())
}
companion object {
private const val SHOW_LOCK = "SHOW_LOCK"
private const val TITLE_KEY = "TITLE_KEY"
private const val TAG_NESTED = "TAG_NESTED"
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
val intent = Intent(activity, SettingsActivity::class.java)
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
intent.putExtra(TIMEOUT_ENABLE_KEY, timeoutEnable)
if (!timeoutEnable) {
activity.startActivity(intent)
} else if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
activity.startActivity(intent)
}
}
} }
} }

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import androidx.preference.DialogPreference
import android.util.AttributeSet
import com.kunzisoft.keepass.R
open class TextPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
override fun getDialogLayoutResource(): Int {
return R.layout.pref_dialog_text
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.View
import com.kunzisoft.keepass.R
class DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
explanationText = SpannableStringBuilder().apply {
append(getString(R.string.warning_remove_unlinked_attachment))
append("\n\n")
append(getString(R.string.warning_sure_remove_data))
}.toString()
}
override fun onDialogClosed(positiveResult: Boolean) {
database?.let { _ ->
if (positiveResult) {
mProgressDatabaseTaskProvider?.startDatabaseRemoveUnlinkedData(mDatabaseAutoSaveEnable)
}
}
}
companion object {
fun newInstance(key: String): DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat {
val fragment = DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -48,6 +48,11 @@ abstract class DatabaseSavePreferenceDialogFragmentCompat : InputPreferenceDialo
this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context) this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
} }
override fun onDetach() {
mProgressDatabaseTaskProvider = null
super.onDetach()
}
companion object { companion object {
private const val TAG = "DbSavePrefDialog" private const val TAG = "DbSavePrefDialog"
} }

View File

@@ -34,6 +34,7 @@ import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_REMOVE
class AttachmentFileBinderManager(private val activity: FragmentActivity) { class AttachmentFileBinderManager(private val activity: FragmentActivity) {
@@ -53,7 +54,7 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
AttachmentState.COMPLETE, AttachmentState.COMPLETE,
AttachmentState.ERROR -> { AttachmentState.ERROR -> {
// Finish the action when capture by activity // Finish the action when capture by activity
consummeAttachmentAction(entryAttachmentState) consumeAttachmentAction(entryAttachmentState)
} }
else -> {} else -> {}
} }
@@ -99,7 +100,7 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
} }
@Synchronized @Synchronized
fun consummeAttachmentAction(attachment: EntryAttachmentState) { fun consumeAttachmentAction(attachment: EntryAttachmentState) {
mBinder?.getService()?.removeAttachmentAction(attachment) mBinder?.getService()?.removeAttachmentAction(attachment)
} }
@@ -126,4 +127,10 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment) putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
}, ACTION_ATTACHMENT_FILE_START_DOWNLOAD) }, ACTION_ATTACHMENT_FILE_START_DOWNLOAD)
} }
fun removeBinaryAttachment(attachment: Attachment) {
start(Bundle().apply {
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
}, ACTION_ATTACHMENT_REMOVE)
}
} }

View File

@@ -1,42 +0,0 @@
package com.kunzisoft.keepass.view
import android.content.Context
import android.util.AttributeSet
import com.google.android.material.textfield.TextInputEditText
class EditTextSelectable @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: TextInputEditText(context, attrs) {
// TODO constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
// after material design upgrade
private val mOnSelectionChangedListeners: MutableList<OnSelectionChangedListener>?
init {
mOnSelectionChangedListeners = ArrayList()
}
fun addOnSelectionChangedListener(onSelectionChangedListener: OnSelectionChangedListener) {
mOnSelectionChangedListeners?.add(onSelectionChangedListener)
}
fun removeOnSelectionChangedListener(onSelectionChangedListener: OnSelectionChangedListener) {
mOnSelectionChangedListeners?.remove(onSelectionChangedListener)
}
fun removeAllOnSelectionChangedListeners() {
mOnSelectionChangedListeners?.clear()
}
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
mOnSelectionChangedListeners?.forEach {
it.onSelectionChanged(selStart, selEnd)
}
}
interface OnSelectionChangedListener {
fun onSelectionChanged(start: Int, end: Int)
}
}

View File

@@ -27,15 +27,16 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.search.UuidUtil import com.kunzisoft.keepass.database.search.UuidUtil
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
@@ -133,6 +134,17 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
this.fontInVisibility = fontInVisibility this.fontInVisibility = fontInVisibility
} }
fun firstEntryFieldCopyView(): View? {
return when {
userNameFieldView.isVisible && userNameFieldView.copyButtonView.isVisible -> userNameFieldView.copyButtonView
passwordFieldView.isVisible && passwordFieldView.copyButtonView.isVisible -> passwordFieldView.copyButtonView
otpFieldView.isVisible && otpFieldView.copyButtonView.isVisible -> otpFieldView.copyButtonView
urlFieldView.isVisible && urlFieldView.copyButtonView.isVisible -> urlFieldView.copyButtonView
notesFieldView.isVisible && notesFieldView.copyButtonView.isVisible -> notesFieldView.copyButtonView
else -> null
}
}
fun assignUserName(userName: String?, fun assignUserName(userName: String?,
onClickListener: OnClickListener?) { onClickListener: OnClickListener?) {
userNameFieldView.apply { userNameFieldView.apply {

View File

@@ -1,359 +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.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
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.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
class EntryEditContentsView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: LinearLayout(context, attrs, defStyle) {
private var fontInVisibility: Boolean = false
private val entryTitleLayoutView: TextInputLayout
private val entryTitleView: EditText
private val entryIconView: ImageView
private val entryUserNameView: EditText
private val entryUrlView: EditText
private val entryPasswordLayoutView: TextInputLayout
private val entryPasswordView: EditText
val entryPasswordGeneratorView: View
private val entryExpiresCheckBox: CompoundButton
private val entryExpiresTextView: TextView
private val entryNotesView: EditText
private val extraFieldsContainerView: ViewGroup
private val extraFieldsListView: RecyclerView
private val attachmentsContainerView: View
private val attachmentsListView: RecyclerView
private val extraFieldsAdapter = EntryExtraFieldsItemsAdapter(context)
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
private var iconColor: Int = 0
private var expiresInstant: DateInstant = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
var onDateClickListener: OnClickListener? = null
set(value) {
field = value
if (entryExpiresCheckBox.isChecked)
entryExpiresTextView.setOnClickListener(value)
else
entryExpiresTextView.setOnClickListener(null)
}
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.view_entry_edit_contents, this)
entryTitleLayoutView = findViewById(R.id.entry_edit_container_title)
entryTitleView = findViewById(R.id.entry_edit_title)
entryIconView = findViewById(R.id.entry_edit_icon_button)
entryUserNameView = findViewById(R.id.entry_edit_user_name)
entryUrlView = findViewById(R.id.entry_edit_url)
entryPasswordLayoutView = findViewById(R.id.entry_edit_container_password)
entryPasswordView = findViewById(R.id.entry_edit_password)
entryPasswordGeneratorView = findViewById(R.id.entry_edit_password_generator_button)
entryExpiresCheckBox = findViewById(R.id.entry_edit_expires_checkbox)
entryExpiresTextView = findViewById(R.id.entry_edit_expires_text)
entryNotesView = findViewById(R.id.entry_edit_notes)
extraFieldsContainerView = findViewById(R.id.extra_fields_container)
extraFieldsListView = findViewById(R.id.extra_fields_list)
// To hide or not the container
extraFieldsAdapter.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) {
extraFieldsContainerView.collapse(true)
} else if (previousSize == 0 && newSize == 1) {
extraFieldsContainerView.expand(true)
}
}
extraFieldsListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = extraFieldsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
attachmentsContainerView = findViewById(R.id.entry_attachments_container)
attachmentsListView = findViewById(R.id.entry_attachments_list)
attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true)
} else if (previousSize == 0 && newSize == 1) {
attachmentsContainerView.expand(true)
}
}
attachmentsListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
entryExpiresCheckBox.setOnCheckedChangeListener { _, _ ->
assignExpiresDateText()
}
// Retrieve the textColor to tint the icon
val taIconColor = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
iconColor = taIconColor.getColor(0, Color.WHITE)
taIconColor.recycle()
}
fun applyFontVisibilityToFields(fontInVisibility: Boolean) {
this.fontInVisibility = fontInVisibility
this.extraFieldsAdapter.applyFontVisibility = fontInVisibility
}
var title: String
get() {
return entryTitleView.text.toString()
}
set(value) {
entryTitleView.setText(value)
if (fontInVisibility)
entryTitleView.applyFontVisibility()
}
fun setDefaultIcon(iconFactory: IconDrawableFactory) {
entryIconView.assignDefaultDatabaseIcon(iconFactory, iconColor)
}
fun setIcon(iconFactory: IconDrawableFactory, icon: IconImage) {
entryIconView.assignDatabaseIcon(iconFactory, icon, iconColor)
}
fun setOnIconViewClickListener(clickListener: () -> Unit) {
entryIconView.setOnClickListener { clickListener.invoke() }
}
var username: String
get() {
return entryUserNameView.text.toString()
}
set(value) {
entryUserNameView.setText(value)
if (fontInVisibility)
entryUserNameView.applyFontVisibility()
}
var url: String
get() {
return entryUrlView.text.toString()
}
set(value) {
entryUrlView.setText(value)
if (fontInVisibility)
entryUrlView.applyFontVisibility()
}
var password: String
get() {
return entryPasswordView.text.toString()
}
set(value) {
entryPasswordView.setText(value)
if (fontInVisibility) {
entryPasswordView.applyFontVisibility()
}
}
private fun assignExpiresDateText() {
entryExpiresTextView.text = if (entryExpiresCheckBox.isChecked) {
entryExpiresTextView.setOnClickListener(onDateClickListener)
expiresInstant.getDateTimeString(resources)
} else {
entryExpiresTextView.setOnClickListener(null)
resources.getString(R.string.never)
}
if (fontInVisibility)
entryExpiresTextView.applyFontVisibility()
}
var expires: Boolean
get() {
return entryExpiresCheckBox.isChecked
}
set(value) {
entryExpiresCheckBox.isChecked = value
assignExpiresDateText()
}
var expiresDate: DateInstant
get() {
return expiresInstant
}
set(value) {
expiresInstant = value
assignExpiresDateText()
}
var notes: String
get() {
return entryNotesView.text.toString()
}
set(value) {
entryNotesView.setText(value)
if (fontInVisibility)
entryNotesView.applyFontVisibility()
}
/* -------------
* Extra Fields
* -------------
*/
fun getExtraFields(): List<Field> {
return extraFieldsAdapter.itemsList
}
fun getExtraFieldFocused(): FocusedEditField {
// To keep focused after an orientation change
return extraFieldsAdapter.getFocusedField()
}
/**
* Remove all children and add new views for each field
*/
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
}
/**
* Update an extra field or create a new one if doesn't exists
*/
fun putExtraField(extraField: Field) {
extraFieldsContainerView.visibility = View.VISIBLE
val oldField = extraFieldsAdapter.itemsList.firstOrNull { it.name == extraField.name }
oldField?.let {
if (extraField.protectedValue.stringValue.isEmpty())
extraField.protectedValue.stringValue = it.protectedValue.stringValue
}
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 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)
}
/**
* Validate or not the entry form
*
* @return ErrorValidation An error with a message or a validation without message
*/
fun isValid(): Boolean {
// TODO
return true
}
}

View File

@@ -39,7 +39,7 @@ class EntryField @JvmOverloads constructor(context: Context,
private val labelView: TextView private val labelView: TextView
private val valueView: TextView private val valueView: TextView
private val showButtonView: ImageView private val showButtonView: ImageView
private val copyButtonView: ImageView val copyButtonView: ImageView
private var isProtected = false private var isProtected = false
var hiddenProtectedValue: Boolean var hiddenProtectedValue: Boolean

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M 10.191406 1 C 7.8864287 1 6 2.8866579 6 5.1914062 L 6 13.976562 L 7.5722656 13.355469 L 7.5722656 5.1914062 C 7.5722656 3.7248197 8.7248288 2.5722656 10.191406 2.5722656 C 11.657948 2.5722656 12.808594 3.7248197 12.808594 5.1914062 L 12.808594 9.2851562 L 14.380859 8.6640625 L 14.380859 5.1914062 C 14.380859 2.8866579 12.496402 1 10.191406 1 z M 15.953125 5.7148438 L 15.953125 8.0410156 L 17.523438 7.421875 L 17.523438 5.7148438 L 15.953125 5.7148438 z M 9.1425781 7.2851562 L 9.1425781 12.734375 L 10.714844 12.113281 L 10.714844 7.2851562 L 9.1425781 7.2851562 z M 17.523438 11.722656 L 15.953125 12.34375 L 15.953125 17.238281 C 15.953125 19.543273 14.066714 21.427734 11.761719 21.427734 C 9.5944825 21.427734 7.8200908 19.755748 7.6132812 17.640625 L 6.0917969 18.242188 C 6.5643368 20.953226 8.9101628 23 11.761719 23 C 14.956723 23 17.523438 20.433343 17.523438 17.238281 L 17.523438 11.722656 z M 14.380859 12.964844 L 12.808594 13.587891 L 12.808594 17.238281 C 12.808594 17.814256 12.337701 18.285156 11.761719 18.285156 C 11.185737 18.285156 10.714844 17.814256 10.714844 17.238281 L 10.714844 16.414062 L 9.1425781 17.035156 L 9.1425781 17.238281 C 9.1426137 18.704818 10.295141 19.857422 11.761719 19.857422 C 13.228314 19.857422 14.380859 18.704818 14.380859 17.238281 L 14.380859 12.964844 z" />
</vector>

View File

@@ -54,7 +54,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.kunzisoft.keepass.view.EntryEditContentsView <androidx.fragment.app.FragmentContainerView
android:id="@+id/entry_edit_contents" android:id="@+id/entry_edit_contents"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -209,14 +209,12 @@
android:layout_marginBottom="@dimen/card_view_margin_bottom" android:layout_marginBottom="@dimen/card_view_margin_bottom"
app:layout_constraintTop_toBottomOf="@+id/entry_edit_container" app:layout_constraintTop_toBottomOf="@+id/entry_edit_container"
app:layout_constraintBottom_toTopOf="@+id/entry_attachments_container"> app:layout_constraintBottom_toTopOf="@+id/entry_attachments_container">
<androidx.recyclerview.widget.RecyclerView <LinearLayout
android:id="@+id/extra_fields_list" android:id="@+id/extra_fields_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:layout_margin="@dimen/card_view_padding" android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical"> android:orientation="vertical" />
</androidx.recyclerview.widget.RecyclerView>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView

View File

@@ -28,6 +28,29 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:targetApi="o"> tools:targetApi="o">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
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/credentials_information"
android:text="@string/assign_master_key"
style="@style/KeepassDXStyle.TextAppearance.Large"/>
<ImageView
android:id="@+id/credentials_information"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/ic_info_white_24dp"
style="@style/KeepassDXStyle.ImageButton.Simple"
android:contentDescription="@string/content_description_credentials_information"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_view_master_password" android:id="@+id/card_view_master_password"
android:layout_margin="4dp" android:layout_margin="4dp"

View File

@@ -27,10 +27,21 @@
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/item_attachment_broken"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:src="@drawable/ic_attach_file_broken_white_24dp"
android:contentDescription="@string/entry_attachments" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/item_attachment_title" android:id="@+id/item_attachment_title"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/item_attachment_broken"
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container" app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -105,6 +116,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_gravity="center"
style="@style/KeepassDXStyle.ProgressBar.Circle" style="@style/KeepassDXStyle.ProgressBar.Circle"
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="36dp" android:layout_height="36dp"

View File

@@ -34,7 +34,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_edit"> app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_edit">
<com.kunzisoft.keepass.view.EditTextSelectable <com.google.android.material.textfield.TextInputEditText
android:id="@+id/entry_extra_field_value" android:id="@+id/entry_extra_field_value"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Jeremy Jamet / Kunzisoft.
This file is part of KeePassDX.
KeePassDX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePassDX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/edit"
android:padding="20dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/explanation_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_element"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable"
app:layout_constraintTop_toBottomOf="@+id/explanation_text"
app:layout_constraintStart_toStartOf="parent"
android:minHeight="48dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@@ -93,8 +93,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/card_view_padding" android:layout_margin="@dimen/card_view_padding"
android:orientation="vertical"> android:orientation="vertical" />
</LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView

View File

@@ -183,7 +183,7 @@
<string name="list_password_generator_options_summary">Défini les caractères autorisés du générateur de mot de passe</string> <string name="list_password_generator_options_summary">Défini les caractères autorisés du générateur de mot de passe</string>
<string name="clipboard">Presse-papier</string> <string name="clipboard">Presse-papier</string>
<string name="clipboard_notifications_title">Notifications du presse-papier</string> <string name="clipboard_notifications_title">Notifications du presse-papier</string>
<string name="clipboard_notifications_summary">Active les notifications du presse-papier pour copier les champs lors de laffichage dune entrée</string> <string name="clipboard_notifications_summary">Affiche les notifications du presse-papier pour copier les champs lors de laffichage dune entrée</string>
<string name="clipboard_warning">Si la suppression automatique du presse-papier échoue, supprimer son historique manuellement.</string> <string name="clipboard_warning">Si la suppression automatique du presse-papier échoue, supprimer son historique manuellement.</string>
<string name="lock">Verrouiller</string> <string name="lock">Verrouiller</string>
<string name="lock_database_screen_off_title">Verrouillage décran</string> <string name="lock_database_screen_off_title">Verrouillage décran</string>
@@ -195,7 +195,7 @@
<string name="biometric_delete_all_key_summary">Supprime toutes les clés de chiffrement liées à la reconnaissance biométrique</string> <string name="biometric_delete_all_key_summary">Supprime toutes les clés de chiffrement liées à la reconnaissance biométrique</string>
<string name="biometric_delete_all_key_warning">Supprimer toutes les clés de chiffrement liées à la reconnaissance biométrique\?</string> <string name="biometric_delete_all_key_warning">Supprimer toutes les clés de chiffrement liées à la reconnaissance biométrique\?</string>
<string name="unavailable_feature_text">Impossible de démarrer cette fonctionnalité.</string> <string name="unavailable_feature_text">Impossible de démarrer cette fonctionnalité.</string>
<string name="unavailable_feature_version">Votre version dAndroid %1$s est inférieure à la version minimale %2$s requise.</string> <string name="unavailable_feature_version">Lappareil tourne sous Android %1$s, mais la version %2$s ou supérieure est requise.</string>
<string name="unavailable_feature_hardware">Impossible de trouver le matériel correspondant.</string> <string name="unavailable_feature_hardware">Impossible de trouver le matériel correspondant.</string>
<string name="file_name">Nom de fichier</string> <string name="file_name">Nom de fichier</string>
<string name="path">Chemin daccès</string> <string name="path">Chemin daccès</string>
@@ -232,7 +232,7 @@
\nLes groupes (≈dossiers) organisent les entrées dans votre base de données.</string> \nLes groupes (≈dossiers) organisent les entrées dans votre base de données.</string>
<string name="education_search_title">Rechercher dans les entrées</string> <string name="education_search_title">Rechercher dans les entrées</string>
<string name="education_search_summary">Saisir le titre, le nom dutilisateur ou le contenu des autres champs pour récupérer vos mots de passe.</string> <string name="education_search_summary">Saisir le titre, le nom dutilisateur ou le contenu des autres champs pour récupérer vos mots de passe.</string>
<string name="education_biometric_title">Déverrouillage de la base de données par la biométrie</string> <string name="education_biometric_title">Déverrouillage biométrique de la base de données</string>
<string name="education_biometric_summary">Associe votre mot de passe à votre empreinte biométrique numérisée pour déverrouiller rapidement votre base de données.</string> <string name="education_biometric_summary">Associe votre mot de passe à votre empreinte biométrique numérisée pour déverrouiller rapidement votre base de données.</string>
<string name="education_entry_edit_title">Modifier lentrée</string> <string name="education_entry_edit_title">Modifier lentrée</string>
<string name="education_entry_edit_summary">Modifie votre entrée avec des champs personnalisés. La collection des données peut être référencée entre différents champs de lentrée.</string> <string name="education_entry_edit_summary">Modifie votre entrée avec des champs personnalisés. La collection des données peut être référencée entre différents champs de lentrée.</string>
@@ -307,7 +307,7 @@
<string name="menu_paste">Coller</string> <string name="menu_paste">Coller</string>
<string name="menu_cancel">Annuler</string> <string name="menu_cancel">Annuler</string>
<string name="allow_no_password_title">Autoriser l\'absence de clé principale</string> <string name="allow_no_password_title">Autoriser l\'absence de clé principale</string>
<string name="allow_no_password_summary">Active le bouton «Ouvrir» si aucun identifiant nest sélectionné</string> <string name="allow_no_password_summary">Autorise lappui du bouton «Ouvrir» si aucun identifiant nest sélectionné</string>
<string name="menu_file_selection_read_only">Protéger en écriture</string> <string name="menu_file_selection_read_only">Protéger en écriture</string>
<string name="menu_open_file_read_and_write">Modifiable</string> <string name="menu_open_file_read_and_write">Modifiable</string>
<string name="enable_read_only_title">Protégé en écriture</string> <string name="enable_read_only_title">Protégé en écriture</string>
@@ -454,24 +454,24 @@
<string name="download_initialization">Initialisation…</string> <string name="download_initialization">Initialisation…</string>
<string name="download_progression">En cours : %1$d%%</string> <string name="download_progression">En cours : %1$d%%</string>
<string name="download_finalization">Finalisation…</string> <string name="download_finalization">Finalisation…</string>
<string name="download_complete">Terminé !</string> <string name="download_complete">Terminé!</string>
<string name="hide_expired_entries_title">Masquer les entrées expirées</string> <string name="hide_expired_entries_title">Masquer les entrées expirées</string>
<string name="hide_expired_entries_summary">Les entrées expirées sont cachées</string> <string name="hide_expired_entries_summary">Les entrées expirées ne sont pas affichées</string>
<string name="contact">Contact</string> <string name="contact">Contact</string>
<string name="contribution">Contribution</string> <string name="contribution">Contribution</string>
<string name="html_about_contribution">Afin de &lt;strong&gt;garder notre liberté&lt;/strong&gt;, &lt;strong&gt;corriger les bugs&lt;/strong&gt;, &lt;strong&gt;ajouter des fonctionnalités&lt;/strong&gt; et &lt;strong&gt;être toujours actif&lt;/strong&gt;, nous comptons sur votre &lt;strong&gt;contribution&lt;/strong&gt;.</string> <string name="html_about_contribution">Afin de &lt;strong&gt;garder notre liberté&lt;/strong&gt;, &lt;strong&gt;corriger les bugs&lt;/strong&gt;, &lt;strong&gt;ajouter des fonctionnalités&lt;/strong&gt; et &lt;strong&gt;être toujours actif&lt;/strong&gt;, nous comptons sur votre &lt;strong&gt;contribution&lt;/strong&gt;.</string>
<string name="auto_focus_search_title">Recherche rapide</string> <string name="auto_focus_search_title">Recherche rapide</string>
<string name="auto_focus_search_summary">Demander une recherche lors de l\'ouverture d\'une base de données</string> <string name="auto_focus_search_summary">Demander une recherche lors de l\'ouverture d\'une base de données</string>
<string name="remember_database_locations_title">Enregistrer l\'emplacement des bases de données</string> <string name="remember_database_locations_title">Mémoriser lemplacement des bases de données</string>
<string name="remember_database_locations_summary">Se souvenir de l\'emplacement des bases de données</string> <string name="remember_database_locations_summary">Garde en mémoire lemplacement où les bases de données sont stockées</string>
<string name="remember_keyfile_locations_title">Enregistrer l\'emplacement des fichiers clés</string> <string name="remember_keyfile_locations_title">Mémoriser les emplacements des fichiers clé</string>
<string name="remember_keyfile_locations_summary">Se souvenir de l\'emplacement des fichiers de clés des bases de données</string> <string name="remember_keyfile_locations_summary">Garde en mémoire lemplacement où les fichiers clé sont stockés</string>
<string name="show_recent_files_title">Afficher les fichiers récents</string> <string name="show_recent_files_title">Afficher les fichiers récents</string>
<string name="show_recent_files_summary">Afficher les emplacements des bases de données récentes</string> <string name="show_recent_files_summary">Afficher les emplacements des bases de données récentes</string>
<string name="hide_broken_locations_title">Masquer les liens rompus de base de données</string> <string name="hide_broken_locations_title">Masquer les liens rompus de base de données</string>
<string name="hide_broken_locations_summary">Masquer les liens rompus dans la liste des bases de données récentes</string> <string name="hide_broken_locations_summary">Masquer les liens rompus dans la liste des bases de données récentes</string>
<string name="warning_database_read_only">Accorder un accès en écriture au fichier pour enregistrer les modifications de la base de données</string> <string name="warning_database_read_only">Accorder un accès en écriture au fichier pour enregistrer les modifications de la base de données</string>
<string name="education_setup_OTP_summary">Définir le mot de passe à usage unique (temporel ou évènementiel) pour générer un jeton demandé par lauthentification à deux facteurs (A2F).</string> <string name="education_setup_OTP_summary">Définit le mot de passe à usage unique (temporel ou évènementiel) pour générer un jeton demandé par lauthentification à deux facteurs (A2F).</string>
<string name="education_setup_OTP_title">Définir lOTP</string> <string name="education_setup_OTP_title">Définir lOTP</string>
<string name="error_create_database">Impossible de créer le fichier de base de données.</string> <string name="error_create_database">Impossible de créer le fichier de base de données.</string>
<string name="entry_add_attachment">Ajouter une pièce jointe</string> <string name="entry_add_attachment">Ajouter une pièce jointe</string>
@@ -498,4 +498,12 @@
<string name="subdomain_search_title">Recherche de sous-domaine</string> <string name="subdomain_search_title">Recherche de sous-domaine</string>
<string name="error_string_type">Ce texte ne correspond pas à l\'élément demandé.</string> <string name="error_string_type">Ce texte ne correspond pas à l\'élément demandé.</string>
<string name="content_description_add_item">Ajouter un élément</string> <string name="content_description_add_item">Ajouter un élément</string>
<string name="upload_attachment">Téléverser %1$s</string>
<string name="education_add_attachment_summary">Téléverse une pièce-jointe à votre entrée pour enregistrer dimportantes données externes.</string>
<string name="education_add_attachment_title">Ajouter une pièce-jointe</string>
<string name="warning_sure_add_file">Ajouter quand même le fichier\?</string>
<string name="warning_replace_file">Téléverser ce fichier va remplacer celui en place.</string>
<string name="warning_file_too_big">Une base de données KeePass est seulement censée contenir de petits fichiers utilitaires (tels que les fichiers clé PGP).
\n
\nVotre base de données peut vraiment devenir grosse et réduire les performances avec ce téléversement.</string>
</resources> </resources>

View File

@@ -473,4 +473,12 @@
<string name="keyboard_previous_database_credentials_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka</string> <string name="keyboard_previous_database_credentials_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka</string>
<string name="keyboard_previous_database_credentials_title">Ekran za unos podataka za prijavu u bazu podataka</string> <string name="keyboard_previous_database_credentials_title">Ekran za unos podataka za prijavu u bazu podataka</string>
<string name="keyboard_change">Promijeni tipkovnicu</string> <string name="keyboard_change">Promijeni tipkovnicu</string>
<string name="warning_file_too_big">Baza podataka za KeePass trebala bi sadržavati samo male datoteke uslužnih programa (poput PGP datoteke ključeva).
\n
\nOvaj prijenos će možda neizmjerno povećati tvoju baza podataka i smanjiti performansu.</string>
<string name="upload_attachment">Prenesi %1$s</string>
<string name="education_add_attachment_summary">Za spremanje važnih vanjskih podataka, prenesi privitak u tvoj unos.</string>
<string name="education_add_attachment_title">Dodaj privitak</string>
<string name="warning_sure_add_file">Svejedno dodati datoteku\?</string>
<string name="warning_replace_file">Prijenosom ove datoteke zamijenit će se postojeća.</string>
</resources> </resources>

View File

@@ -176,4 +176,11 @@
<string name="feedback">Umpan Balik</string> <string name="feedback">Umpan Balik</string>
<string name="contribution">Kontribusi</string> <string name="contribution">Kontribusi</string>
<string name="contact">Kontak</string> <string name="contact">Kontak</string>
<string name="auto_focus_search_summary">Minta pencarian saat membuka database</string>
<string name="auto_focus_search_title">Pencarian cepat</string>
<string name="omit_backup_search_summary">Menghilangkan grup \"Cadangan\" dan \"Tempat sampah\" dari hasil penelusuran</string>
<string name="omit_backup_search_title">Jangan mencari melalui entri cadangan</string>
<string name="create_keepass_file">Buat basisdata baru</string>
<string name="select_database_file">Buka basisdata yang sudah ada</string>
<string name="no_url_handler">Pasang browser web untuk membuka URL ini.</string>
</resources> </resources>

View File

@@ -39,17 +39,17 @@
<string name="decrypting_db">データベースの内容を復号しています…</string> <string name="decrypting_db">データベースの内容を復号しています…</string>
<string name="default_checkbox">デフォルトのデータベースとして使用</string> <string name="default_checkbox">デフォルトのデータベースとして使用</string>
<string name="digits">数字</string> <string name="digits">数字</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft は&lt;strong&gt;オープンソース&lt;/strong&gt;&lt;strong&gt;広告なし&lt;/strong&gt;です。 <string name="html_about_licence">KeePassDX © %1$d Kunzisoft は&lt;strong&gt;オープンソース&lt;/strong&gt;かつ&lt;strong&gt;広告なし&lt;/strong&gt;です。
\nそのままの状態で、&lt;strong&gt;GPLv3&lt;/strong&gt; ライセンスの下、いかなる保証もなく提供されます。</string> \nそのままの状態で、&lt;strong&gt;GPLv3&lt;/strong&gt; ライセンスの下、いかなる保証もなく提供されます。</string>
<string name="select_database_file">既存のデータベースを開く</string> <string name="select_database_file">既存のデータベースを開く</string>
<string name="entry_accessed">アクセス日時</string> <string name="entry_accessed">最終アクセス</string>
<string name="entry_cancel">キャンセル</string> <string name="entry_cancel">キャンセル</string>
<string name="entry_notes">備考</string> <string name="entry_notes">備考</string>
<string name="entry_confpassword">パスワードを確認</string> <string name="entry_confpassword">パスワードを確認</string>
<string name="entry_created">作成日</string> <string name="entry_created">作成日</string>
<string name="entry_expires">有効期限</string> <string name="entry_expires">有効期限</string>
<string name="entry_keyfile">キーファイル</string> <string name="entry_keyfile">キーファイル</string>
<string name="entry_modified">変更日</string> <string name="entry_modified">変更日</string>
<string name="entry_password">パスワード</string> <string name="entry_password">パスワード</string>
<string name="entry_save">保存</string> <string name="entry_save">保存</string>
<string name="entry_title">タイトル</string> <string name="entry_title">タイトル</string>
@@ -65,8 +65,8 @@
<string name="error_out_of_memory">データベース全体を読み込むメモリがありません。</string> <string name="error_out_of_memory">データベース全体を読み込むメモリがありません。</string>
<string name="error_pass_gen_type">少なくとも 1 つのパスワード生成タイプを選択する必要があります。</string> <string name="error_pass_gen_type">少なくとも 1 つのパスワード生成タイプを選択する必要があります。</string>
<string name="error_pass_match">パスワードが一致しません。</string> <string name="error_pass_match">パスワードが一致しません。</string>
<string name="error_rounds_too_large">変換ラウンドが多すぎます。2147483648 に設定します。</string> <string name="error_rounds_too_large">[変換ラウンド] が多すぎます。2147483648 に設定します。</string>
<string name="error_wrong_length">長さフィールドには正の整数を入力してください。</string> <string name="error_wrong_length">[長さ] フィールドには正の整数を入力してください。</string>
<string name="file_browser">ファイル マネージャー</string> <string name="file_browser">ファイル マネージャー</string>
<string name="generate_password">パスワードを生成</string> <string name="generate_password">パスワードを生成</string>
<string name="hint_conf_pass">パスワードを確認</string> <string name="hint_conf_pass">パスワードを確認</string>
@@ -103,7 +103,7 @@
<string name="no_results">検索結果なし</string> <string name="no_results">検索結果なし</string>
<string name="no_url_handler">この URL を開くにはウェブブラウザをインストールしてください。</string> <string name="no_url_handler">この URL を開くにはウェブブラウザをインストールしてください。</string>
<string name="omit_backup_search_title">バックアップ エントリーを検索しない</string> <string name="omit_backup_search_title">バックアップ エントリーを検索しない</string>
<string name="omit_backup_search_summary">検索結果からバックアップ」と「ごみ箱」のグループを省きます</string> <string name="omit_backup_search_summary">検索結果から [バックアップ] と [ゴミ箱] のグループを省きます</string>
<string name="progress_create">新しいデータベースを作成しています…</string> <string name="progress_create">新しいデータベースを作成しています…</string>
<string name="progress_title">実行しています…</string> <string name="progress_title">実行しています…</string>
<string name="content_description_remove_from_list">削除</string> <string name="content_description_remove_from_list">削除</string>
@@ -151,7 +151,7 @@
<string name="clipboard_error_clear">クリップボードを消去できませんでした</string> <string name="clipboard_error_clear">クリップボードを消去できませんでした</string>
<string name="entry_not_found">エントリーのデータが見つかりませんでした。</string> <string name="entry_not_found">エントリーのデータが見つかりませんでした。</string>
<string name="error_load_database">データベースを読み込めませんでした。</string> <string name="error_load_database">データベースを読み込めませんでした。</string>
<string name="error_load_database_KDF_memory">鍵を読み込めませんでした。鍵導出関数のメモリ使用量を下げてみてください。</string> <string name="error_load_database_KDF_memory">鍵を読み込めませんでした。鍵導出関数の [メモリ使用量] を下げてみてください。</string>
<string name="error_string_key">各文字列にはフィールド名が必要です。</string> <string name="error_string_key">各文字列にはフィールド名が必要です。</string>
<string name="error_autofill_enable_service">自動入力サービスを有効にできませんでした。</string> <string name="error_autofill_enable_service">自動入力サービスを有効にできませんでした。</string>
<string name="error_move_folder_in_itself">グループを自分自身の中に移動することはできません。</string> <string name="error_move_folder_in_itself">グループを自分自身の中に移動することはできません。</string>
@@ -177,13 +177,13 @@
<string name="read_only_warning">ファイル マネージャーによっては、KeePassDX によるストレージへの書き込みを許可していない場合があります。</string> <string name="read_only_warning">ファイル マネージャーによっては、KeePassDX によるストレージへの書き込みを許可していない場合があります。</string>
<string name="encryption_explanation">すべてのデータで使用するデータベース暗号化アルゴリズムです。</string> <string name="encryption_explanation">すべてのデータで使用するデータベース暗号化アルゴリズムです。</string>
<string name="sort_menu">並び替え</string> <string name="sort_menu">並び替え</string>
<string name="sort_ascending">低い順 ↓</string> <string name="sort_ascending">順 ↓</string>
<string name="sort_recycle_bin_bottom">ごみ箱を一番下にする</string> <string name="sort_recycle_bin_bottom">ゴミ箱を一番下にする</string>
<string name="sort_title">タイトル</string> <string name="sort_title">タイトル</string>
<string name="sort_username">ユーザー名</string> <string name="sort_username">ユーザー名</string>
<string name="sort_creation_time">作成日</string> <string name="sort_creation_time">作成日</string>
<string name="sort_last_modify_time">変更日</string> <string name="sort_last_modify_time">変更日</string>
<string name="sort_last_access_time">アクセス</string> <string name="sort_last_access_time">最終アクセス</string>
<string name="warning">警告</string> <string name="warning">警告</string>
<string name="content_description_open_file">ファイルを開く</string> <string name="content_description_open_file">ファイルを開く</string>
<string name="content_description_add_entry">エントリーを追加</string> <string name="content_description_add_entry">エントリーを追加</string>
@@ -196,7 +196,7 @@
<string name="content_description_remove_field">フィールドを削除</string> <string name="content_description_remove_field">フィールドを削除</string>
<string name="error_move_entry_here">ここではエントリーを移動することはできません。</string> <string name="error_move_entry_here">ここではエントリーを移動することはできません。</string>
<string name="error_copy_entry_here">ここではエントリーをコピーすることはできません。</string> <string name="error_copy_entry_here">ここではエントリーをコピーすることはできません。</string>
<string name="configure_biometric">生体認証プロンプト対応端末ですが未設定です。</string> <string name="configure_biometric">生体認証プロンプト対応ですが未設定です。</string>
<string name="master_key">マスターキー</string> <string name="master_key">マスターキー</string>
<string name="entry_history">履歴</string> <string name="entry_history">履歴</string>
<string name="otp_type">OTP の種類</string> <string name="otp_type">OTP の種類</string>
@@ -211,7 +211,7 @@
<string name="menu_master_key_settings">マスターキーの設定</string> <string name="menu_master_key_settings">マスターキーの設定</string>
<string name="error_save_database">データベースを保存できませんでした。</string> <string name="error_save_database">データベースを保存できませんでした。</string>
<string name="menu_save_database">データベースを保存</string> <string name="menu_save_database">データベースを保存</string>
<string name="menu_empty_recycle_bin">ごみ箱を空にする</string> <string name="menu_empty_recycle_bin">ゴミ箱を空にする</string>
<string name="command_execution">コマンドを実行しています…</string> <string name="command_execution">コマンドを実行しています…</string>
<string name="warning_permanently_delete_nodes">選択したノードを完全に削除しますか?</string> <string name="warning_permanently_delete_nodes">選択したノードを完全に削除しますか?</string>
<string name="auto_focus_search_summary">データベースを開いたとき、検索を促します</string> <string name="auto_focus_search_summary">データベースを開いたとき、検索を促します</string>
@@ -255,20 +255,20 @@
<string name="parallelism">並列処理</string> <string name="parallelism">並列処理</string>
<string name="memory_usage_explanation">鍵導出関数が使用するメモリの量(バイト単位)です。</string> <string name="memory_usage_explanation">鍵導出関数が使用するメモリの量(バイト単位)です。</string>
<string name="memory_usage">メモリ使用量</string> <string name="memory_usage">メモリ使用量</string>
<string name="kdf_explanation">暗号化アルゴリズム用の鍵を生成するために、マスターキーはランダムソルト化された鍵導出関数を使用して変換されます。</string> <string name="kdf_explanation">暗号化アルゴリズム用の鍵を生成するために、マスターキーはランダムソルトを加える鍵導出関数を使用して変換されます。</string>
<string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string> <string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string>
<string name="hide_broken_locations_title">データベースへの壊れたリンクを非表示にする</string> <string name="hide_broken_locations_title">データベースへの壊れたリンクを非表示にする</string>
<string name="show_recent_files_summary">最近使ったデータベースの場所を表示します</string> <string name="show_recent_files_summary">最近使ったデータベースの場所を表示します</string>
<string name="remember_keyfile_locations_summary">キーファイルの保存先を追跡します</string> <string name="remember_keyfile_locations_summary">キーファイルの保存先を保持します</string>
<string name="remember_keyfile_locations_title">キーファイルの場所を記憶</string> <string name="remember_keyfile_locations_title">キーファイルの場所を記憶</string>
<string name="remember_database_locations_summary">データベースの保存先を追跡します</string> <string name="remember_database_locations_summary">データベースの保存先を保持します</string>
<string name="remember_database_locations_title">データベースの場所を記憶</string> <string name="remember_database_locations_title">データベースの場所を記憶</string>
<string name="selection_mode">選択モード</string> <string name="selection_mode">選択モード</string>
<string name="contains_duplicate_uuid_procedure">重複したエントリーに対する新しい UUID を生成して、問題を解決し続行しますか?</string> <string name="contains_duplicate_uuid_procedure">重複したエントリーに対する新しい UUID を生成して、問題を解決し続行しますか?</string>
<string name="contains_duplicate_uuid">データベースには重複する UUID が含まれています。</string> <string name="contains_duplicate_uuid">データベースには重複する UUID が含まれています。</string>
<string name="subdomain_search_title">サブドメイン検索</string> <string name="subdomain_search_title">サブドメイン検索</string>
<string name="menu_advanced_unlock_settings">高度なロック解除</string> <string name="menu_advanced_unlock_settings">高度なロック解除</string>
<string name="invalid_db_same_uuid">同じ UUID %2$s を持つ %1$s がに存在します。</string> <string name="invalid_db_same_uuid">同じ UUID %2$s を持つ %1$s がすでに存在します。</string>
<string name="error_string_type">このテキストは指定された項目と整合しません。</string> <string name="error_string_type">このテキストは指定された項目と整合しません。</string>
<string name="error_otp_counter">カウンターは %1$d から %2$d の間でなければなりません。</string> <string name="error_otp_counter">カウンターは %1$d から %2$d の間でなければなりません。</string>
<string name="otp_counter">カウンター</string> <string name="otp_counter">カウンター</string>
@@ -294,7 +294,7 @@
<string name="biometric_auto_open_prompt_title">生体認証プロンプトを自動で開く</string> <string name="biometric_auto_open_prompt_title">生体認証プロンプトを自動で開く</string>
<string name="biometric_delete_all_key_warning">生体認証に関するすべての暗号鍵を削除しますか?</string> <string name="biometric_delete_all_key_warning">生体認証に関するすべての暗号鍵を削除しますか?</string>
<string name="biometric_delete_all_key_summary">生体認証に関するすべての暗号鍵を削除します</string> <string name="biometric_delete_all_key_summary">生体認証に関するすべての暗号鍵を削除します</string>
<string name="advanced_unlock_explanation_summary">高度なロック解除を使用すると、データベースより簡単に開きます</string> <string name="advanced_unlock_explanation_summary">高度なロック解除を使用して、データベースより簡単に開きます</string>
<string name="education_lock_summary">データベースをすばやくロックします。時間が経ったり画面がオフになったときロックするよう、アプリを設定することもできます。</string> <string name="education_lock_summary">データベースをすばやくロックします。時間が経ったり画面がオフになったときロックするよう、アプリを設定することもできます。</string>
<string name="education_lock_title">データベースをロック</string> <string name="education_lock_title">データベースをロック</string>
<string name="education_sort_summary">エントリーとグループの並べ替え方法を選択します。</string> <string name="education_sort_summary">エントリーとグループの並べ替え方法を選択します。</string>
@@ -307,7 +307,7 @@
<string name="education_sort_title">項目の並び替え</string> <string name="education_sort_title">項目の並び替え</string>
<string name="education_new_node_title">データベースに項目を追加</string> <string name="education_new_node_title">データベースに項目を追加</string>
<string name="assign_master_key">マスターキーを設定</string> <string name="assign_master_key">マスターキーを設定</string>
<string name="education_select_database_summary">ファイル ブラウザからこれまで使ってきたデータベース ファイルを開いて引き続き使用します。</string> <string name="education_select_database_summary">これまで使ってきたデータベース ファイルを、ファイルブラウザから開いて引き続き使用します。</string>
<string name="education_select_database_title">既存のデータペースを開く</string> <string name="education_select_database_title">既存のデータペースを開く</string>
<string name="list_password_generator_options_title">パスワードの文字種</string> <string name="list_password_generator_options_title">パスワードの文字種</string>
<string name="build_label">Build %1$s</string> <string name="build_label">Build %1$s</string>
@@ -318,7 +318,7 @@
<string name="enable_read_only_title">書き込み禁止</string> <string name="enable_read_only_title">書き込み禁止</string>
<string name="delete_entered_password_summary">入力されたパスワードをデータベースへの接続試行後に削除します</string> <string name="delete_entered_password_summary">入力されたパスワードをデータベースへの接続試行後に削除します</string>
<string name="delete_entered_password_title">パスワードを削除</string> <string name="delete_entered_password_title">パスワードを削除</string>
<string name="allow_no_password_summary">認証情報が選択されていない場合でも、開くボタンのタップを許可します</string> <string name="allow_no_password_summary">認証情報が選択されていない場合でも、[開く] ボタンのタップを許可します</string>
<string name="autofill_block_restart">ブロッキングを有効にするには、そのフォームを含むアプリを再起動します。</string> <string name="autofill_block_restart">ブロッキングを有効にするには、そのフォームを含むアプリを再起動します。</string>
<string name="autofill_block">自動入力をブロック</string> <string name="autofill_block">自動入力をブロック</string>
<string name="autofill_web_domain_blocklist_title">ウェブドメインのブロックリスト</string> <string name="autofill_web_domain_blocklist_title">ウェブドメインのブロックリスト</string>
@@ -336,7 +336,7 @@
<string name="settings_database_recommend_changing_master_key_summary">マスターキーの変更を推奨します(日数)</string> <string name="settings_database_recommend_changing_master_key_summary">マスターキーの変更を推奨します(日数)</string>
<string name="settings_database_recommend_changing_master_key_title">更新を推奨</string> <string name="settings_database_recommend_changing_master_key_title">更新を推奨</string>
<string name="max_history_size_summary">エントリーあたりの履歴のサイズ(バイト単位)を制限します</string> <string name="max_history_size_summary">エントリーあたりの履歴のサイズ(バイト単位)を制限します</string>
<string name="lock_database_back_root_title">戻るを押してロック</string> <string name="lock_database_back_root_title">[戻る] を押してロック</string>
<string name="lock_database_screen_off_summary">画面がオフのとき、データベースをロックします</string> <string name="lock_database_screen_off_summary">画面がオフのとき、データベースをロックします</string>
<string name="lock_database_screen_off_title">画面ロック</string> <string name="lock_database_screen_off_title">画面ロック</string>
<string name="lock">ロック</string> <string name="lock">ロック</string>
@@ -350,14 +350,14 @@
<string name="autofill_preference_title">自動入力の設定</string> <string name="autofill_preference_title">自動入力の設定</string>
<string name="set_autofill_service_title">デフォルトの自動入力サービスに設定</string> <string name="set_autofill_service_title">デフォルトの自動入力サービスに設定</string>
<string name="autofill_explanation_summary">自動入力を有効にして、他のアプリ内のフォームにすばやく入力します</string> <string name="autofill_explanation_summary">自動入力を有効にして、他のアプリ内のフォームにすばやく入力します</string>
<string name="autofill_sign_in_prompt">KeePassDX でサインイン</string> <string name="autofill_sign_in_prompt">KeePassDX でログイン</string>
<string name="autofill_service_name">KeePassDX フォーム自動入力</string> <string name="autofill_service_name">KeePassDX フォーム自動入力</string>
<string name="autofill">自動入力</string> <string name="autofill">自動入力</string>
<string name="general">全般</string> <string name="general">全般</string>
<string name="biometric">生体認証</string> <string name="biometric">生体認証</string>
<string name="menu_appearance_settings">外観</string> <string name="menu_appearance_settings">デザイン</string>
<string name="database_history">履歴</string> <string name="database_history">履歴</string>
<string name="credential_before_click_biometric_button">パスワードを入力し、生体認証ボタンをタップします。</string> <string name="credential_before_click_biometric_button">パスワードを入力し、[生体認証] ボタンをタップします。</string>
<string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string> <string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string>
<string name="biometric_scanning_error">生体認証エラー:%1$s</string> <string name="biometric_scanning_error">生体認証エラー:%1$s</string>
<string name="biometric_not_recognized">生体情報を認識できませんでした</string> <string name="biometric_not_recognized">生体情報を認識できませんでした</string>
@@ -370,7 +370,7 @@
<string name="keyboard_notification_entry_clear_close_title">データベースを閉じて消去</string> <string name="keyboard_notification_entry_clear_close_title">データベースを閉じて消去</string>
<string name="clear_clipboard_notification_title">データベースを閉じて消去</string> <string name="clear_clipboard_notification_title">データベースを閉じて消去</string>
<string name="keyboard_notification_entry_clear_close_summary">通知を閉じるとき、データベースも閉じます</string> <string name="keyboard_notification_entry_clear_close_summary">通知を閉じるとき、データベースも閉じます</string>
<string name="clear_clipboard_notification_summary">クリップボードでの保存期間が終了したとき、またはこの機能の使用中に通知が閉じられたとき、データベースをロックします</string> <string name="clear_clipboard_notification_summary">クリップボードでの保存期間が終了したとき、または使用開始後に通知が閉じられたとき、データベースをロックします</string>
<string name="html_text_dev_feature_encourage">あなたの意見によって、デベロッパーは&lt;strong&gt;新機能&lt;/strong&gt;の開発や&lt;strong&gt;バグの修正&lt;/strong&gt;を速く進めることができます。</string> <string name="html_text_dev_feature_encourage">あなたの意見によって、デベロッパーは&lt;strong&gt;新機能&lt;/strong&gt;の開発や&lt;strong&gt;バグの修正&lt;/strong&gt;を速く進めることができます。</string>
<string name="reset_education_screens_summary">すべての教育的な情報をもう一度表示します</string> <string name="reset_education_screens_summary">すべての教育的な情報をもう一度表示します</string>
<string name="html_text_dev_feature">この機能は&lt;strong&gt;開発中&lt;/strong&gt;であり、早期に提供するにはあなたの&lt;strong&gt;貢献&lt;/strong&gt;が必要です。</string> <string name="html_text_dev_feature">この機能は&lt;strong&gt;開発中&lt;/strong&gt;であり、早期に提供するにはあなたの&lt;strong&gt;貢献&lt;/strong&gt;が必要です。</string>
@@ -384,16 +384,16 @@
<string name="keyboard_previous_fill_in_title">自動キーアクション</string> <string name="keyboard_previous_fill_in_title">自動キーアクション</string>
<string name="keyboard_key_sound_title">キー操作音</string> <string name="keyboard_key_sound_title">キー操作音</string>
<string name="keyboard_key_vibrate_title">キー操作バイブレーション</string> <string name="keyboard_key_vibrate_title">キー操作バイブレーション</string>
<string name="keyboard_auto_go_action_summary">フィールド キーを押した後に「移動」キーアクションを行います</string> <string name="keyboard_auto_go_action_summary">[フィールド] キーを押した後に [移動] キーアクションを行います</string>
<string name="keyboard_auto_go_action_title">自動キーアクション</string> <string name="keyboard_auto_go_action_title">自動キーアクション</string>
<string name="keyboard_notification_entry_content_title">%1$s が Magikeyboard で利用可能</string> <string name="keyboard_notification_entry_content_title">%1$s が Magikeyboard で利用可能</string>
<string name="keyboard_entry_timeout_summary">キーボードのエントリーを消去するまでの時間</string> <string name="keyboard_entry_timeout_summary">キーボードのエントリーを消去するまでの時間</string>
<string name="keyboard_search_share_title">共有から送られた情報の検索</string> <string name="keyboard_search_share_title">共有情報の検索</string>
<string name="keyboard_search_share_summary">共有から送られた情報を自動的に検索して、結果をキーボードに格納します</string> <string name="keyboard_search_share_summary">共有情報を自動的に検索して、結果をキーボードに格納します</string>
<string name="keyboard_notification_entry_summary">エントリーが利用可能なとき、通知を表示します</string> <string name="keyboard_notification_entry_summary">エントリーが利用可能なとき、通知を表示します</string>
<string name="device_keyboard_setting_title">デバイス キーボードの設定</string> <string name="device_keyboard_setting_title">デバイス キーボードの設定</string>
<string name="settings_database_force_changing_master_key_next_time_summary">次回マスターキーの変更を必須にします1 回のみ)</string> <string name="settings_database_force_changing_master_key_next_time_summary">次回マスターキーの変更を必須にします1 回のみ)</string>
<string name="recycle_bin_summary">グループとエントリーを削除する前に「ごみ箱」グループに移動します</string> <string name="recycle_bin_summary">グループとエントリーを削除する前に [ゴミ箱] グループに移動します</string>
<string name="keyboard_selection_entry_summary">エントリーを開いているとき、Magikeyboard に入力フィールドを表示します</string> <string name="keyboard_selection_entry_summary">エントリーを開いているとき、Magikeyboard に入力フィールドを表示します</string>
<string name="lock_database_show_button_title">ロックボタンを表示</string> <string name="lock_database_show_button_title">ロックボタンを表示</string>
<string name="download_finalization">終了しています…</string> <string name="download_finalization">終了しています…</string>
@@ -401,7 +401,7 @@
<string name="education_field_copy_summary">コピーしたフィールドはどこにでも貼り付けることができます。 <string name="education_field_copy_summary">コピーしたフィールドはどこにでも貼り付けることができます。
\n \n
\nお好みのフォーム入力方法を使用してください。</string> \nお好みのフォーム入力方法を使用してください。</string>
<string name="warning_database_link_revoked">ファイル マネージャーによって取り消されたファイルにアクセスします</string> <string name="warning_database_link_revoked">ファイルへのアクセス権がファイル マネージャーによって取り消されまし</string>
<string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string> <string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string>
<string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string> <string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string>
<string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string> <string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string>
@@ -416,7 +416,7 @@
<string name="html_text_dev_feature_contibute">&lt;strong&gt;貢献&lt;/strong&gt;による、</string> <string name="html_text_dev_feature_contibute">&lt;strong&gt;貢献&lt;/strong&gt;による、</string>
<string name="html_text_donation">自由を維持し活発に開発し続けるために、私たちはあなたの&lt;strong&gt;貢献&lt;/strong&gt;に期待しています。</string> <string name="html_text_donation">自由を維持し活発に開発し続けるために、私たちはあなたの&lt;strong&gt;貢献&lt;/strong&gt;に期待しています。</string>
<string name="html_text_ad_free">多くのパスワード管理アプリとは異なり、このアプリは&lt;strong&gt;広告なし&lt;/strong&gt;かつ&lt;strong&gt;コピーレフトの自由ソフトウェア&lt;/strong&gt;です。どのバージョンを使っても、サーバー上で個人情報が収集されることはありません。</string> <string name="html_text_ad_free">多くのパスワード管理アプリとは異なり、このアプリは&lt;strong&gt;広告なし&lt;/strong&gt;かつ&lt;strong&gt;コピーレフトの自由ソフトウェア&lt;/strong&gt;です。どのバージョンを使っても、サーバー上で個人情報が収集されることはありません。</string>
<string name="enable_auto_save_database_summary">重要なアクションを起こすたびにデータベースを保存します(変更可能モードのとき)</string> <string name="enable_auto_save_database_summary">重要なアクションを起こすたびにデータベースを保存します( [変更可能] モードのとき)</string>
<string name="allow_no_password_title">空のマスターキーを許可</string> <string name="allow_no_password_title">空のマスターキーを許可</string>
<string name="autofill_auto_search_title">自動検索</string> <string name="autofill_auto_search_title">自動検索</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string> <string name="keyboard_label">Magikeyboard (KeePassDX)</string>
@@ -440,7 +440,7 @@
<string name="education_read_only_title">データベースの書き込みを禁止</string> <string name="education_read_only_title">データベースの書き込みを禁止</string>
<string name="education_entry_new_field_summary">追加フィールドを登録し、値を追加し、必要に応じて保護します。</string> <string name="education_entry_new_field_summary">追加フィールドを登録し、値を追加し、必要に応じて保護します。</string>
<string name="education_entry_new_field_title">カスタム フィールドを追加</string> <string name="education_entry_new_field_title">カスタム フィールドを追加</string>
<string name="education_entry_edit_summary">カスタム フィールドを使ってエントリーを編集します。共有データは異なるエントリーのフィールド間で参照することができます。</string> <string name="education_entry_edit_summary">エントリーをカスタム フィールドとともに編集します。共有データは異なるエントリーのフィールド間で参照することができます。</string>
<string name="education_entry_edit_title">エントリーを編集</string> <string name="education_entry_edit_title">エントリーを編集</string>
<string name="education_biometric_title">生体認証によるロック解除</string> <string name="education_biometric_title">生体認証によるロック解除</string>
<string name="education_create_database_summary">最初のパスワード管理ファイルを作成します。</string> <string name="education_create_database_summary">最初のパスワード管理ファイルを作成します。</string>
@@ -448,8 +448,8 @@
<string name="enable_education_screens_summary">要素をハイライトしてアプリの動作を学びます</string> <string name="enable_education_screens_summary">要素をハイライトしてアプリの動作を学びます</string>
<string name="education_read_only_summary">セッションのロック解除モードを変更します。 <string name="education_read_only_summary">セッションのロック解除モードを変更します。
\n \n
\n書き込み禁止では、データベースに対する意図しない変更を防ぐことができます。 \n[書き込み禁止] では、データベースに対する意図しない変更を防ぐことができます。
\n変更可能では、すべての要素を追加、削除、変更できます。</string> \n[変更可能] では、すべての要素を追加、削除、変更できます。</string>
<string name="allow_copy_password_title">クリップボードの信頼</string> <string name="allow_copy_password_title">クリップボードの信頼</string>
<string name="encryption_chacha20">ChaCha20</string> <string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_Argon2">Argon2</string> <string name="kdf_Argon2">Argon2</string>
@@ -459,7 +459,7 @@
<string name="keyboard_change">キーボードの切り替え</string> <string name="keyboard_change">キーボードの切り替え</string>
<string name="keyboard_keys_category">キー</string> <string name="keyboard_keys_category">キー</string>
<string name="keyboard_theme_title">キーボードのテーマ</string> <string name="keyboard_theme_title">キーボードのテーマ</string>
<string name="keyboard_appearance_category">外観</string> <string name="keyboard_appearance_category">デザイン</string>
<string name="keyboard_notification_entry_content_text">%1$s</string> <string name="keyboard_notification_entry_content_text">%1$s</string>
<string name="keyboard_notification_entry_content_title_text">エントリー</string> <string name="keyboard_notification_entry_content_title_text">エントリー</string>
<string name="keyboard_entry_timeout_title">タイムアウト</string> <string name="keyboard_entry_timeout_title">タイムアウト</string>
@@ -486,7 +486,15 @@
<string name="database_default_username_title">デフォルトのユーザー名</string> <string name="database_default_username_title">デフォルトのユーザー名</string>
<string name="style_choose_title">アプリのテーマ</string> <string name="style_choose_title">アプリのテーマ</string>
<string name="reset_education_screens_title">教育的なヒントをリセット</string> <string name="reset_education_screens_title">教育的なヒントをリセット</string>
<string name="recycle_bin">ごみ</string> <string name="recycle_bin">ゴミ</string>
<string name="recycle_bin_group_title">ごみ箱グループ</string> <string name="recycle_bin_group_title">ゴミ箱グループ</string>
<string name="recycle_bin_title">ごみ箱の使用</string> <string name="recycle_bin_title">ゴミ箱の使用</string>
<string name="warning_file_too_big">KeePass データベースに含まれると想定されるのは、小さなユーティリティ ファイルPGP 鍵ファイルなど)のみです。
\n
\nこのアップロードによりデータベースが非常に大きくなり、パフォーマンスが低下する可能性があります。</string>
<string name="warning_replace_file">このファイルをアップロードすると、既存のファイルが置き換えられます。</string>
<string name="upload_attachment">%1$s をアップロード</string>
<string name="education_add_attachment_summary">エントリーに添付ファイルをアップロードして、重要な外部データを保存します。</string>
<string name="education_add_attachment_title">添付ファイルを追加</string>
<string name="warning_sure_add_file">それでもファイルを追加しますか?</string>
</resources> </resources>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="clipboard_timeout_summary">Durasi panyimpenan ing clipboard (yen didhukung dening piranti)</string>
<string name="clipboard_timeout">Wektune papan klip resik</string>
<string name="clipboard_error_clear">Papan klip ora iso diresiki</string>
<string name="clipboard_error">Ono aplikasi liyo seng ora ngijini gunakne pspan klip</string>
<string name="clipboard_cleared">Papan klip di resiki</string>
<string name="clipboard_error_title">Papan klipe ora beres</string>
<string name="allow">Sumonggo</string>
<string name="file_manager_install_description">Pangurus file sing nompo tumindak Intent ACTION_CREATE_DOCUMENT lan ACTION_OPEN_DOCUMENT dibutuhake kanggo nggawe, mbukak lan nyimpen file database.</string>
<string name="extended_ASCII">ASCII lengkap</string>
<string name="brackets">Kurung</string>
<string name="application">Aplikasi</string>
<string name="app_timeout_summary">Wektu kosong sadurunge ngunci basis data</string>
<string name="app_timeout">Wektu entek applikasine</string>
<string name="key_derivation_function">Fungsi derivasi utama</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">Tambah grupe</string>
<string name="add_entry">Tambah entrine</string>
<string name="edit_entry">Owah entrine</string>
<string name="accept">Mantep</string>
<string name="about_description">Implementasine Android seko pengolah kata sandi KeePass</string>
<string name="feedback">Aturi Wejangan</string>
<string name="homepage">Ngarep</string>
<string name="contribution">Urun Rembuk</string>
<string name="contact">Nomer Hape</string>
</resources>

View File

@@ -296,4 +296,78 @@
<string name="error_string_type">ഈ വാചകം അഭ്യർത്ഥിച്ച ഇനവുമായി പൊരുത്തപ്പെടുന്നില്ല.</string> <string name="error_string_type">ഈ വാചകം അഭ്യർത്ഥിച്ച ഇനവുമായി പൊരുത്തപ്പെടുന്നില്ല.</string>
<string name="error_otp_counter">%1$dന്റെയു %2$dന്റെയു ഇടയിൽ ആയിരിക്കണം കൌണ്ടർ.</string> <string name="error_otp_counter">%1$dന്റെയു %2$dന്റെയു ഇടയിൽ ആയിരിക്കണം കൌണ്ടർ.</string>
<string name="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="autofill_service_name">KeePassDX ഫോം ഓട്ടോഫില്ലിംഗ്</string>
<string name="biometric">ബയോമെട്രിക്</string>
<string name="credential_before_click_biometric_button">പാസ്‌വേഡ് ടൈപ്പുചെയ്യുക, തുടർന്ന് \"ബയോമെട്രിക്\" ബട്ടൺ ക്ലിക്കുചെയ്യുക.</string>
<string name="no_credentials_stored">ഈ ഡാറ്റാബേസിൽ ഇതുവരെ ക്രെഡൻഷ്യൽ സംഭരിച്ചിട്ടില്ല.</string>
<string name="biometric_invalid_key">ബയോമെട്രിക് കീ വായിക്കാൻ കഴിയില്ല. ദയവായി ഇത് ഇല്ലാതാക്കി ബയോമെട്രിക് തിരിച്ചറിയൽ നടപടിക്രമം ആവർത്തിക്കുക.</string>
<string name="biometric_prompt_extract_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="configure_biometric">ബയോമെട്രിക് പ്രോംപ്റ്റ് പിന്തുണയ്ക്കുന്നു, പക്ഷേ സജ്ജീകരിച്ചിട്ടില്ല.</string>
<string name="warning_sure_add_file">എന്തായാലും ഫയൽ ചേർക്കണോ\?</string>
<string name="warning_replace_file">ഈ ഫയൽ അപ്‌ലോഡുചെയ്യുന്നത് നിലവിലുള്ള ഫയലിനെ മാറ്റിസ്ഥാപിക്കും.</string>
<string name="warning_database_link_revoked">ഫയൽ മാനേജർ റദ്ദാക്കിയ ഫയലിലേക്കുള്ള ആക്സസ്</string>
<string name="warning_database_read_only">ഡാറ്റാബേസ് മാറ്റങ്ങൾ സംരക്ഷിക്കുന്നതിന് ഫയൽ റൈറ്റ് ആക്സസ് അനുവദിക്കുക</string>
<string name="sort_groups_before">മുമ്പുള്ള ഗ്രൂപ്പുകൾ</string>
<string name="sort_ascending">ആദ്യത്തേത് ഏറ്റവും കുറഞ്ഞത്↓</string>
<string name="parallelism_explanation">കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന സമാന്തരതയുടെ ബിരുദം (അതായത് ത്രെഡുകളുടെ എണ്ണം).</string>
<string name="memory_usage_explanation">കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന മെമ്മറിയുടെ അളവ് (ബൈറ്റുകളിൽ).</string>
<string name="encryption_explanation">എല്ലാ ഡാറ്റയ്ക്കും ഉപയോഗിക്കുന്ന ഡാറ്റാബേസ് എൻ‌ക്രിപ്ഷൻ അൽ‌ഗോരിതം.</string>
<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="contains_duplicate_uuid">ഡാറ്റാബേസിൽ തനിപ്പകർപ്പ് UUID-കൾ അടങ്ങിയിരിക്കുന്നു.</string>
<string name="read_only_warning">നിങ്ങളുടെ ഫയൽ മാനേജരെ ആശ്രയിച്ച്, നിങ്ങളുടെ സ്റ്റോറേജിൽ എഴുതാൻ KeepassDX-നെ അനുവദിച്ചേക്കില്ല.</string>
<string name="auto_focus_search_summary">ഡാറ്റാബേസ് തുറക്കുമ്പോൾ തിരയൽ അഭ്യർത്ഥിക്കുക</string>
<string name="omit_backup_search_summary">തിരയൽ ഫലങ്ങളിൽ നിന്ന് \"ബാക്കപ്പ്\", \"റീസൈക്കിൾ ബിൻ\" ഗ്രൂപ്പുകൾ ഒഴിവാക്കുക</string>
<string name="omit_backup_search_title">ബാക്കപ്പ് എൻ‌ട്രികളിലൂടെ തിരയരുത്</string>
<string name="menu_open_file_read_and_write">പരിഷ്‌ക്കരിക്കാവുന്ന</string>
<string name="menu_file_selection_read_only">റൈറ്റ്-പരിരക്ഷിതമാണ്</string>
<string name="error_otp_digits">ടോക്കണിൽ %1$ മുതൽ %2$d അക്കങ്ങൾ ഉണ്ടായിരിക്കണം.</string>
<string name="database_custom_color_title">ഇഷ്‌ടാനുസൃത ഡാറ്റാബേസ് നിറം</string>
<string name="keyboard_notification_entry_summary">ഒരു എൻ‌ട്രി ലഭ്യമാകുമ്പോൾ, അറിയിപ്പ് കാണിക്കുക</string>
<string name="keyboard_previous_database_credentials_title">ഡാറ്റാബേസ് ക്രെഡൻഷ്യലുകൾ സ്ക്രീൻ</string>
<string name="autofill_application_id_blocklist_title">അപ്ലിക്കേഷൻ ബ്ലോക്ക്ലിസ്റ്റ്</string>
<string name="autofill_web_domain_blocklist_title">വെബ് ഡൊമെയ്ൻ ബ്ലോക്ക്ലിസ്റ്റ്</string>
<string name="education_donation_summary">സ്ഥിരത, സുരക്ഷ എന്നിവ വർദ്ധിപ്പിക്കുന്നതിനും കൂടുതൽ സവിശേഷതകൾ ചേർക്കുന്നതിനും സഹായിക്കുക.</string>
<string name="html_text_feature_generosity">ഞങ്ങളുടെ സ്വാതന്ത്ര്യം നിലനിർത്തുന്നതിനും എല്ലായ്പ്പോഴും സജീവമായിരിക്കുന്നതിനും, നിങ്ങളുടെ&lt;strong&gt;സംഭാവനയെ ഞങ്ങൾ ആശ്രയിക്കുന്നു&lt;/strong&gt;.</string>
<string name="html_text_dev_feature">ഈ സവിശേഷത&lt;strong&gt;വികസിച്ചുകൊണ്ടിരിക്കുന്നു&lt;/strong&gt;കൂടാതെ നിങ്ങളുടെ&lt;strong&gt;സംഭാവന&lt;/strong&gt;ഉടൻ ലഭ്യമാകേണ്ടതുണ്ട്.</string>
<string name="html_text_dev_feature_buy_pro">&lt;strong&gt;പ്രോ&lt;/strong&gt;വേർഷൻ വാങ്ങുന്നതിലൂടെ,</string>
<string name="html_text_dev_feature_contibute">&lt;strong&gt;സംഭാവന ചെയ്യുന്നതിലൂടെ&lt;/strong&gt;,</string>
<string name="upload_attachment">%1$s അപ്‌ലോഡുചെയ്യുക</string>
<string name="hide_expired_entries_title">കാലഹരണപ്പെട്ട എൻ‌ട്രികൾ‌ മറയ്‌ക്കുക</string>
<string name="contribute">സംഭാവന ചെയ്യുക</string>
<string name="html_text_dev_feature_upgrade">പുതിയ പതിപ്പ് ഇൻസ്റ്റാളുചെയ്യുന്നതിലൂടെ നിങ്ങളുടെ അപ്ലിക്കേഷൻ നിലനിർത്താൻ മറക്കരുത്.</string>
<string name="html_text_dev_feature_work_hard">ഈ സവിശേഷത വേഗത്തിൽ പുറത്തിറക്കാൻ ഞങ്ങൾ കഠിനമായി പരിശ്രമിക്കുന്നു.</string>
<string name="education_add_attachment_title">അറ്റാച്ചുമെന്റ് ചേർക്കുക</string>
<string name="education_search_title">എൻ‌ട്രികളിലൂടെ തിരയുക</string>
<string name="reset_education_screens_title">വിദ്യാഭ്യാസ സൂചനകൾ പുനഃസജ്ജമാക്കുക</string>
<string name="keyboard_appearance_category">രൂപം</string>
<string name="keyboard_notification_entry_content_title_text">എൻട്രി</string>
<string name="keyboard_notification_entry_clear_close_summary">അറിയിപ്പ് അടയ്‌ക്കുമ്പോൾ ഡാറ്റാബേസ് അടയ്‌ക്കുക</string>
<string name="keyboard_selection_entry_title">എൻട്രി തിരഞ്ഞെടുക്കൽ</string>
<string name="compression">കംപ്രഷൻ</string>
<string name="database_default_username_title">സ്ഥിരസ്ഥിതി ഉപയോക്തൃനാമം</string>
<string name="database_data_compression_summary">ഡാറ്റ കംപ്രഷൻ ഡാറ്റാബേസിന്റെ വലുപ്പം കുറയ്ക്കുന്നു.</string>
<string name="database_data_compression_title">ഡാറ്റ കംപ്രഷൻ</string>
<string name="unavailable_feature_hardware">അനുബന്ധ ഹാർഡ്‌വെയർ കണ്ടെത്താൻ കഴിഞ്ഞില്ല.</string>
<string name="biometric_unlock_enable_title">ബയോമെട്രിക് അൺലോക്കിംഗ്</string>
<string name="advanced_unlock_explanation_summary">ഒരു ഡാറ്റാബേസ് കൂടുതൽ എളുപ്പത്തിൽ തുറക്കാൻ വിപുലമായ അൺലോക്കിംഗ് ഉപയോഗിക്കുക</string>
<string name="lock_database_show_button_summary">ഉപയോക്തൃ ഇന്റർഫേസിൽ ലോക്ക് ബട്ടൺ പ്രദർശിപ്പിക്കുക</string>
<string name="clipboard_explanation_summary">നിങ്ങളുടെ ഉപകരണത്തിന്റെ ക്ലിപ്പ്ബോർഡ് ഉപയോഗിച്ച് എൻട്രി ഫീൽഡുകൾ പകർത്തുക</string>
<string name="list_password_generator_options_title">പാസ്‌വേഡ് പ്രതീകങ്ങൾ</string>
<string name="password_size_summary">സൃഷ്ടിച്ച പാസ്‌വേഡുകളുടെ സ്ഥിരസ്ഥിതി വലുപ്പം സജ്ജമാക്കുക</string>
<string name="set_autofill_service_title">സ്ഥിരസ്ഥിതി ഓട്ടോഫിൽ സേവനം സജ്ജമാക്കുക</string>
<string name="autofill_explanation_summary">മറ്റ് അപ്ലിക്കേഷനുകളിൽ ഫോമുകൾ വേഗത്തിൽ പൂരിപ്പിക്കുന്നതിന് ഓട്ടോഫില്ലിംഗ് പ്രവർത്തനക്ഷമമാക്കുക</string>
<string name="keystore_not_accessible">കീസ്റ്റോർ ശരിയായി സമാരംഭിച്ചിട്ടില്ല.</string>
<string name="sort_recycle_bin_bottom">റീസൈക്കിൾ ബിൻ ചുവടെയുണ്ട്</string>
<string name="root">റൂട്ട്</string>
<string name="minus">മൈനസ് ചിഹ്നം</string>
<string name="hide_expired_entries_summary">കാലാവധി കഴിഞ്ഞ എൻട്രികൾ കാണിക്കുന്നില്ല</string>
</resources> </resources>

View File

@@ -60,7 +60,7 @@
<string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string> <string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string>
<string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string> <string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string>
<string name="error_file_not_create">Bestand is niet aangemaakt</string> <string name="error_file_not_create">Bestand is niet aangemaakt</string>
<string name="error_invalid_db">Kan database niet uitlezen.</string> <string name="error_invalid_db">Kan de database niet uitlezen.</string>
<string name="error_invalid_path">Zorg ervoor dat het pad juist is.</string> <string name="error_invalid_path">Zorg ervoor dat het pad juist is.</string>
<string name="error_no_name">Voer een naam in.</string> <string name="error_no_name">Voer een naam in.</string>
<string name="error_nokeyfile">Kies een sleutelbestand.</string> <string name="error_nokeyfile">Kies een sleutelbestand.</string>
@@ -205,7 +205,7 @@
<string name="biometric_not_recognized">Biometrie niet herkend</string> <string name="biometric_not_recognized">Biometrie niet herkend</string>
<string name="biometric_scanning_error">Probleem met biometrie: %1$s</string> <string name="biometric_scanning_error">Probleem met biometrie: %1$s</string>
<string name="open_biometric_prompt_store_credential">Biometrische herkenning gebruiken om wachtwoorden op te slaan</string> <string name="open_biometric_prompt_store_credential">Biometrische herkenning gebruiken om wachtwoorden op te slaan</string>
<string name="no_credentials_stored">Deze databank heeft nog geen wachtwoord.</string> <string name="no_credentials_stored">Deze database heeft nog geen opgeslagen gegevens.</string>
<string name="database_history">Geschiedenis</string> <string name="database_history">Geschiedenis</string>
<string name="menu_appearance_settings">Uiterlijk</string> <string name="menu_appearance_settings">Uiterlijk</string>
<string name="general">Algemeen</string> <string name="general">Algemeen</string>
@@ -220,7 +220,7 @@
<string name="list_password_generator_options_summary">Toegestane wachtwoordtekens instellen</string> <string name="list_password_generator_options_summary">Toegestane wachtwoordtekens instellen</string>
<string name="clipboard">Klembord</string> <string name="clipboard">Klembord</string>
<string name="clipboard_notifications_title">Klembordmeldingen</string> <string name="clipboard_notifications_title">Klembordmeldingen</string>
<string name="clipboard_notifications_summary">Schakel klembordmeldingen in om velden te kopiëren bij het bekijken van een item</string> <string name="clipboard_notifications_summary">Toon klembordmeldingen om velden te kopiëren bij het bekijken van een item</string>
<string name="clipboard_warning">Als automatisch wissen van het klembord mislukt, doe dit dan handmatig.</string> <string name="clipboard_warning">Als automatisch wissen van het klembord mislukt, doe dit dan handmatig.</string>
<string name="lock">Vergrendelen</string> <string name="lock">Vergrendelen</string>
<string name="lock_database_screen_off_title">Schermvergrendeling</string> <string name="lock_database_screen_off_title">Schermvergrendeling</string>
@@ -232,7 +232,7 @@
<string name="biometric_delete_all_key_summary">Alle sleutels voor biometrische herkenning verwijderen</string> <string name="biometric_delete_all_key_summary">Alle sleutels voor biometrische herkenning verwijderen</string>
<string name="biometric_delete_all_key_warning">Alle coderingssleutels voor biometrische herkenning verwijderen\?</string> <string name="biometric_delete_all_key_warning">Alle coderingssleutels voor biometrische herkenning verwijderen\?</string>
<string name="unavailable_feature_text">Kan deze functie niet starten.</string> <string name="unavailable_feature_text">Kan deze functie niet starten.</string>
<string name="unavailable_feature_version">Je Android-versie, %1$s, voldoet niet aan de minimumvereiste %2$s.</string> <string name="unavailable_feature_version">Het apparaat draait op Android %1$s, maar %2$s of hoger is vereist.</string>
<string name="unavailable_feature_hardware">De bijbehorende hardware werd niet gevonden.</string> <string name="unavailable_feature_hardware">De bijbehorende hardware werd niet gevonden.</string>
<string name="file_name">Bestandsnaam</string> <string name="file_name">Bestandsnaam</string>
<string name="path">Pad</string> <string name="path">Pad</string>
@@ -254,8 +254,8 @@
<string name="keyboard">Toetsenbord</string> <string name="keyboard">Toetsenbord</string>
<string name="magic_keyboard_title">Magikeyboard</string> <string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_explanation_summary">Aangepast toetsenbord met je wachtwoorden en alle identiteitsvelden activeren</string> <string name="magic_keyboard_explanation_summary">Aangepast toetsenbord met je wachtwoorden en alle identiteitsvelden activeren</string>
<string name="allow_no_password_title">Geen hoofdwachtwoord</string> <string name="allow_no_password_title">Geen hoofdwachtwoord toestaan</string>
<string name="allow_no_password_summary">Schakel de knop \"Openen\" in als er geen referenties zijn geselecteerd</string> <string name="allow_no_password_summary">Maakt het mogelijk op de knop \"Openen\" te tikken als er geen inloggegevens zijn geselecteerd</string>
<string name="enable_read_only_title">Alleen-lezen</string> <string name="enable_read_only_title">Alleen-lezen</string>
<string name="enable_read_only_summary">Open de database standaard alleen-lezen</string> <string name="enable_read_only_summary">Open de database standaard alleen-lezen</string>
<string name="enable_education_screens_title">Informatieve tips</string> <string name="enable_education_screens_title">Informatieve tips</string>
@@ -273,7 +273,7 @@
\nGroepen (~mappen) organiseren de items in je database.</string> \nGroepen (~mappen) organiseren de items in je database.</string>
<string name="education_search_title">Doorzoek al je items</string> <string name="education_search_title">Doorzoek al je items</string>
<string name="education_search_summary">Doorzoek items op titel, gebruikersnaam of andere velden om wachtwoorden te vinden.</string> <string name="education_search_summary">Doorzoek items op titel, gebruikersnaam of andere velden om wachtwoorden te vinden.</string>
<string name="education_biometric_title">Database-ontgrendeling met biometrie</string> <string name="education_biometric_title">Biometrische database -ontgrendeling</string>
<string name="education_biometric_summary">Koppel je wachtwoord aan je biometrie om de database snel te ontgrendelen.</string> <string name="education_biometric_summary">Koppel je wachtwoord aan je biometrie om de database snel te ontgrendelen.</string>
<string name="education_entry_edit_title">Item bewerken</string> <string name="education_entry_edit_title">Item bewerken</string>
<string name="education_entry_edit_summary">Bewerk het item met aangepaste velden. Referenties kunnen worden toegevoegd tussen velden van verschillende items.</string> <string name="education_entry_edit_summary">Bewerk het item met aangepaste velden. Referenties kunnen worden toegevoegd tussen velden van verschillende items.</string>
@@ -297,7 +297,7 @@
<string name="education_sort_summary">Kies hoe items en groepen worden gesorteerd.</string> <string name="education_sort_summary">Kies hoe items en groepen worden gesorteerd.</string>
<string name="education_donation_title">Bijdragen</string> <string name="education_donation_title">Bijdragen</string>
<string name="education_donation_summary">Draag bij om de stabiliteit en veiligheid te vergroten en door meer functies toe te voegen.</string> <string name="education_donation_summary">Draag bij om de stabiliteit en veiligheid te vergroten en door meer functies toe te voegen.</string>
<string name="html_text_ad_free">In tegenstelling tot veel apps voor wachtwoordbeheer, is deze &lt;strong&gt; reclamevrij&lt;/strong&gt;, &lt;strong&gt; vrije software &lt;/strong&gt; en verzamelt het geen persoonlijke gegevens op haar servers, ongeacht welke versie je gebruikt.</string> <string name="html_text_ad_free">In tegenstelling tot veel apps voor wachtwoordbeheer, is deze &lt;strong&gt; reclamevrij&lt;/strong&gt;, &lt;strong&gt; vrije software &lt;/strong&gt; en verzamelt het geen persoonlijke gegevens op haar servers, ongeacht de versie die je gebruikt.</string>
<string name="html_text_buy_pro">Door de pro-versie te kopen krijg je toegang tot dit &lt;strong&gt;visuele thema&lt;/strong&gt; en draag je bij aan het &lt;strong&gt;realiseren van gemeenschapsprojecten.&lt;/strong&gt;</string> <string name="html_text_buy_pro">Door de pro-versie te kopen krijg je toegang tot dit &lt;strong&gt;visuele thema&lt;/strong&gt; en draag je bij aan het &lt;strong&gt;realiseren van gemeenschapsprojecten.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Dit &lt;strong&gt;visuele thema&lt;/strong&gt; is beschikbaar gemaakt dankzij jouw vrijgevigheid.</string> <string name="html_text_feature_generosity">Dit &lt;strong&gt;visuele thema&lt;/strong&gt; is beschikbaar gemaakt dankzij jouw vrijgevigheid.</string>
<string name="html_text_donation">Om altijd vrij en actief te blijven, rekenen we op jouw &lt;strong&gt;bijdrage.&lt;/strong&gt;</string> <string name="html_text_donation">Om altijd vrij en actief te blijven, rekenen we op jouw &lt;strong&gt;bijdrage.&lt;/strong&gt;</string>
@@ -426,17 +426,17 @@
<string name="compression_gzip">Gzip</string> <string name="compression_gzip">Gzip</string>
<string name="device_keyboard_setting_title">Toetsenbordinstellingen</string> <string name="device_keyboard_setting_title">Toetsenbordinstellingen</string>
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string> <string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
<string name="education_setup_OTP_title">Instellingen OTP</string> <string name="education_setup_OTP_title">OTP instellen</string>
<string name="remember_keyfile_locations_title">Locatie van sleutelbestanden opslaan</string> <string name="remember_keyfile_locations_title">Locatie van sleutelbestanden opslaan</string>
<string name="remember_database_locations_title">Databaselocatie opslaan</string> <string name="remember_database_locations_title">Databaselocaties onthouden</string>
<string name="hide_expired_entries_summary">Verlopen items worden verborgen</string> <string name="hide_expired_entries_summary">Verlopen items worden niet getoond</string>
<string name="hide_expired_entries_title">Verberg verlopen items</string> <string name="hide_expired_entries_title">Verberg verlopen items</string>
<string name="download_complete">Tik om het bestand te openen</string> <string name="download_complete">Klaar!</string>
<string name="download_finalization">Voltooien…</string> <string name="download_finalization">Voltooien…</string>
<string name="download_progression">Voortgang: %1$d%%</string> <string name="download_progression">Voortgang: %1$d%%</string>
<string name="download_initialization">Initialiseren…</string> <string name="download_initialization">Initialiseren…</string>
<string name="download_attachment">Download %1$s</string> <string name="download_attachment">Download %1$s</string>
<string name="education_setup_OTP_summary">Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren dat is aangevraagd voor tweefactorauthenticatie (2FA).</string> <string name="education_setup_OTP_summary">Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren dat wordt gevraagd voor tweefactorauthenticatie (2FA).</string>
<string name="enable_auto_save_database_title">Automatisch opslaan</string> <string name="enable_auto_save_database_title">Automatisch opslaan</string>
<string name="autofill_auto_search_summary">Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID</string> <string name="autofill_auto_search_summary">Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID</string>
<string name="autofill_auto_search_title">Automatisch zoeken</string> <string name="autofill_auto_search_title">Automatisch zoeken</string>
@@ -452,8 +452,8 @@
<string name="command_execution">Opdracht uitvoeren…</string> <string name="command_execution">Opdracht uitvoeren…</string>
<string name="hide_broken_locations_summary">Verberg gebroken links in de lijst met recente databases</string> <string name="hide_broken_locations_summary">Verberg gebroken links in de lijst met recente databases</string>
<string name="hide_broken_locations_title">Verberg corrupte databasekoppelingen</string> <string name="hide_broken_locations_title">Verberg corrupte databasekoppelingen</string>
<string name="remember_keyfile_locations_summary">Onthoud de locatie van databasesleutelbestanden</string> <string name="remember_keyfile_locations_summary">Onthoudt waar de databasesleutelbestanden zijn opgeslagen</string>
<string name="remember_database_locations_summary">Onthoud de locatie van databases</string> <string name="remember_database_locations_summary">Onthoudt waar de databases zijn opgeslagen</string>
<string name="auto_focus_search_summary">Zoekopdracht aanmaken bij het openen van een database</string> <string name="auto_focus_search_summary">Zoekopdracht aanmaken bij het openen van een database</string>
<string name="auto_focus_search_title">Snel zoeken</string> <string name="auto_focus_search_title">Snel zoeken</string>
<string name="menu_delete_entry_history">Geschiedenis wissen</string> <string name="menu_delete_entry_history">Geschiedenis wissen</string>
@@ -491,4 +491,12 @@
<string name="keyboard_previous_database_credentials_summary">Schakel automatisch terug naar het vorige toetsenbord op het databasereferentiescherm</string> <string name="keyboard_previous_database_credentials_summary">Schakel automatisch terug naar het vorige toetsenbord op het databasereferentiescherm</string>
<string name="keyboard_previous_database_credentials_title">Scherm Databasereferenties</string> <string name="keyboard_previous_database_credentials_title">Scherm Databasereferenties</string>
<string name="keyboard_change">Van toetsenbord wisselen</string> <string name="keyboard_change">Van toetsenbord wisselen</string>
<string name="upload_attachment">%1$s uploaden</string>
<string name="education_add_attachment_summary">Upload een bijlage bij dit item om belangrijke externe gegevens op te slaan.</string>
<string name="education_add_attachment_title">Bijlage toevoegen</string>
<string name="warning_sure_add_file">Toch het bestand toevoegen\?</string>
<string name="warning_file_too_big">Een KeePass-database mag alleen kleine hulpprogramma-bestanden bevatten (zoals PGP-sleutelbestanden).
\n
\nDe database kan erg groot worden en de prestaties kunnen verminderen bij deze upload.</string>
<string name="warning_replace_file">Als je dit bestand uploadt, wordt het bestaande vervangen.</string>
</resources> </resources>

View File

@@ -489,4 +489,12 @@
<string name="keyboard_previous_database_credentials_summary">Automatycznie przełącz się z powrotem do poprzedniej klawiatury na ekranie poświadczeń bazy danych</string> <string name="keyboard_previous_database_credentials_summary">Automatycznie przełącz się z powrotem do poprzedniej klawiatury na ekranie poświadczeń bazy danych</string>
<string name="keyboard_previous_database_credentials_title">Ekran poświadczeń bazy danych</string> <string name="keyboard_previous_database_credentials_title">Ekran poświadczeń bazy danych</string>
<string name="keyboard_change">Przełącz klawiaturę</string> <string name="keyboard_change">Przełącz klawiaturę</string>
<string name="upload_attachment">Prześlij %1$s</string>
<string name="education_add_attachment_summary">Prześlij załącznik do wpisu, aby zapisać ważne dane zewnętrzne.</string>
<string name="education_add_attachment_title">Dodawanie załącznika</string>
<string name="warning_sure_add_file">Czy mimo to dodać plik\?</string>
<string name="warning_replace_file">Przesłanie tego pliku spowoduje zastąpienie istniejącego.</string>
<string name="warning_file_too_big">Baza danych KeePass powinna zawierać tylko małe pliki narzędziowe (takie jak pliki kluczy PGP).
\n
\nTwoja baza danych może stać się bardzo duża i zmniejszyć wydajność dzięki temu wgrywaniu danych.</string>
</resources> </resources>

View File

@@ -425,7 +425,7 @@
<string name="menu_save_database">Сохранить базу</string> <string name="menu_save_database">Сохранить базу</string>
<string name="menu_empty_recycle_bin">Очистить \"корзину\"</string> <string name="menu_empty_recycle_bin">Очистить \"корзину\"</string>
<string name="command_execution">Выполнение команды…</string> <string name="command_execution">Выполнение команды…</string>
<string name="warning_permanently_delete_nodes">Безвозвратно удалить выбранные узлы\?</string> <string name="warning_permanently_delete_nodes">Безвозвратно удалить выбранные элементы\?</string>
<string name="keystore_not_accessible">Хранилище ключей не инициализировано должным образом.</string> <string name="keystore_not_accessible">Хранилище ключей не инициализировано должным образом.</string>
<string name="credential_before_click_biometric_button">Введите пароль, затем нажмите кнопку биометрии.</string> <string name="credential_before_click_biometric_button">Введите пароль, затем нажмите кнопку биометрии.</string>
<string name="recycle_bin_group_title">Группа \"корзины\"</string> <string name="recycle_bin_group_title">Группа \"корзины\"</string>
@@ -489,4 +489,12 @@
<string name="keyboard_previous_fill_in_title">Автоматическое действие кнопки</string> <string name="keyboard_previous_fill_in_title">Автоматическое действие кнопки</string>
<string name="keyboard_previous_database_credentials_summary">Автоматически переключаться на предыдущую клавиатуру на экране входа в базу</string> <string name="keyboard_previous_database_credentials_summary">Автоматически переключаться на предыдущую клавиатуру на экране входа в базу</string>
<string name="keyboard_change">Переключение клавиатуры</string> <string name="keyboard_change">Переключение клавиатуры</string>
<string name="upload_attachment">Добавить %1$s</string>
<string name="education_add_attachment_summary">В запись можно добавить файл как вложение, чтобы сохранить важные внешние данные.</string>
<string name="education_add_attachment_title">Добавление вложения</string>
<string name="warning_file_too_big">Предполагается, что база KeePass содержит только небольшие служебные файлы (например, файлы ключей PGP).
\n
\nЕсли добавить этот файл, база станет очень большой и снизится производительность.</string>
<string name="warning_replace_file">Добавление этого файла заменит существующий.</string>
<string name="warning_sure_add_file">Добавить файл в любом случае\?</string>
</resources> </resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="contribution">பங்களிப்பு</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 கடவுச்சொல் நிர்வாகியின் Android செயல்படுத்தல்</string>
<string name="homepage">வலைமனை</string>
<string name="contact">தொடர்பு கொள்ளுங்கள்</string>
<string name="feedback">பின்னூட்டம்</string>
</resources>

View File

@@ -473,4 +473,12 @@
<string name="keyboard_previous_database_credentials_summary">Veri tabanı kimlik bilgileri ekranında otomatik olarak önceki klavyeye dön</string> <string name="keyboard_previous_database_credentials_summary">Veri tabanı kimlik bilgileri ekranında otomatik olarak önceki klavyeye dön</string>
<string name="keyboard_previous_database_credentials_title">Veri tabanı kimlik bilgileri ekranı</string> <string name="keyboard_previous_database_credentials_title">Veri tabanı kimlik bilgileri ekranı</string>
<string name="keyboard_change">Klavye değiştir</string> <string name="keyboard_change">Klavye değiştir</string>
<string name="upload_attachment">%1$s yükle</string>
<string name="education_add_attachment_summary">Önemli harici verileri kaydetmek için girdinize bir ek yükleyin.</string>
<string name="education_add_attachment_title">Ek ekle</string>
<string name="warning_sure_add_file">Dosya yine de eklensin mi\?</string>
<string name="warning_replace_file">Bu dosyanın yüklenmesi mevcut dosyanın yerini alacaktır.</string>
<string name="warning_file_too_big">Bir KeePass veri tabanının sadece küçük yardımcı dosyaları (PGP anahtar dosyaları gibi) içermesi beklenmektedir.
\n
\nVeri tabanınız bu yüklemeyle çok büyük hale gelebilir ve performansı düşürebilir.</string>
</resources> </resources>

View File

@@ -489,4 +489,12 @@
<string name="keyboard_previous_fill_in_title">Самочинне введення</string> <string name="keyboard_previous_fill_in_title">Самочинне введення</string>
<string name="keyboard_previous_database_credentials_summary">Автоматичне перемикання до попередньої клавіатури, на екрані входу до бази даних</string> <string name="keyboard_previous_database_credentials_summary">Автоматичне перемикання до попередньої клавіатури, на екрані входу до бази даних</string>
<string name="keyboard_previous_database_credentials_title">Екран входу до бази даних</string> <string name="keyboard_previous_database_credentials_title">Екран входу до бази даних</string>
<string name="warning_file_too_big">База даних KeePass має містити лише невеликі файли утиліт (наприклад, файли ключів PGP).
\n
\nЧерез це завантаження, база даних може стати завеликою, що уповільнить роботу.</string>
<string name="education_add_attachment_summary">Завантажте вкладення до запису, щоб зберегти важливі зовнішні дані.</string>
<string name="upload_attachment">Завантажити %1$s</string>
<string name="education_add_attachment_title">Додати вкладення</string>
<string name="warning_sure_add_file">Все одно додати файл\?</string>
<string name="warning_replace_file">Завантаження цього файлу замінить наявний.</string>
</resources> </resources>

View File

@@ -489,4 +489,12 @@
<string name="keyboard_previous_fill_in_title">自动键动作</string> <string name="keyboard_previous_fill_in_title">自动键动作</string>
<string name="keyboard_previous_database_credentials_summary">如果显示数据库凭据屏幕,则自动返回到上一个键盘</string> <string name="keyboard_previous_database_credentials_summary">如果显示数据库凭据屏幕,则自动返回到上一个键盘</string>
<string name="keyboard_change">切换键盘</string> <string name="keyboard_change">切换键盘</string>
<string name="upload_attachment">上传 %1$s</string>
<string name="education_add_attachment_summary">将附件上传到您的条目以保存重要的外部数据。</string>
<string name="education_add_attachment_title">添加附件</string>
<string name="warning_sure_add_file">不论如何都要添加这个文件吗?</string>
<string name="warning_replace_file">上载此文件将替换现有文件。</string>
<string name="warning_file_too_big">KeePass数据库应该只包含小的实用程序文件(例如PGP密钥文件)。
\n
\n上传大文件会使增大数据库体积并降低性能。</string>
</resources> </resources>

View File

@@ -34,6 +34,7 @@
<string name="contribution_url" translatable="false">https://www.keepassdx.com/contribution</string> <string name="contribution_url" translatable="false">https://www.keepassdx.com/contribution</string>
<string name="homepage_url" translatable="false">https://www.keepassdx.com</string> <string name="homepage_url" translatable="false">https://www.keepassdx.com</string>
<string name="issues_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/issues</string> <string name="issues_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/issues</string>
<string name="credentials_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Credentials</string>
<string name="advanced_unlock_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Advanced-Unlocking</string> <string name="advanced_unlock_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Advanced-Unlocking</string>
<string name="magic_keyboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Magikeyboard</string> <string name="magic_keyboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Magikeyboard</string>
<string name="clipboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Clipboard</string> <string name="clipboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Clipboard</string>
@@ -178,8 +179,9 @@
<string name="database_custom_color_key" translatable="false">database_custom_color_key</string> <string name="database_custom_color_key" translatable="false">database_custom_color_key</string>
<string name="database_version_key" translatable="false">database_version_key</string> <string name="database_version_key" translatable="false">database_version_key</string>
<string name="database_category_compression_key" translatable="false">database_category_compression_key</string> <string name="database_category_data_key" translatable="false">database_category_data_key</string>
<string name="database_data_compression_key" translatable="false">database_data_compression_key</string> <string name="database_data_compression_key" translatable="false">database_data_compression_key</string>
<string name="database_data_remove_unlinked_attachments_key" translatable="false">database_data_remove_unlinked_attachments_key</string>
<string name="database_category_recycle_bin_key" translatable="false">database_category_recycle_bin_key</string> <string name="database_category_recycle_bin_key" translatable="false">database_category_recycle_bin_key</string>
<string name="recycle_bin_enable_key" translatable="false">recycle_bin_enable_key</string> <string name="recycle_bin_enable_key" translatable="false">recycle_bin_enable_key</string>

View File

@@ -53,6 +53,7 @@
<string name="content_description_add_group">Add group</string> <string name="content_description_add_group">Add group</string>
<string name="content_description_add_item">Add item</string> <string name="content_description_add_item">Add item</string>
<string name="content_description_file_information">File info</string> <string name="content_description_file_information">File info</string>
<string name="content_description_credentials_information">Credentials info</string>
<string name="content_description_password_checkbox">Password checkbox</string> <string name="content_description_password_checkbox">Password checkbox</string>
<string name="content_description_keyfile_checkbox">Keyfile checkbox</string> <string name="content_description_keyfile_checkbox">Keyfile checkbox</string>
<string name="content_description_repeat_toggle_password_visibility">Repeat toggle password visibility</string> <string name="content_description_repeat_toggle_password_visibility">Repeat toggle password visibility</string>
@@ -260,6 +261,10 @@
<string name="warning_file_too_big">A KeePass database is supposed to contain only small utility files (such as PGP key files).\n\nYour database may get very large and reduce performance with this upload.</string> <string name="warning_file_too_big">A KeePass database is supposed to contain only small utility files (such as PGP key files).\n\nYour database may get very large and reduce performance with this upload.</string>
<string name="warning_replace_file">Uploading this file will replace the existing one.</string> <string name="warning_replace_file">Uploading this file will replace the existing one.</string>
<string name="warning_sure_add_file">Add the file anyway?</string> <string name="warning_sure_add_file">Add the file anyway?</string>
<string name="warning_remove_unlinked_attachment">Removing unlinked data may decrease the size of your database but may also delete data used for KeePass plugins.</string>
<string name="warning_sure_remove_data">Remove this data anyway?</string>
<string name="warning_empty_keyfile">It is not recommended to add an empty keyfile.</string>
<string name="warning_empty_keyfile_explanation">The content of the keyfile should never be changed, and in the best case, should contain randomly generated data.</string>
<string name="version_label">Version %1$s</string> <string name="version_label">Version %1$s</string>
<string name="build_label">Build %1$s</string> <string name="build_label">Build %1$s</string>
<string name="configure_biometric">Biometric prompt is supported, but not set up.</string> <string name="configure_biometric">Biometric prompt is supported, but not set up.</string>
@@ -318,8 +323,11 @@
<string name="file_name">Filename</string> <string name="file_name">Filename</string>
<string name="path">Path</string> <string name="path">Path</string>
<string name="assign_master_key">Assign a master key</string> <string name="assign_master_key">Assign a master key</string>
<string name="data">Data</string>
<string name="database_data_compression_title">Data compression</string> <string name="database_data_compression_title">Data compression</string>
<string name="database_data_compression_summary">Data compression reduces the size of the database.</string> <string name="database_data_compression_summary">Data compression reduces the size of the database</string>
<string name="database_data_remove_unlinked_attachments_title">Remove unlinked data</string>
<string name="database_data_remove_unlinked_attachments_summary">Removes attachments contained in the database but not linked to an entry</string>
<string name="recycle_bin_title">Recycle bin usage</string> <string name="recycle_bin_title">Recycle bin usage</string>
<string name="recycle_bin_summary">Moves groups and entries to \"Recycle bin\" group before deleting</string> <string name="recycle_bin_summary">Moves groups and entries to \"Recycle bin\" group before deleting</string>
<string name="recycle_bin_group_title">Recycle bin group</string> <string name="recycle_bin_group_title">Recycle bin group</string>

View File

@@ -59,14 +59,19 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="@string/database_category_compression_key" android:key="@string/database_category_data_key"
android:title="@string/compression"> android:title="@string/data">
<com.kunzisoft.keepass.settings.preference.DialogListExplanationPreference <com.kunzisoft.keepass.settings.preference.DialogListExplanationPreference
android:key="@string/database_data_compression_key" android:key="@string/database_data_compression_key"
android:persistent="false" android:persistent="false"
android:title="@string/database_data_compression_title"/> android:title="@string/database_data_compression_title"/>
<com.kunzisoft.keepass.settings.preference.TextPreference
android:key="@string/database_data_remove_unlinked_attachments_key"
android:persistent="false"
android:title="@string/database_data_remove_unlinked_attachments_title"
android:summary="@string/database_data_remove_unlinked_attachments_summary"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4830"
version="1.1"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
inkscape:export-filename="/home/joker/Project/Scratcheck/TestExport.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
sodipodi:docname="ic_attachment_broken.svg">
<defs
id="defs4832" />
<sodipodi:namedview
id="base"
pagecolor="#acacac"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="15.999999"
inkscape:cx="9.9882808"
inkscape:cy="7.7944656"
inkscape:current-layer="g4770"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="1920"
inkscape:window-y="74"
inkscape:window-maximized="1">
<sodipodi:guide
position="0.99999471,22.999999"
orientation="22,0"
id="guide2987"
inkscape:locked="false" />
<sodipodi:guide
position="0.99999471,0.99999888"
orientation="0,22"
id="guide2989"
inkscape:locked="false" />
<sodipodi:guide
position="22.999995,0.99999888"
orientation="-22,0"
id="guide2991"
inkscape:locked="false" />
<sodipodi:guide
position="39,23"
orientation="0,-22"
id="guide2993"
inkscape:locked="false" />
<inkscape:grid
type="xygrid"
id="grid2989" />
</sodipodi:namedview>
<metadata
id="metadata4835">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0,-8)">
<g
id="g4770"
transform="matrix(1.7777778,0,0,1.7777778,-205.48441,-31.997877)">
<g
id="Layer_1"
transform="matrix(-0.00397893,0,0,0.00397893,125.58386,23.674135)" />
<path
style="stroke-width:0.02762278;fill:#ffffff;fill-opacity:1"
d="m 121.54264,23.061306 c -1.29654,0 -2.35766,1.061245 -2.35766,2.357666 v 4.94165 l 0.8844,-0.349365 v -4.592285 c 0,-0.824955 0.64832,-1.473267 1.47326,-1.473267 0.82493,0 1.47217,0.648312 1.47217,1.473267 v 2.302734 l 0.8844,-0.349365 v -1.953369 c 0,-1.296421 -1.06,-2.357666 -2.35657,-2.357666 z m 3.24097,2.6521 v 1.308471 l 0.8833,-0.348266 v -0.960205 z m -3.83093,0.8833 v 3.065186 l 0.8844,-0.349366 v -2.71582 z m 4.71423,2.496094 -0.8833,0.349365 v 2.753174 c 0,1.296558 -1.0611,2.356567 -2.35766,2.356567 -1.21907,0 -2.21717,-0.940492 -2.3335,-2.130249 l -0.85584,0.33838 c 0.26581,1.524958 1.58534,2.676269 3.18934,2.676269 1.79719,0 3.24096,-1.443745 3.24096,-3.240967 z m -1.7677,0.698731 -0.8844,0.350464 v 2.053344 c 0,0.323986 -0.26487,0.588867 -0.58886,0.588867 -0.32399,0 -0.58887,-0.264881 -0.58887,-0.588867 v -0.463623 l -0.8844,0.349365 v 0.114258 c 2e-5,0.824927 0.64832,1.473267 1.47327,1.473267 0.82496,0 1.47326,-0.64834 1.47326,-1.473267 z"
id="Icon_3_"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,6 @@
* Fix incomplete attachment deletion #684
* Fix opening database v1 without backup folder #692
* Fix ANR during first entry education #685
* Entry edition as fragment and manual views to fix focus #686
* Fix opening database with corrupted attachment #691
* Manage empty keyfile #679

View File

@@ -0,0 +1,6 @@
* Correction de la suppression incomplète des fichiers joints #684
* Correction de l'ouverture de base de données v1 sans dossier backup #692
* Correction de l'ANR lors de la formation de première entrée #685
* Édition d'entrée en tant que fragment et vues manuelles pour corriger le focus #686
* Correction de l'ouverture d'une base de données avec un fichier joint corrompu #691
* Gestion de fichier clé vide #679