mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'release/2.8.4'
This commit is contained in:
@@ -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)
|
||||
* Upload attachments
|
||||
* Visibility button for each hidden field
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 29
|
||||
versionCode = 39
|
||||
versionName = "2.8.3"
|
||||
versionCode = 40
|
||||
versionName = "2.8.4"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
|
||||
@@ -39,13 +39,13 @@ import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
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.Entry
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.education.EntryActivityEducation
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||
@@ -309,9 +309,11 @@ class EntryActivity : LockingActivity() {
|
||||
entryContentsView?.assignNotes(entry.notes)
|
||||
|
||||
// Assign custom fields
|
||||
if (entry.allowCustomFields()) {
|
||||
if (mDatabase?.allowEntryCustomFields() == true) {
|
||||
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
|
||||
if (allowCopyProtectedField) {
|
||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
|
||||
@@ -427,7 +429,7 @@ class EntryActivity : LockingActivity() {
|
||||
|
||||
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
||||
menu: Menu) {
|
||||
val entryFieldCopyView = findViewById<View>(R.id.entry_field_copy)
|
||||
val entryFieldCopyView = entryContentsView?.firstEntryFieldCopyView()
|
||||
val entryCopyEducationPerformed = entryFieldCopyView != null
|
||||
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
|
||||
entryFieldCopyView,
|
||||
|
||||
@@ -35,7 +35,9 @@ import android.widget.TimePicker
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.*
|
||||
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.icon.IconImage
|
||||
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.security.ProtectedString
|
||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||
import com.kunzisoft.keepass.model.*
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||
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_UPDATE_ENTRY_TASK
|
||||
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.utils.MenuUtil
|
||||
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.updateLockPaddingLeft
|
||||
import org.joda.time.DateTime
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class EntryEditActivity : LockingActivity(),
|
||||
IconPickerDialogFragment.IconPickerListener,
|
||||
@@ -81,24 +85,21 @@ class EntryEditActivity : LockingActivity(),
|
||||
// Refs of an entry and group in database, are not modifiable
|
||||
private var mEntry: Entry? = 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
|
||||
|
||||
// Views
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
private var scrollView: NestedScrollView? = null
|
||||
private var entryEditContentsView: EntryEditContentsView? = null
|
||||
private var entryEditFragment: EntryEditFragment? = null
|
||||
private var entryEditAddToolBar: Toolbar? = null
|
||||
private var validateButton: View? = null
|
||||
private var lockView: View? = null
|
||||
|
||||
private var mFocusedEditExtraField: FocusedEditField? = null
|
||||
|
||||
// To manage attachments
|
||||
private var mSelectFileHelper: SelectFileHelper? = null
|
||||
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||
private var mAllowMultipleAttachments: Boolean = false
|
||||
private var mTempAttachments = ArrayList<Attachment>()
|
||||
|
||||
// Education
|
||||
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
||||
@@ -118,22 +119,6 @@ class EntryEditActivity : LockingActivity(),
|
||||
scrollView = findViewById(R.id.entry_edit_scroll)
|
||||
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?.setOnClickListener {
|
||||
lockAndExit()
|
||||
@@ -148,6 +133,8 @@ class EntryEditActivity : LockingActivity(),
|
||||
// Likely the app has been killed exit the activity
|
||||
mDatabase = Database.getInstance()
|
||||
|
||||
var tempEntryInfo: EntryInfo? = null
|
||||
|
||||
// Entry is retrieve, it's an entry to update
|
||||
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
|
||||
mIsNew = false
|
||||
@@ -163,70 +150,77 @@ class EntryEditActivity : LockingActivity(),
|
||||
entry.parent = mParent
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
tempEntryInfo = mEntry?.getEntryInfo(mDatabase, true)
|
||||
}
|
||||
|
||||
// Parent is retrieve, it's a new entry to create
|
||||
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
|
||||
mIsNew = true
|
||||
// Create an empty new entry
|
||||
if (savedInstanceState?.containsKey(KEY_NEW_ENTRY) != true) {
|
||||
mNewEntry = mDatabase?.createEntry()
|
||||
}
|
||||
mParent = mDatabase?.getGroupById(it)
|
||||
// Add the default icon from parent if not a folder
|
||||
val parentIcon = mParent?.icon
|
||||
tempEntryInfo = mDatabase?.createEntry()?.getEntryInfo(mDatabase, true)
|
||||
// Set default icon
|
||||
if (parentIcon != null
|
||||
&& parentIcon.iconId != IconImage.UNKNOWN_ID
|
||||
&& parentIcon.iconId != IconImageStandard.FOLDER) {
|
||||
temporarilySaveAndShowSelectedIcon(parentIcon)
|
||||
} else {
|
||||
mDatabase?.drawFactory?.let { iconFactory ->
|
||||
entryEditContentsView?.setDefaultIcon(iconFactory)
|
||||
tempEntryInfo?.icon = parentIcon
|
||||
}
|
||||
// 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
|
||||
if (savedInstanceState?.containsKey(KEY_NEW_ENTRY) == true) {
|
||||
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY)
|
||||
// Retrieve temp attachments in case of deletion
|
||||
if (savedInstanceState?.containsKey(TEMP_ATTACHMENTS) == true) {
|
||||
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
|
||||
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
|
||||
entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
|
||||
entryEditAddToolBar?.apply {
|
||||
menuInflater.inflate(R.menu.entry_edit, menu)
|
||||
|
||||
menu.findItem(R.id.menu_add_field).apply {
|
||||
val allowCustomField = mNewEntry?.allowCustomFields() == true
|
||||
val allowCustomField = mDatabase?.allowEntryCustomFields() == true
|
||||
isEnabled = allowCustomField
|
||||
isVisible = allowCustomField
|
||||
}
|
||||
@@ -278,10 +272,24 @@ class EntryEditActivity : LockingActivity(),
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_CREATE_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()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve entry after database action", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
coordinatorLayout?.showActionError(result)
|
||||
}
|
||||
}
|
||||
@@ -305,13 +313,12 @@ class EntryEditActivity : LockingActivity(),
|
||||
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
|
||||
when (entryAttachmentState.downloadState) {
|
||||
AttachmentState.START -> {
|
||||
entryEditContentsView?.apply {
|
||||
entryEditFragment?.apply {
|
||||
// When only one attachment is allowed
|
||||
if (!mAllowMultipleAttachments) {
|
||||
clearAttachments()
|
||||
}
|
||||
putAttachment(entryAttachmentState)
|
||||
requestLayout()
|
||||
// Scroll to the attachment position
|
||||
getAttachmentViewPosition(entryAttachmentState) {
|
||||
scrollView?.smoothScrollTo(0, it.toInt())
|
||||
@@ -319,10 +326,10 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
AttachmentState.IN_PROGRESS -> {
|
||||
entryEditContentsView?.putAttachment(entryAttachmentState)
|
||||
entryEditFragment?.putAttachment(entryAttachmentState)
|
||||
}
|
||||
AttachmentState.COMPLETE -> {
|
||||
entryEditContentsView?.apply {
|
||||
entryEditFragment?.apply {
|
||||
putAttachment(entryAttachmentState)
|
||||
// Scroll to the attachment position
|
||||
getAttachmentViewPosition(entryAttachmentState) {
|
||||
@@ -331,8 +338,10 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
AttachmentState.ERROR -> {
|
||||
mDatabase?.removeAttachmentIfNotUsed(entryAttachmentState.attachment)
|
||||
entryEditContentsView?.removeAttachment(entryAttachmentState)
|
||||
entryEditFragment?.removeAttachment(entryAttachmentState)
|
||||
coordinatorLayout?.let {
|
||||
Snackbar.make(it, R.string.error_file_not_create, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -347,79 +356,6 @@ class EntryEditActivity : LockingActivity(),
|
||||
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
|
||||
*/
|
||||
@@ -439,20 +375,17 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
override fun onNewCustomFieldApproved(newField: Field) {
|
||||
entryEditContentsView?.apply {
|
||||
entryEditFragment?.apply {
|
||||
putExtraField(newField)
|
||||
getExtraFieldViewPosition(newField) { position ->
|
||||
scrollView?.smoothScrollTo(0, position.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
|
||||
entryEditContentsView?.replaceExtraField(oldField, newField)
|
||||
entryEditFragment?.replaceExtraField(oldField, newField)
|
||||
}
|
||||
|
||||
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?) {
|
||||
startUploadAttachment(attachmentToUploadUri, attachment)
|
||||
}
|
||||
|
||||
private fun startUploadAttachment(attachmentToUploadUri: Uri?, attachment: Attachment?) {
|
||||
if (attachmentToUploadUri != null && attachment != null) {
|
||||
// Start uploading in service
|
||||
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 ->
|
||||
val entryAttachment = Attachment(fileName, binaryAttachment)
|
||||
// Ask to replace the current attachment
|
||||
if ((mDatabase?.allowMultipleAttachments != true && entryEditContentsView?.containsAttachment() == true) ||
|
||||
entryEditContentsView?.containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD)) == true) {
|
||||
if ((mDatabase?.allowMultipleAttachments != true && entryEditFragment?.containsAttachment() == true) ||
|
||||
entryEditFragment?.containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD)) == true) {
|
||||
ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
|
||||
.show(supportFragmentManager, "replacementFileFragment")
|
||||
} else {
|
||||
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, entryAttachment)
|
||||
startUploadAttachment(attachmentToUploadUri, entryAttachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,7 +455,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
private fun setupOTP() {
|
||||
// Retrieve the current otpElement if exists
|
||||
// and open the dialog to set up the OTP
|
||||
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
|
||||
SetOTPDialogFragment.build(entryEditFragment?.getEntryInfo()?.otpModel)
|
||||
.show(supportFragmentManager, "addOTPDialog")
|
||||
}
|
||||
|
||||
@@ -523,18 +463,30 @@ class EntryEditActivity : LockingActivity(),
|
||||
* Saves the new entry or update an existing entry in the database
|
||||
*/
|
||||
private fun saveEntry() {
|
||||
// Launch a validation and show the error if present
|
||||
if (entryEditContentsView?.isValid() == true) {
|
||||
// Clone the entry
|
||||
mNewEntry?.let { newEntry ->
|
||||
// Get the temp entry
|
||||
entryEditFragment?.getEntryInfo()?.let { newEntryInfo ->
|
||||
|
||||
// WARNING Add the parent previously deleted
|
||||
newEntry.parent = mEntry?.parent
|
||||
if (mIsNew) {
|
||||
// Create new one
|
||||
mDatabase?.createEntry()
|
||||
} else {
|
||||
// Create a clone
|
||||
Entry(mEntry!!)
|
||||
}?.let { newEntry ->
|
||||
|
||||
newEntry.setEntryInfo(mDatabase, newEntryInfo)
|
||||
// Build info
|
||||
newEntry.lastAccessTime = 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
|
||||
if (mIsNew) {
|
||||
@@ -567,30 +519,23 @@ class EntryEditActivity : LockingActivity(),
|
||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||
|
||||
entryEditActivityEducation?.let {
|
||||
Handler().post { performedNextEducation(it) }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||
val passwordGeneratorView: View? = entryEditContentsView?.entryPasswordGeneratorView
|
||||
val generatePasswordEducationPerformed = passwordGeneratorView != null
|
||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
||||
passwordGeneratorView,
|
||||
{
|
||||
openPasswordGenerator()
|
||||
},
|
||||
{
|
||||
performedNextEducation(entryEditActivityEducation)
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
||||
entryEditActivityEducation?.let {
|
||||
Handler().post { performedNextEducation(it) }
|
||||
}
|
||||
)
|
||||
if (!generatePasswordEducationPerformed) {
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||
if (entryEditFragment?.generatePasswordEducationPerformed(entryEditActivityEducation) != true) {
|
||||
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
|
||||
val addNewFieldEducationPerformed = mNewEntry != null
|
||||
&& mNewEntry!!.allowCustomFields() && addNewFieldView != null
|
||||
&& addNewFieldView.visibility == View.VISIBLE
|
||||
val addNewFieldEducationPerformed = mDatabase?.allowEntryCustomFields() == true
|
||||
&& addNewFieldView != null
|
||||
&& addNewFieldView.isVisible
|
||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
||||
addNewFieldView,
|
||||
{
|
||||
@@ -602,7 +547,8 @@ class EntryEditActivity : LockingActivity(),
|
||||
)
|
||||
if (!addNewFieldEducationPerformed) {
|
||||
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(
|
||||
attachmentView,
|
||||
{
|
||||
@@ -614,7 +560,8 @@ class EntryEditActivity : LockingActivity(),
|
||||
)
|
||||
if (!addAttachmentEducationPerformed) {
|
||||
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
|
||||
setupOtpView != null && setupOtpView.visibility == View.VISIBLE
|
||||
setupOtpView != null
|
||||
&& setupOtpView.isVisible
|
||||
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
|
||||
setupOtpView,
|
||||
{
|
||||
@@ -644,21 +591,28 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
override fun onOtpCreated(otpElement: OtpElement) {
|
||||
// Update the otp field with otpauth:// url
|
||||
val otpField = OtpEntryFields.buildOtpField(otpElement,
|
||||
mEntry?.title, mEntry?.username)
|
||||
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
|
||||
entryEditContentsView?.apply {
|
||||
putExtraField(otpField)
|
||||
getExtraFieldViewPosition(otpField) { position ->
|
||||
scrollView?.smoothScrollTo(0, position.toInt())
|
||||
var titleOTP: String? = null
|
||||
var usernameOTP: String? = null
|
||||
// Build a temp entry to get title and username (by ref)
|
||||
entryEditFragment?.getEntryInfo()?.let { entryInfo ->
|
||||
val entryTemp = mDatabase?.createEntry()
|
||||
entryTemp?.setEntryInfo(mDatabase, entryInfo)
|
||||
mDatabase?.startManageEntry(entryTemp)
|
||||
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) {
|
||||
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
|
||||
temporarilySaveAndShowSelectedIcon(icon)
|
||||
entryEditFragment?.icon = icon
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,9 +620,9 @@ class EntryEditActivity : LockingActivity(),
|
||||
// To fix android 4.4 issue
|
||||
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
|
||||
if (datePicker?.isShown == true) {
|
||||
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
|
||||
entryEditFragment?.expiryTime?.date?.let { expiresDate ->
|
||||
// Save the date
|
||||
entryEditContentsView?.expiresDate =
|
||||
entryEditFragment?.expiryTime =
|
||||
DateInstant(DateTime(expiresDate)
|
||||
.withYear(year)
|
||||
.withMonthOfYear(month + 1)
|
||||
@@ -685,9 +639,9 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
|
||||
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
|
||||
entryEditFragment?.expiryTime?.date?.let { expiresDate ->
|
||||
// Save the date
|
||||
entryEditContentsView?.expiresDate =
|
||||
entryEditFragment?.expiryTime =
|
||||
DateInstant(DateTime(expiresDate)
|
||||
.withHourOfDay(hours)
|
||||
.withMinuteOfHour(minutes)
|
||||
@@ -696,21 +650,15 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
mNewEntry?.let {
|
||||
populateEntryWithViews(it)
|
||||
outState.putParcelable(KEY_NEW_ENTRY, it)
|
||||
}
|
||||
|
||||
mFocusedEditExtraField?.let {
|
||||
outState.putParcelable(EXTRA_FIELD_FOCUSED_ENTRY, it)
|
||||
}
|
||||
outState.putParcelableArrayList(TEMP_ATTACHMENTS, mTempAttachments)
|
||||
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun acceptPassword(bundle: Bundle) {
|
||||
bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID)?.let {
|
||||
entryEditContentsView?.password = it
|
||||
entryEditFragment?.password = it
|
||||
}
|
||||
|
||||
entryEditActivityEducation?.let {
|
||||
@@ -734,10 +682,10 @@ class EntryEditActivity : LockingActivity(),
|
||||
override fun finish() {
|
||||
// Assign entry callback as a result in all case
|
||||
try {
|
||||
mNewEntry?.let {
|
||||
mEntry?.let { entry ->
|
||||
val bundle = Bundle()
|
||||
val intentEntry = Intent()
|
||||
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mNewEntry)
|
||||
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry)
|
||||
intentEntry.putExtras(bundle)
|
||||
if (mIsNew) {
|
||||
setResult(ADD_ENTRY_RESULT_CODE, intentEntry)
|
||||
@@ -761,8 +709,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
const val KEY_PARENT = "parent"
|
||||
|
||||
// SaveInstanceState
|
||||
const val KEY_NEW_ENTRY = "new_entry"
|
||||
const val EXTRA_FIELD_FOCUSED_ENTRY = "EXTRA_FIELD_FOCUSED_ENTRY"
|
||||
const val TEMP_ATTACHMENTS = "TEMP_ATTACHMENTS"
|
||||
|
||||
// Keys for callback
|
||||
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_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
|
||||
|
||||
const val ENTRY_EDIT_FRAGMENT_TAG = "ENTRY_EDIT_FRAGMENT_TAG"
|
||||
|
||||
/**
|
||||
* Launch EntryEditActivity to update an existing entry
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -97,6 +97,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
nodeClickListener = null
|
||||
onScrollListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
||||
@@ -26,15 +26,18 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||
|
||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
@@ -58,6 +61,10 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
activity?.let { activity ->
|
||||
|
||||
@@ -99,11 +117,15 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
|
||||
rootView = inflater.inflate(R.layout.fragment_set_password, null)
|
||||
builder.setView(rootView)
|
||||
.setTitle(R.string.assign_master_key)
|
||||
// Add action buttons
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
.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)
|
||||
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
|
||||
passwordView = rootView?.findViewById(R.id.pass_password)
|
||||
@@ -129,7 +151,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
mMasterPassword = ""
|
||||
mKeyFile = null
|
||||
|
||||
var error = verifyPassword() || verifyFile()
|
||||
var error = verifyPassword() || verifyKeyFile()
|
||||
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
|
||||
error = true
|
||||
if (allowNoMasterKey)
|
||||
@@ -199,7 +221,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
return error
|
||||
}
|
||||
|
||||
private fun verifyFile(): Boolean {
|
||||
private fun verifyKeyFile(): Boolean {
|
||||
var error = false
|
||||
if (keyFileCheckBox != null
|
||||
&& keyFileCheckBox!!.isChecked) {
|
||||
@@ -219,7 +241,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
val builder = AlertDialog.Builder(it)
|
||||
builder.setMessage(R.string.warning_empty_password)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (!verifyFile()) {
|
||||
if (!verifyKeyFile()) {
|
||||
mListener?.onAssignKeyDialogPositiveClick(
|
||||
passwordCheckBox!!.isChecked, mMasterPassword,
|
||||
keyFileCheckBox!!.isChecked, mKeyFile)
|
||||
@@ -227,7 +249,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
builder.create().show()
|
||||
mEmptyPasswordConfirmationDialog = builder.create()
|
||||
mEmptyPasswordConfirmationDialog?.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +265,28 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
this@AssignMasterKeyDialogFragment.dismiss()
|
||||
}
|
||||
.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 ->
|
||||
uri?.let { pathUri ->
|
||||
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
|
||||
keyFileSelectionView?.error = null
|
||||
keyFileCheckBox?.isChecked = true
|
||||
keyFileSelectionView?.uri = pathUri
|
||||
if (lengthFile <= 0L) {
|
||||
showEmptyKeyFileConfirmationDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ class DatePickerFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
// Create a new instance of DatePickerDialog and return it
|
||||
return context?.let {
|
||||
|
||||
@@ -46,6 +46,11 @@ class DeleteNodesDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
||||
arguments?.apply {
|
||||
|
||||
@@ -60,6 +60,11 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
entryCustomFieldListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
val root = activity.layoutInflater.inflate(R.layout.fragment_entry_new_field, null)
|
||||
|
||||
@@ -46,6 +46,11 @@ class FileTooBigDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mActionChooseListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
// Use the Builder class for convenient dialog construction
|
||||
|
||||
@@ -64,6 +64,11 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
|
||||
@@ -73,7 +73,11 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + GroupEditDialogFragment::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
editGroupListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
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 {
|
||||
activity?.let { activity ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
@@ -132,7 +138,7 @@ class IconPickerDialogFragment : DialogFragment() {
|
||||
return bundle.getParcelable(KEY_ICON_STANDARD)
|
||||
}
|
||||
|
||||
fun launch(activity: AppCompatActivity) {
|
||||
fun launch(activity: FragmentActivity) {
|
||||
// Create an instance of the dialog fragment and show it
|
||||
val dialog = IconPickerDialogFragment()
|
||||
dialog.show(activity.supportFragmentManager, "IconPickerDialogFragment")
|
||||
|
||||
@@ -21,20 +21,51 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
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 {
|
||||
|
||||
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 ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
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() }
|
||||
|
||||
return builder.create()
|
||||
@@ -42,5 +73,36 @@ class PasswordEncodingDialogFragment : DialogFragment() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ class ReplaceFileDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mActionChooseListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
// Use the Builder class for convenient dialog construction
|
||||
|
||||
@@ -107,6 +107,11 @@ class SetOTPDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mCreateOTPElementListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ class SortDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
|
||||
@@ -25,6 +25,11 @@ class TimePickerFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
// Create a new instance of DatePickerDialog and return it
|
||||
return context?.let {
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
package com.kunzisoft.keepass.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.graphics.Color
|
||||
import android.text.format.Formatter
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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.StreamDirection
|
||||
|
||||
|
||||
class EntryAttachmentsItemsAdapter(context: Context)
|
||||
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
||||
|
||||
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 {
|
||||
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
|
||||
}
|
||||
@@ -46,7 +62,20 @@ class EntryAttachmentsItemsAdapter(context: Context)
|
||||
val entryAttachmentState = itemsList[position]
|
||||
|
||||
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
|
||||
if (entryAttachmentState.attachment.binaryAttachment.isCorrupted) {
|
||||
holder.binaryFileTitle.setTextColor(Color.RED)
|
||||
} else {
|
||||
holder.binaryFileTitle.setTextColor(mTitleColor)
|
||||
}
|
||||
holder.binaryFileSize.text = Formatter.formatFileSize(context,
|
||||
entryAttachmentState.attachment.binaryAttachment.length())
|
||||
holder.binaryFileCompression.apply {
|
||||
@@ -107,6 +136,7 @@ class EntryAttachmentsItemsAdapter(context: Context)
|
||||
|
||||
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 binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
|
||||
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import android.content.*
|
||||
import android.content.Context.BIND_ABOVE_CLIENT
|
||||
import android.content.Context.BIND_NOT_FOREGROUND
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
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_LOAD_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_SAVE
|
||||
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)
|
||||
}
|
||||
|
||||
fun startDatabaseRemoveUnlinkedData(save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK)
|
||||
}
|
||||
|
||||
fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int,
|
||||
newMaxHistoryItems: Int,
|
||||
save: Boolean) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -461,12 +461,13 @@ class Database {
|
||||
|
||||
fun removeAttachmentIfNotUsed(attachment: Attachment) {
|
||||
// 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() {
|
||||
// No check in database KDB because unique attachment by entry
|
||||
mDatabaseKDBX?.removeUnlinkedAttachments()
|
||||
mDatabaseKDBX?.removeUnlinkedAttachments(true)
|
||||
}
|
||||
|
||||
@Throws(DatabaseOutputException::class)
|
||||
@@ -822,18 +823,25 @@ class Database {
|
||||
}
|
||||
}
|
||||
|
||||
fun startManageEntry(entry: Entry) {
|
||||
fun startManageEntry(entry: Entry?) {
|
||||
mDatabaseKDBX?.let {
|
||||
entry.startToManageFieldReferences(it)
|
||||
entry?.startToManageFieldReferences(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopManageEntry(entry: Entry) {
|
||||
fun stopManageEntry(entry: Entry?) {
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,8 @@ import android.content.res.Resources
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import org.joda.time.Duration
|
||||
import org.joda.time.Instant
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@@ -95,6 +97,7 @@ class DateInstant : Parcelable {
|
||||
companion object {
|
||||
|
||||
val NEVER_EXPIRE = neverExpire
|
||||
val IN_ONE_MONTH = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
|
||||
private val dateFormat = SimpleDateFormat.getDateTimeInstance()
|
||||
|
||||
private val neverExpire: DateInstant
|
||||
|
||||
@@ -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.NodeIdUUID
|
||||
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.Field
|
||||
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
|
||||
*/
|
||||
val customFields: HashMap<String, ProtectedString>
|
||||
get() = entryKDBX?.customFields ?: HashMap()
|
||||
|
||||
/**
|
||||
* To redefine if version of entry allow custom field,
|
||||
* @return true if entry allows custom field
|
||||
*/
|
||||
fun allowCustomFields(): Boolean {
|
||||
return entryKDBX?.allowCustomFields() ?: false
|
||||
fun getExtraFields(): List<Field> {
|
||||
val extraFields = ArrayList<Field>()
|
||||
entryKDBX?.let {
|
||||
for (field in it.customFields) {
|
||||
extraFields.add(Field(field.key, field.value))
|
||||
}
|
||||
|
||||
fun removeAllFields() {
|
||||
entryKDBX?.removeAllFields()
|
||||
}
|
||||
return extraFields
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
entryKDBX?.putExtraField(label, value)
|
||||
fun putExtraField(field: Field) {
|
||||
entryKDBX?.putExtraField(field.name, field.protectedValue)
|
||||
}
|
||||
|
||||
private fun addExtraFields(fields: List<Field>) {
|
||||
fields.forEach {
|
||||
putExtraField(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeAllFields() {
|
||||
entryKDBX?.removeAllFields()
|
||||
}
|
||||
|
||||
fun getOtpElement(): OtpElement? {
|
||||
entryKDBX?.let {
|
||||
return OtpEntryFields.parseFields { key ->
|
||||
customFields[key]?.toString()
|
||||
it.customFields[key]?.toString()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun startToManageFieldReferences(database: DatabaseKDBX) {
|
||||
entryKDBX?.startToManageFieldReferences(database)
|
||||
@@ -341,16 +346,27 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
|| entryKDBX?.containsAttachment() == true
|
||||
}
|
||||
|
||||
fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
|
||||
entryKDB?.putAttachment(attachment)
|
||||
entryKDBX?.putAttachment(attachment, binaryPool)
|
||||
private fun addAttachments(binaryPool: BinaryPool, attachments: List<Attachment>) {
|
||||
attachments.forEach {
|
||||
putAttachment(it, binaryPool)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeAttachment(attachment: Attachment) {
|
||||
private fun removeAttachment(attachment: Attachment) {
|
||||
entryKDB?.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> {
|
||||
val history = ArrayList<Entry>()
|
||||
val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
|
||||
@@ -404,26 +420,54 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
database?.stopManageEntry(this)
|
||||
else
|
||||
database?.startManageEntry(this)
|
||||
|
||||
entryInfo.id = nodeId.toString()
|
||||
entryInfo.title = title
|
||||
entryInfo.icon = icon
|
||||
entryInfo.username = username
|
||||
entryInfo.password = password
|
||||
entryInfo.expires = expires
|
||||
entryInfo.expiryTime = expiryTime
|
||||
entryInfo.url = url
|
||||
entryInfo.notes = notes
|
||||
for (entry in customFields.entries) {
|
||||
entryInfo.customFields.add(
|
||||
Field(entry.key, entry.value))
|
||||
}
|
||||
entryInfo.customFields = getExtraFields()
|
||||
// Add otpElement to generate token
|
||||
entryInfo.otpModel = getOtpElement()?.otpModel
|
||||
if (!raw) {
|
||||
// Replace parameter fields by generated OTP fields
|
||||
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
|
||||
}
|
||||
database?.binaryPool?.let { binaryPool ->
|
||||
entryInfo.attachments = getAttachments(binaryPool)
|
||||
}
|
||||
|
||||
if (!raw)
|
||||
database?.stopManageEntry(this)
|
||||
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 {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
@@ -32,6 +32,7 @@ class BinaryAttachment : Parcelable {
|
||||
private set
|
||||
var isProtected: Boolean = false
|
||||
private set
|
||||
var isCorrupted: Boolean = false
|
||||
private var dataFile: File? = null
|
||||
|
||||
fun length(): Long {
|
||||
@@ -43,11 +44,7 @@ class BinaryAttachment : Parcelable {
|
||||
/**
|
||||
* Empty protected binary
|
||||
*/
|
||||
constructor() {
|
||||
this.isCompressed = false
|
||||
this.isProtected = false
|
||||
this.dataFile = null
|
||||
}
|
||||
constructor()
|
||||
|
||||
constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean = false) {
|
||||
this.isCompressed = compressed
|
||||
@@ -59,6 +56,7 @@ class BinaryAttachment : Parcelable {
|
||||
val compressedByte = parcel.readByte().toInt()
|
||||
isCompressed = compressedByte != 0
|
||||
isProtected = parcel.readByte().toInt() != 0
|
||||
isCorrupted = parcel.readByte().toInt() != 0
|
||||
parcel.readString()?.let {
|
||||
dataFile = File(it)
|
||||
}
|
||||
@@ -164,6 +162,7 @@ class BinaryAttachment : Parcelable {
|
||||
|
||||
return isCompressed == other.isCompressed
|
||||
&& isProtected == other.isProtected
|
||||
&& isCorrupted == other.isCorrupted
|
||||
&& sameData
|
||||
}
|
||||
|
||||
@@ -172,6 +171,7 @@ class BinaryAttachment : Parcelable {
|
||||
var result = 0
|
||||
result = 31 * result + if (isCompressed) 1 else 0
|
||||
result = 31 * result + if (isProtected) 1 else 0
|
||||
result = 31 * result + if (isCorrupted) 1 else 0
|
||||
result = 31 * result + dataFile!!.hashCode()
|
||||
return result
|
||||
}
|
||||
@@ -187,6 +187,7 @@ class BinaryAttachment : Parcelable {
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeByte((if (isCompressed) 1 else 0).toByte())
|
||||
dest.writeByte((if (isProtected) 1 else 0).toByte())
|
||||
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
|
||||
dest.writeString(dataFile?.absolutePath)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
// Retrieve backup group in index
|
||||
val backupGroup: GroupKDB?
|
||||
get() {
|
||||
if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
|
||||
ensureBackupExists()
|
||||
return if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
|
||||
null
|
||||
else
|
||||
@@ -186,6 +184,9 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
override fun isInRecycleBin(group: GroupKDB): Boolean {
|
||||
var currentGroup: GroupKDB? = group
|
||||
|
||||
if (backupGroup == null)
|
||||
return false
|
||||
|
||||
if (currentGroup == backupGroup)
|
||||
return true
|
||||
|
||||
@@ -229,6 +230,8 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
* @return true if node can be recycle, false elsewhere
|
||||
*/
|
||||
fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
|
||||
if (backupGroup == null)
|
||||
ensureBackupExists()
|
||||
if (node == backupGroup)
|
||||
return false
|
||||
backupGroup?.let {
|
||||
@@ -239,14 +242,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
}
|
||||
|
||||
fun recycle(group: GroupKDB) {
|
||||
ensureBackupExists()
|
||||
removeGroupFrom(group, group.parent)
|
||||
addGroupTo(group, backupGroup)
|
||||
group.afterAssignNewParent()
|
||||
}
|
||||
|
||||
fun recycle(entry: EntryKDB) {
|
||||
ensureBackupExists()
|
||||
removeEntryFrom(entry, entry.parent)
|
||||
addEntryTo(entry, backupGroup)
|
||||
entry.afterAssignNewParent()
|
||||
|
||||
@@ -30,9 +30,9 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
@@ -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.MemoryProtectionConfig
|
||||
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
@@ -570,12 +569,17 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
return binaryAttachment
|
||||
}
|
||||
|
||||
fun removeAttachmentIfNotUsed(attachment: Attachment) {
|
||||
// Remove attachment from pool
|
||||
removeUnlinkedAttachments(attachment.binaryAttachment)
|
||||
fun removeUnlinkedAttachment(binary: BinaryAttachment, clear: Boolean) {
|
||||
val listBinaries = ArrayList<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
|
||||
val binariesToRemove = ArrayList<BinaryAttachment>()
|
||||
if (binaries.isEmpty()) {
|
||||
@@ -598,6 +602,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
binariesToRemove.forEach {
|
||||
try {
|
||||
binaryPool.remove(it)
|
||||
if (clear)
|
||||
it.clear()
|
||||
} catch (e: Exception) {
|
||||
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 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
|
||||
}
|
||||
|
||||
@@ -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.security.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.KeyFileEmptyDatabaseException
|
||||
import java.io.*
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
@@ -136,7 +135,6 @@ abstract class DatabaseVersioned<
|
||||
}
|
||||
|
||||
when (keyData.size.toLong()) {
|
||||
0L -> throw KeyFileEmptyDatabaseException()
|
||||
32L -> return keyData
|
||||
64L -> try {
|
||||
return hexStringToByteArray(String(keyData))
|
||||
|
||||
@@ -153,8 +153,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
|
||||
this.binaryData = attachment.binaryAttachment
|
||||
}
|
||||
|
||||
fun removeAttachment(attachment: Attachment) {
|
||||
if (this.binaryDescription == attachment.name) {
|
||||
fun removeAttachment(attachment: Attachment? = null) {
|
||||
if (attachment == null || this.binaryDescription == attachment.name) {
|
||||
this.binaryDescription = ""
|
||||
this.binaryData = null
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ import com.kunzisoft.keepass.utils.ParcelableUtil
|
||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.collections.LinkedHashMap
|
||||
|
||||
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
||||
@@ -272,10 +271,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
return field
|
||||
}
|
||||
|
||||
fun allowCustomFields(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun removeAllFields() {
|
||||
fields.clear()
|
||||
}
|
||||
@@ -314,6 +309,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
binaries.remove(attachment.name)
|
||||
}
|
||||
|
||||
fun removeAttachments() {
|
||||
binaries.clear()
|
||||
}
|
||||
|
||||
private fun getAttachmentsSize(binaryPool: BinaryPool): Long {
|
||||
var size = 0L
|
||||
for ((label, poolId) in binaries) {
|
||||
@@ -323,11 +322,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
return size
|
||||
}
|
||||
|
||||
// TODO Remove ?
|
||||
fun sizeOfHistory(): Int {
|
||||
return history.size
|
||||
}
|
||||
|
||||
override fun putCustomData(key: String, value: String) {
|
||||
customData[key] = value
|
||||
}
|
||||
|
||||
@@ -116,13 +116,6 @@ class InvalidCredentialsDatabaseException : LoadDatabaseException {
|
||||
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 {
|
||||
@StringRes
|
||||
override var errorId: Int = R.string.error_out_of_memory
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.file.input
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||
import com.kunzisoft.keepass.crypto.StreamCipherFactory
|
||||
@@ -742,8 +743,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
if (entryInHistory) {
|
||||
ctxEntry = ctxHistoryBase
|
||||
return KdbContext.EntryHistory
|
||||
}
|
||||
else if (ctxEntry != null) {
|
||||
} else if (ctxEntry != null) {
|
||||
// Add entry to the index only when close the XML element
|
||||
mDatabase.addEntryIndex(ctxEntry!!)
|
||||
}
|
||||
@@ -879,9 +879,14 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
if (encoded.isEmpty()) {
|
||||
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)
|
||||
@@ -981,12 +986,19 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
val base64 = readString(xpp)
|
||||
if (base64.isEmpty())
|
||||
return null
|
||||
val data = Base64.decode(base64, BASE_64_FLAG)
|
||||
|
||||
// Build the new binary and compress
|
||||
val binaryAttachment = mDatabase.buildNewBinary(cacheDirectory, protected, compressed, binaryId)
|
||||
try {
|
||||
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
|
||||
}
|
||||
@@ -1045,6 +1057,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = DatabaseInputKDBX::class.java.name
|
||||
|
||||
private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
|
||||
|
||||
@Throws(XmlPullParserException::class)
|
||||
|
||||
@@ -21,7 +21,10 @@ package com.kunzisoft.keepass.model
|
||||
|
||||
import android.os.Parcel
|
||||
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.IconImageStandard
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||
import java.util.*
|
||||
@@ -30,12 +33,15 @@ class EntryInfo : Parcelable {
|
||||
|
||||
var id: String = ""
|
||||
var title: String = ""
|
||||
var icon: IconImage? = null
|
||||
var icon: IconImage = IconImageStandard()
|
||||
var username: String = ""
|
||||
var password: String = ""
|
||||
var expires: Boolean = false
|
||||
var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH
|
||||
var url: String = ""
|
||||
var notes: String = ""
|
||||
var customFields: MutableList<Field> = ArrayList()
|
||||
var customFields: List<Field> = ArrayList()
|
||||
var attachments: List<Attachment> = ArrayList()
|
||||
var otpModel: OtpModel? = null
|
||||
|
||||
constructor()
|
||||
@@ -43,12 +49,15 @@ class EntryInfo : Parcelable {
|
||||
private constructor(parcel: Parcel) {
|
||||
id = parcel.readString() ?: id
|
||||
title = parcel.readString() ?: title
|
||||
icon = parcel.readParcelable(IconImage::class.java.classLoader)
|
||||
icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
|
||||
username = parcel.readString() ?: username
|
||||
password = parcel.readString() ?: password
|
||||
expires = parcel.readInt() != 0
|
||||
expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
|
||||
url = parcel.readString() ?: url
|
||||
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
|
||||
}
|
||||
|
||||
@@ -62,9 +71,12 @@ class EntryInfo : Parcelable {
|
||||
parcel.writeParcelable(icon, flags)
|
||||
parcel.writeString(username)
|
||||
parcel.writeString(password)
|
||||
parcel.writeInt(if (expires) 1 else 0)
|
||||
parcel.writeParcelable(expiryTime, flags)
|
||||
parcel.writeString(url)
|
||||
parcel.writeString(notes)
|
||||
parcel.writeArray(customFields.toTypedArray())
|
||||
parcel.writeArray(attachments.toTypedArray())
|
||||
parcel.writeParcelable(otpModel, flags)
|
||||
}
|
||||
|
||||
|
||||
@@ -58,15 +58,9 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
|
||||
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||
mActionTaskListeners.add(actionTaskListener)
|
||||
attachmentNotificationList.forEach {
|
||||
it.attachmentFileAction?.listener = attachmentFileActionListener
|
||||
}
|
||||
}
|
||||
|
||||
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||
attachmentNotificationList.forEach {
|
||||
it.attachmentFileAction?.listener = null
|
||||
}
|
||||
mActionTaskListeners.remove(actionTaskListener)
|
||||
}
|
||||
}
|
||||
@@ -107,6 +101,13 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
intent,
|
||||
StreamDirection.DOWNLOAD)
|
||||
}
|
||||
ACTION_ATTACHMENT_REMOVE -> {
|
||||
intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
|
||||
attachmentNotificationList.firstOrNull { it.entryAttachmentState.attachment == entryAttachment }?.let { elementToRemove ->
|
||||
attachmentNotificationList.remove(elementToRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (downloadFileUri != null) {
|
||||
attachmentNotificationList.firstOrNull { it.uri == downloadFileUri }?.let { elementToRemove ->
|
||||
@@ -265,6 +266,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
?.notificationId ?: notificationId) + 1
|
||||
val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection)
|
||||
val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState)
|
||||
|
||||
// Add action to the list on start
|
||||
attachmentNotificationList.add(attachmentNotification)
|
||||
|
||||
mainScope.launch {
|
||||
@@ -293,15 +296,18 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
}
|
||||
|
||||
suspend fun executeAction() {
|
||||
TimeoutHelper.temporarilyDisableTimeout()
|
||||
|
||||
// on pre execute
|
||||
attachmentNotification.attachmentFileAction = this
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
TimeoutHelper.temporarilyDisableTimeout()
|
||||
|
||||
attachmentNotification.attachmentFileAction = this@AttachmentFileAction
|
||||
attachmentNotification.entryAttachmentState.apply {
|
||||
downloadState = AttachmentState.START
|
||||
downloadProgression = 0
|
||||
}
|
||||
listener?.onUpdate(attachmentNotification)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
// 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_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
|
||||
const val ACTION_ATTACHMENT_REMOVE = "ACTION_ATTACHMENT_REMOVE"
|
||||
|
||||
const val FILE_URI_KEY = "FILE_URI_KEY"
|
||||
const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
|
||||
|
||||
@@ -141,6 +141,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent)
|
||||
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent)
|
||||
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent)
|
||||
ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK -> buildDatabaseRemoveUnlinkedDataActionTask(intent)
|
||||
ACTION_DATABASE_UPDATE_NAME_TASK,
|
||||
ACTION_DATABASE_UPDATE_DESCRIPTION_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? {
|
||||
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
|
||||
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_COLOR_TASK = "ACTION_DATABASE_UPDATE_COLOR_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_SIZE_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK"
|
||||
const val ACTION_DATABASE_UPDATE_ENCRYPTION_TASK = "ACTION_DATABASE_UPDATE_ENCRYPTION_TASK"
|
||||
|
||||
@@ -347,7 +347,7 @@ object OtpEntryFields {
|
||||
* Build new generated fields in a new list from [fieldsToParse] in parameter,
|
||||
* 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)
|
||||
// Remove parameter fields
|
||||
val otpField = Field(OTP_FIELD)
|
||||
|
||||
@@ -40,6 +40,11 @@ class MainPreferenceFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mCallback = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
findPreference<Preference>(getString(R.string.database_version_key))
|
||||
?.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
|
||||
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
|
||||
@@ -482,6 +482,9 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
getString(R.string.database_data_compression_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) -> {
|
||||
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ import com.kunzisoft.keepass.view.showActionError
|
||||
open class SettingsActivity
|
||||
: LockingActivity(),
|
||||
MainPreferenceFragment.Callback,
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
PasswordEncodingDialogFragment.Listener {
|
||||
|
||||
private var backupManager: BackupManager? = null
|
||||
|
||||
@@ -50,23 +51,6 @@ open class SettingsActivity
|
||||
private var toolbar: Toolbar? = 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
|
||||
* @return The main fragment
|
||||
@@ -83,7 +67,11 @@ open class SettingsActivity
|
||||
|
||||
coordinatorLayout = findViewById(R.id.toolbar_coordinator)
|
||||
toolbar = findViewById(R.id.toolbar)
|
||||
|
||||
if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty())
|
||||
toolbar?.setTitle(R.string.settings)
|
||||
else
|
||||
toolbar?.title = savedInstanceState?.getString(TITLE_KEY)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
@@ -128,6 +116,22 @@ open class SettingsActivity
|
||||
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,
|
||||
masterPassword: String?,
|
||||
keyFileChecked: Boolean,
|
||||
@@ -144,18 +148,12 @@ open class SettingsActivity
|
||||
keyFile
|
||||
)
|
||||
} else {
|
||||
PasswordEncodingDialogFragment().apply {
|
||||
positiveButtonClickListener = DialogInterface.OnClickListener { _, _ ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
|
||||
databaseUri,
|
||||
PasswordEncodingDialogFragment.getInstance(databaseUri,
|
||||
masterPasswordChecked,
|
||||
masterPassword,
|
||||
keyFileChecked,
|
||||
keyFile
|
||||
)
|
||||
}
|
||||
show(supportFragmentManager, "passwordEncodingTag")
|
||||
}
|
||||
).show(supportFragmentManager, "passwordEncodingTag")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,8 +162,7 @@ open class SettingsActivity
|
||||
override fun onAssignKeyDialogNegativeClick(masterPasswordChecked: Boolean,
|
||||
masterPassword: String?,
|
||||
keyFileChecked: Boolean,
|
||||
keyFile: Uri?) {
|
||||
}
|
||||
keyFile: Uri?) {}
|
||||
|
||||
private fun hideOrShowLockButton(key: NestedSettingsFragment.Screen) {
|
||||
if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||
@@ -220,5 +217,24 @@ open class SettingsActivity
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,11 @@ abstract class DatabaseSavePreferenceDialogFragmentCompat : InputPreferenceDialo
|
||||
this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mProgressDatabaseTaskProvider = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DbSavePrefDialog"
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
|
||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_REMOVE
|
||||
|
||||
class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
||||
|
||||
@@ -53,7 +54,7 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
||||
AttachmentState.COMPLETE,
|
||||
AttachmentState.ERROR -> {
|
||||
// Finish the action when capture by activity
|
||||
consummeAttachmentAction(entryAttachmentState)
|
||||
consumeAttachmentAction(entryAttachmentState)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -99,7 +100,7 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun consummeAttachmentAction(attachment: EntryAttachmentState) {
|
||||
fun consumeAttachmentAction(attachment: EntryAttachmentState) {
|
||||
mBinder?.getService()?.removeAttachmentAction(attachment)
|
||||
}
|
||||
|
||||
@@ -126,4 +127,10 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
||||
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
|
||||
}, ACTION_ATTACHMENT_FILE_START_DOWNLOAD)
|
||||
}
|
||||
|
||||
fun removeBinaryAttachment(attachment: Attachment) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
|
||||
}, ACTION_ATTACHMENT_REMOVE)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -27,15 +27,16 @@ import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||
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.Entry
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.search.UuidUtil
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
@@ -133,6 +134,17 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
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?,
|
||||
onClickListener: OnClickListener?) {
|
||||
userNameFieldView.apply {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class EntryField @JvmOverloads constructor(context: Context,
|
||||
private val labelView: TextView
|
||||
private val valueView: TextView
|
||||
private val showButtonView: ImageView
|
||||
private val copyButtonView: ImageView
|
||||
val copyButtonView: ImageView
|
||||
private var isProtected = false
|
||||
|
||||
var hiddenProtectedValue: Boolean
|
||||
|
||||
@@ -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>
|
||||
@@ -54,7 +54,7 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<com.kunzisoft.keepass.view.EntryEditContentsView
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/entry_edit_contents"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -209,14 +209,12 @@
|
||||
android:layout_marginBottom="@dimen/card_view_margin_bottom"
|
||||
app:layout_constraintTop_toBottomOf="@+id/entry_edit_container"
|
||||
app:layout_constraintBottom_toTopOf="@+id/entry_attachments_container">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<LinearLayout
|
||||
android:id="@+id/extra_fields_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:layout_margin="@dimen/card_view_padding"
|
||||
android:orientation="vertical">
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
android:orientation="vertical" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
@@ -28,6 +28,29 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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
|
||||
android:id="@+id/card_view_master_password"
|
||||
android:layout_margin="4dp"
|
||||
|
||||
@@ -27,10 +27,21 @@
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
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
|
||||
android:id="@+id/item_attachment_title"
|
||||
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"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -105,6 +116,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_gravity="center"
|
||||
style="@style/KeepassDXStyle.ProgressBar.Circle"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
48
app/src/main/res/layout/pref_dialog_text.xml
Normal file
48
app/src/main/res/layout/pref_dialog_text.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -93,8 +93,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_view_padding"
|
||||
android:orientation="vertical">
|
||||
</LinearLayout>
|
||||
android:orientation="vertical" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
|
||||
@@ -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="clipboard">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 l’affichage d’une entrée</string>
|
||||
<string name="clipboard_notifications_summary">Affiche les notifications du presse-papier pour copier les champs lors de l’affichage d’une entrée</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_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_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_version">Votre version d’Android %1$s est inférieure à la version minimale %2$s requise.</string>
|
||||
<string name="unavailable_feature_version">L’appareil 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="file_name">Nom de fichier</string>
|
||||
<string name="path">Chemin d’accès</string>
|
||||
@@ -232,7 +232,7 @@
|
||||
\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_summary">Saisir le titre, le nom d’utilisateur 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_entry_edit_title">Modifier l’entré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 l’entrée.</string>
|
||||
@@ -307,7 +307,7 @@
|
||||
<string name="menu_paste">Coller</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_summary">Active le bouton « Ouvrir » si aucun identifiant n’est sélectionné</string>
|
||||
<string name="allow_no_password_summary">Autorise l’appui du bouton « Ouvrir » si aucun identifiant n’est sélectionné</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="enable_read_only_title">Protégé en écriture</string>
|
||||
@@ -454,24 +454,24 @@
|
||||
<string name="download_initialization">Initialisation…</string>
|
||||
<string name="download_progression">En cours : %1$d%%</string>
|
||||
<string name="download_finalization">Finalisation…</string>
|
||||
<string name="download_complete">Terminé !</string>
|
||||
<string name="download_complete">Terminé !</string>
|
||||
<string name="hide_expired_entries_title">Masquer les entrées expirées</string>
|
||||
<string name="hide_expired_entries_summary">Les entrées expirées sont cachées</string>
|
||||
<string name="hide_expired_entries_summary">Les entrées expirées ne sont pas affichées</string>
|
||||
<string name="contact">Contact</string>
|
||||
<string name="contribution">Contribution</string>
|
||||
<string name="html_about_contribution">Afin de <strong>garder notre liberté</strong>, <strong>corriger les bugs</strong>, <strong>ajouter des fonctionnalités</strong> et <strong>être toujours actif</strong>, nous comptons sur votre <strong>contribution</strong>.</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="remember_database_locations_title">Enregistrer l\'emplacement 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_keyfile_locations_title">Enregistrer l\'emplacement des fichiers clés</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_database_locations_title">Mémoriser l’emplacement des bases de données</string>
|
||||
<string name="remember_database_locations_summary">Garde en mémoire l’emplacement où les bases de données sont stockées</string>
|
||||
<string name="remember_keyfile_locations_title">Mémoriser les emplacements des fichiers clé</string>
|
||||
<string name="remember_keyfile_locations_summary">Garde en mémoire l’emplacement 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_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_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="education_setup_OTP_summary">Définir le mot de passe à usage unique (temporel ou évènementiel) pour générer un jeton demandé par l’authentification à 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 l’authentification à deux facteurs (A2F).</string>
|
||||
<string name="education_setup_OTP_title">Définir l’OTP</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>
|
||||
@@ -498,4 +498,12 @@
|
||||
<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="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 d’importantes 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>
|
||||
@@ -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_title">Ekran za unos podataka za prijavu u bazu podataka</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>
|
||||
@@ -176,4 +176,11 @@
|
||||
<string name="feedback">Umpan Balik</string>
|
||||
<string name="contribution">Kontribusi</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>
|
||||
@@ -39,17 +39,17 @@
|
||||
<string name="decrypting_db">データベースの内容を復号しています…</string>
|
||||
<string name="default_checkbox">デフォルトのデータベースとして使用</string>
|
||||
<string name="digits">数字</string>
|
||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft は<strong>オープンソース</strong>/<strong>広告なし</strong>です。
|
||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft は<strong>オープンソース</strong>かつ<strong>広告なし</strong>です。
|
||||
\nそのままの状態で、<strong>GPLv3</strong> ライセンスの下、いかなる保証もなく提供されます。</string>
|
||||
<string name="select_database_file">既存のデータベースを開く</string>
|
||||
<string name="entry_accessed">アクセス日時</string>
|
||||
<string name="entry_accessed">最終アクセス</string>
|
||||
<string name="entry_cancel">キャンセル</string>
|
||||
<string name="entry_notes">備考</string>
|
||||
<string name="entry_confpassword">パスワードを確認</string>
|
||||
<string name="entry_created">作成日時</string>
|
||||
<string name="entry_created">作成日</string>
|
||||
<string name="entry_expires">有効期限</string>
|
||||
<string name="entry_keyfile">キーファイル</string>
|
||||
<string name="entry_modified">変更日時</string>
|
||||
<string name="entry_modified">変更日</string>
|
||||
<string name="entry_password">パスワード</string>
|
||||
<string name="entry_save">保存</string>
|
||||
<string name="entry_title">タイトル</string>
|
||||
@@ -65,8 +65,8 @@
|
||||
<string name="error_out_of_memory">データベース全体を読み込むメモリがありません。</string>
|
||||
<string name="error_pass_gen_type">少なくとも 1 つのパスワード生成タイプを選択する必要があります。</string>
|
||||
<string name="error_pass_match">パスワードが一致しません。</string>
|
||||
<string name="error_rounds_too_large">「変換ラウンド」が多すぎます。2147483648 に設定します。</string>
|
||||
<string name="error_wrong_length">「長さ」フィールドには正の整数を入力してください。</string>
|
||||
<string name="error_rounds_too_large">[変換ラウンド] が多すぎます。2147483648 に設定します。</string>
|
||||
<string name="error_wrong_length">[長さ] フィールドには正の整数を入力してください。</string>
|
||||
<string name="file_browser">ファイル マネージャー</string>
|
||||
<string name="generate_password">パスワードを生成</string>
|
||||
<string name="hint_conf_pass">パスワードを確認</string>
|
||||
@@ -103,7 +103,7 @@
|
||||
<string name="no_results">検索結果なし</string>
|
||||
<string name="no_url_handler">この URL を開くにはウェブブラウザをインストールしてください。</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_title">実行しています…</string>
|
||||
<string name="content_description_remove_from_list">削除</string>
|
||||
@@ -151,7 +151,7 @@
|
||||
<string name="clipboard_error_clear">クリップボードを消去できませんでした</string>
|
||||
<string name="entry_not_found">エントリーのデータが見つかりませんでした。</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_autofill_enable_service">自動入力サービスを有効にできませんでした。</string>
|
||||
<string name="error_move_folder_in_itself">グループを自分自身の中に移動することはできません。</string>
|
||||
@@ -177,13 +177,13 @@
|
||||
<string name="read_only_warning">ファイル マネージャーによっては、KeePassDX によるストレージへの書き込みを許可していない場合があります。</string>
|
||||
<string name="encryption_explanation">すべてのデータで使用するデータベース暗号化アルゴリズムです。</string>
|
||||
<string name="sort_menu">並び替え</string>
|
||||
<string name="sort_ascending">低い順 ↓</string>
|
||||
<string name="sort_recycle_bin_bottom">ごみ箱を一番下にする</string>
|
||||
<string name="sort_ascending">昇順 ↓</string>
|
||||
<string name="sort_recycle_bin_bottom">ゴミ箱を一番下にする</string>
|
||||
<string name="sort_title">タイトル</string>
|
||||
<string name="sort_username">ユーザー名</string>
|
||||
<string name="sort_creation_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="content_description_open_file">ファイルを開く</string>
|
||||
<string name="content_description_add_entry">エントリーを追加</string>
|
||||
@@ -196,7 +196,7 @@
|
||||
<string name="content_description_remove_field">フィールドを削除</string>
|
||||
<string name="error_move_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="entry_history">履歴</string>
|
||||
<string name="otp_type">OTP の種類</string>
|
||||
@@ -211,7 +211,7 @@
|
||||
<string name="menu_master_key_settings">マスターキーの設定</string>
|
||||
<string name="error_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="warning_permanently_delete_nodes">選択したノードを完全に削除しますか?</string>
|
||||
<string name="auto_focus_search_summary">データベースを開いたとき、検索を促します</string>
|
||||
@@ -255,20 +255,20 @@
|
||||
<string name="parallelism">並列処理</string>
|
||||
<string name="memory_usage_explanation">鍵導出関数が使用するメモリの量(バイト単位)です。</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_title">データベースへの壊れたリンクを非表示にする</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_database_locations_summary">データベースの保存先を追跡します</string>
|
||||
<string name="remember_database_locations_summary">データベースの保存先を保持します</string>
|
||||
<string name="remember_database_locations_title">データベースの場所を記憶</string>
|
||||
<string name="selection_mode">選択モード</string>
|
||||
<string name="contains_duplicate_uuid_procedure">重複したエントリーに対する新しい UUID を生成して、問題を解決し続行しますか?</string>
|
||||
<string name="contains_duplicate_uuid">データベースには重複する UUID が含まれています。</string>
|
||||
<string name="subdomain_search_title">サブドメイン検索</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_otp_counter">カウンターは %1$d から %2$d の間でなければなりません。</string>
|
||||
<string name="otp_counter">カウンター</string>
|
||||
@@ -294,7 +294,7 @@
|
||||
<string name="biometric_auto_open_prompt_title">生体認証プロンプトを自動で開く</string>
|
||||
<string name="biometric_delete_all_key_warning">生体認証に関するすべての暗号鍵を削除しますか?</string>
|
||||
<string name="biometric_delete_all_key_summary">生体認証に関するすべての暗号鍵を削除します</string>
|
||||
<string name="advanced_unlock_explanation_summary">高度なロック解除を使用すると、データベースがより簡単に開きます</string>
|
||||
<string name="advanced_unlock_explanation_summary">高度なロック解除を使用して、データベースをより簡単に開きます</string>
|
||||
<string name="education_lock_summary">データベースをすばやくロックします。時間が経ったり画面がオフになったときロックするよう、アプリを設定することもできます。</string>
|
||||
<string name="education_lock_title">データベースをロック</string>
|
||||
<string name="education_sort_summary">エントリーとグループの並べ替え方法を選択します。</string>
|
||||
@@ -307,7 +307,7 @@
|
||||
<string name="education_sort_title">項目の並び替え</string>
|
||||
<string name="education_new_node_title">データベースに項目を追加</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="list_password_generator_options_title">パスワードの文字種</string>
|
||||
<string name="build_label">Build %1$s</string>
|
||||
@@ -318,7 +318,7 @@
|
||||
<string name="enable_read_only_title">書き込み禁止</string>
|
||||
<string name="delete_entered_password_summary">入力されたパスワードをデータベースへの接続試行後に削除します</string>
|
||||
<string name="delete_entered_password_title">パスワードを削除</string>
|
||||
<string name="allow_no_password_summary">認証情報が選択されていない場合でも、「開く」ボタンのタップを許可します</string>
|
||||
<string name="allow_no_password_summary">認証情報が選択されていない場合でも、[開く] ボタンのタップを許可します</string>
|
||||
<string name="autofill_block_restart">ブロッキングを有効にするには、そのフォームを含むアプリを再起動します。</string>
|
||||
<string name="autofill_block">自動入力をブロック</string>
|
||||
<string name="autofill_web_domain_blocklist_title">ウェブドメインのブロックリスト</string>
|
||||
@@ -336,7 +336,7 @@
|
||||
<string name="settings_database_recommend_changing_master_key_summary">マスターキーの変更を推奨します(日数)</string>
|
||||
<string name="settings_database_recommend_changing_master_key_title">更新を推奨</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_title">画面ロック</string>
|
||||
<string name="lock">ロック</string>
|
||||
@@ -350,14 +350,14 @@
|
||||
<string name="autofill_preference_title">自動入力の設定</string>
|
||||
<string name="set_autofill_service_title">デフォルトの自動入力サービスに設定</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">自動入力</string>
|
||||
<string name="general">全般</string>
|
||||
<string name="biometric">生体認証</string>
|
||||
<string name="menu_appearance_settings">外観</string>
|
||||
<string name="menu_appearance_settings">デザイン</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="biometric_scanning_error">生体認証エラー:%1$s</string>
|
||||
<string name="biometric_not_recognized">生体情報を認識できませんでした</string>
|
||||
@@ -370,7 +370,7 @@
|
||||
<string name="keyboard_notification_entry_clear_close_title">データベースを閉じて消去</string>
|
||||
<string name="clear_clipboard_notification_title">データベースを閉じて消去</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">あなたの意見によって、デベロッパーは<strong>新機能</strong>の開発や<strong>バグの修正</strong>を速く進めることができます。</string>
|
||||
<string name="reset_education_screens_summary">すべての教育的な情報をもう一度表示します</string>
|
||||
<string name="html_text_dev_feature">この機能は<strong>開発中</strong>であり、早期に提供するにはあなたの<strong>貢献</strong>が必要です。</string>
|
||||
@@ -384,16 +384,16 @@
|
||||
<string name="keyboard_previous_fill_in_title">自動キーアクション</string>
|
||||
<string name="keyboard_key_sound_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_notification_entry_content_title">%1$s が Magikeyboard で利用可能</string>
|
||||
<string name="keyboard_entry_timeout_summary">キーボードのエントリーを消去するまでの時間</string>
|
||||
<string name="keyboard_search_share_title">共有から送られた情報の検索</string>
|
||||
<string name="keyboard_search_share_summary">共有から送られた情報を自動的に検索して、結果をキーボードに格納します</string>
|
||||
<string name="keyboard_search_share_title">共有情報の検索</string>
|
||||
<string name="keyboard_search_share_summary">共有情報を自動的に検索して、結果をキーボードに格納します</string>
|
||||
<string name="keyboard_notification_entry_summary">エントリーが利用可能なとき、通知を表示します</string>
|
||||
<string name="device_keyboard_setting_title">デバイス キーボードの設定</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">次回マスターキーの変更を必須にします(1 回のみ)</string>
|
||||
<string name="recycle_bin_summary">グループとエントリーを削除する前に「ごみ箱」グループに移動します</string>
|
||||
<string name="recycle_bin_summary">グループとエントリーを削除する前に [ゴミ箱] グループに移動します</string>
|
||||
<string name="keyboard_selection_entry_summary">エントリーを開いているとき、Magikeyboard に入力フィールドを表示します</string>
|
||||
<string name="lock_database_show_button_title">ロックボタンを表示</string>
|
||||
<string name="download_finalization">終了しています…</string>
|
||||
@@ -401,7 +401,7 @@
|
||||
<string name="education_field_copy_summary">コピーしたフィールドはどこにでも貼り付けることができます。
|
||||
\n
|
||||
\nお好みのフォーム入力方法を使用してください。</string>
|
||||
<string name="warning_database_link_revoked">ファイル マネージャーによって取り消されたファイルにアクセスします</string>
|
||||
<string name="warning_database_link_revoked">ファイルへのアクセス権がファイル マネージャーによって取り消されました</string>
|
||||
<string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string>
|
||||
<string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string>
|
||||
<string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string>
|
||||
@@ -416,7 +416,7 @@
|
||||
<string name="html_text_dev_feature_contibute"><strong>貢献</strong>による、</string>
|
||||
<string name="html_text_donation">自由を維持し活発に開発し続けるために、私たちはあなたの<strong>貢献</strong>に期待しています。</string>
|
||||
<string name="html_text_ad_free">多くのパスワード管理アプリとは異なり、このアプリは<strong>広告なし</strong>かつ<strong>コピーレフトの自由ソフトウェア</strong>です。どのバージョンを使っても、サーバー上で個人情報が収集されることはありません。</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="autofill_auto_search_title">自動検索</string>
|
||||
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
||||
@@ -440,7 +440,7 @@
|
||||
<string name="education_read_only_title">データベースの書き込みを禁止</string>
|
||||
<string name="education_entry_new_field_summary">追加フィールドを登録し、値を追加し、必要に応じて保護します。</string>
|
||||
<string name="education_entry_new_field_title">カスタム フィールドを追加</string>
|
||||
<string name="education_entry_edit_summary">カスタム フィールドを使ってエントリーを編集します。共有データは異なるエントリーのフィールド間で参照することができます。</string>
|
||||
<string name="education_entry_edit_summary">エントリーをカスタム フィールドとともに編集します。共有データは異なるエントリーのフィールド間で参照することができます。</string>
|
||||
<string name="education_entry_edit_title">エントリーを編集</string>
|
||||
<string name="education_biometric_title">生体認証によるロック解除</string>
|
||||
<string name="education_create_database_summary">最初のパスワード管理ファイルを作成します。</string>
|
||||
@@ -448,8 +448,8 @@
|
||||
<string name="enable_education_screens_summary">要素をハイライトしてアプリの動作を学びます</string>
|
||||
<string name="education_read_only_summary">セッションのロック解除モードを変更します。
|
||||
\n
|
||||
\n「書き込み禁止」では、データベースに対する意図しない変更を防ぐことができます。
|
||||
\n「変更可能」では、すべての要素を追加、削除、変更できます。</string>
|
||||
\n[書き込み禁止] では、データベースに対する意図しない変更を防ぐことができます。
|
||||
\n[変更可能] では、すべての要素を追加、削除、変更できます。</string>
|
||||
<string name="allow_copy_password_title">クリップボードの信頼</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
@@ -459,7 +459,7 @@
|
||||
<string name="keyboard_change">キーボードの切り替え</string>
|
||||
<string name="keyboard_keys_category">キー</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_title_text">エントリー</string>
|
||||
<string name="keyboard_entry_timeout_title">タイムアウト</string>
|
||||
@@ -486,7 +486,15 @@
|
||||
<string name="database_default_username_title">デフォルトのユーザー名</string>
|
||||
<string name="style_choose_title">アプリのテーマ</string>
|
||||
<string name="reset_education_screens_title">教育的なヒントをリセット</string>
|
||||
<string name="recycle_bin">ごみ箱</string>
|
||||
<string name="recycle_bin_group_title">ごみ箱グループ</string>
|
||||
<string name="recycle_bin_title">ごみ箱の使用</string>
|
||||
<string name="recycle_bin">ゴミ箱</string>
|
||||
<string name="recycle_bin_group_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>
|
||||
30
app/src/main/res/values-jv/strings.xml
Normal file
30
app/src/main/res/values-jv/strings.xml
Normal 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>
|
||||
@@ -296,4 +296,78 @@
|
||||
<string name="error_string_type">ഈ വാചകം അഭ്യർത്ഥിച്ച ഇനവുമായി പൊരുത്തപ്പെടുന്നില്ല.</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="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">ഞങ്ങളുടെ സ്വാതന്ത്ര്യം നിലനിർത്തുന്നതിനും എല്ലായ്പ്പോഴും സജീവമായിരിക്കുന്നതിനും, നിങ്ങളുടെ<strong>സംഭാവനയെ ഞങ്ങൾ ആശ്രയിക്കുന്നു</strong>.</string>
|
||||
<string name="html_text_dev_feature">ഈ സവിശേഷത<strong>വികസിച്ചുകൊണ്ടിരിക്കുന്നു</strong>കൂടാതെ നിങ്ങളുടെ<strong>സംഭാവന</strong>ഉടൻ ലഭ്യമാകേണ്ടതുണ്ട്.</string>
|
||||
<string name="html_text_dev_feature_buy_pro"><strong>പ്രോ</strong>വേർഷൻ വാങ്ങുന്നതിലൂടെ,</string>
|
||||
<string name="html_text_dev_feature_contibute"><strong>സംഭാവന ചെയ്യുന്നതിലൂടെ</strong>,</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>
|
||||
@@ -60,7 +60,7 @@
|
||||
<string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string>
|
||||
<string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string>
|
||||
<string name="error_file_not_create">Bestand is niet aangemaakt</string>
|
||||
<string name="error_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_no_name">Voer een naam in.</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_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="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="menu_appearance_settings">Uiterlijk</string>
|
||||
<string name="general">Algemeen</string>
|
||||
@@ -220,7 +220,7 @@
|
||||
<string name="list_password_generator_options_summary">Toegestane wachtwoordtekens instellen</string>
|
||||
<string name="clipboard">Klembord</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="lock">Vergrendelen</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_warning">Alle coderingssleutels voor biometrische herkenning verwijderen\?</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="file_name">Bestandsnaam</string>
|
||||
<string name="path">Pad</string>
|
||||
@@ -254,8 +254,8 @@
|
||||
<string name="keyboard">Toetsenbord</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="allow_no_password_title">Geen hoofdwachtwoord</string>
|
||||
<string name="allow_no_password_summary">Schakel de knop \"Openen\" in als er geen referenties zijn geselecteerd</string>
|
||||
<string name="allow_no_password_title">Geen hoofdwachtwoord toestaan</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_summary">Open de database standaard alleen-lezen</string>
|
||||
<string name="enable_education_screens_title">Informatieve tips</string>
|
||||
@@ -273,7 +273,7 @@
|
||||
\nGroepen (~mappen) organiseren de items in je database.</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_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_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>
|
||||
@@ -297,7 +297,7 @@
|
||||
<string name="education_sort_summary">Kies hoe items en groepen worden gesorteerd.</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="html_text_ad_free">In tegenstelling tot veel apps voor wachtwoordbeheer, is deze <strong> reclamevrij</strong>, <strong> vrije software </strong> 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 <strong> reclamevrij</strong>, <strong> vrije software </strong> 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 <strong>visuele thema</strong> en draag je bij aan het <strong>realiseren van gemeenschapsprojecten.</strong></string>
|
||||
<string name="html_text_feature_generosity">Dit <strong>visuele thema</strong> is beschikbaar gemaakt dankzij jouw vrijgevigheid.</string>
|
||||
<string name="html_text_donation">Om altijd vrij en actief te blijven, rekenen we op jouw <strong>bijdrage.</strong></string>
|
||||
@@ -426,17 +426,17 @@
|
||||
<string name="compression_gzip">Gzip</string>
|
||||
<string name="device_keyboard_setting_title">Toetsenbordinstellingen</string>
|
||||
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
|
||||
<string name="education_setup_OTP_title">Instellingen OTP</string>
|
||||
<string name="education_setup_OTP_title">OTP instellen</string>
|
||||
<string name="remember_keyfile_locations_title">Locatie van sleutelbestanden opslaan</string>
|
||||
<string name="remember_database_locations_title">Databaselocatie opslaan</string>
|
||||
<string name="hide_expired_entries_summary">Verlopen items worden verborgen</string>
|
||||
<string name="remember_database_locations_title">Databaselocaties onthouden</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="download_complete">Tik om het bestand te openen</string>
|
||||
<string name="download_complete">Klaar!</string>
|
||||
<string name="download_finalization">Voltooien…</string>
|
||||
<string name="download_progression">Voortgang: %1$d%%</string>
|
||||
<string name="download_initialization">Initialiseren…</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="autofill_auto_search_summary">Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID</string>
|
||||
<string name="autofill_auto_search_title">Automatisch zoeken</string>
|
||||
@@ -452,8 +452,8 @@
|
||||
<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_title">Verberg corrupte databasekoppelingen</string>
|
||||
<string name="remember_keyfile_locations_summary">Onthoud de locatie van databasesleutelbestanden</string>
|
||||
<string name="remember_database_locations_summary">Onthoud de locatie van databases</string>
|
||||
<string name="remember_keyfile_locations_summary">Onthoudt waar de databasesleutelbestanden zijn opgeslagen</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_title">Snel zoeken</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_title">Scherm Databasereferenties</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>
|
||||
@@ -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_title">Ekran poświadczeń bazy danych</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>
|
||||
@@ -425,7 +425,7 @@
|
||||
<string name="menu_save_database">Сохранить базу</string>
|
||||
<string name="menu_empty_recycle_bin">Очистить \"корзину\"</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="credential_before_click_biometric_button">Введите пароль, затем нажмите кнопку биометрии.</string>
|
||||
<string name="recycle_bin_group_title">Группа \"корзины\"</string>
|
||||
@@ -489,4 +489,12 @@
|
||||
<string name="keyboard_previous_fill_in_title">Автоматическое действие кнопки</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Автоматически переключаться на предыдущую клавиатуру на экране входа в базу</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>
|
||||
16
app/src/main/res/values-ta/strings.xml
Normal file
16
app/src/main/res/values-ta/strings.xml
Normal 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>
|
||||
@@ -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_title">Veri tabanı kimlik bilgileri ekranı</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>
|
||||
@@ -489,4 +489,12 @@
|
||||
<string name="keyboard_previous_fill_in_title">Самочинне введення</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Автоматичне перемикання до попередньої клавіатури, на екрані входу до бази даних</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>
|
||||
@@ -489,4 +489,12 @@
|
||||
<string name="keyboard_previous_fill_in_title">自动键动作</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">如果显示数据库凭据屏幕,则自动返回到上一个键盘</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>
|
||||
@@ -34,6 +34,7 @@
|
||||
<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="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="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>
|
||||
@@ -178,8 +179,9 @@
|
||||
<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_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_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="recycle_bin_enable_key" translatable="false">recycle_bin_enable_key</string>
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<string name="content_description_add_group">Add group</string>
|
||||
<string name="content_description_add_item">Add item</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_keyfile_checkbox">Keyfile checkbox</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_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_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="build_label">Build %1$s</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="path">Path</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_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_summary">Moves groups and entries to \"Recycle bin\" group before deleting</string>
|
||||
<string name="recycle_bin_group_title">Recycle bin group</string>
|
||||
|
||||
@@ -59,14 +59,19 @@
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="@string/database_category_compression_key"
|
||||
android:title="@string/compression">
|
||||
android:key="@string/database_category_data_key"
|
||||
android:title="@string/data">
|
||||
|
||||
<com.kunzisoft.keepass.settings.preference.DialogListExplanationPreference
|
||||
android:key="@string/database_data_compression_key"
|
||||
android:persistent="false"
|
||||
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
|
||||
|
||||
96
art/ic_attachment_broken.svg
Normal file
96
art/ic_attachment_broken.svg
Normal 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 |
6
fastlane/metadata/android/en-US/changelogs/40.txt
Normal file
6
fastlane/metadata/android/en-US/changelogs/40.txt
Normal 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
|
||||
6
fastlane/metadata/android/fr-FR/changelogs/40.txt
Normal file
6
fastlane/metadata/android/fr-FR/changelogs/40.txt
Normal 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
|
||||
Reference in New Issue
Block a user