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)
|
KeePassDX(2.8.3)
|
||||||
* Upload attachments
|
* Upload attachments
|
||||||
* Visibility button for each hidden field
|
* Visibility button for each hidden field
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "com.kunzisoft.keepass"
|
applicationId "com.kunzisoft.keepass"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode = 39
|
versionCode = 40
|
||||||
versionName = "2.8.3"
|
versionName = "2.8.4"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ import com.google.android.material.appbar.CollapsingToolbarLayout
|
|||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.education.EntryActivityEducation
|
import com.kunzisoft.keepass.education.EntryActivityEducation
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||||
import com.kunzisoft.keepass.database.element.Attachment
|
|
||||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
import com.kunzisoft.keepass.model.StreamDirection
|
import com.kunzisoft.keepass.model.StreamDirection
|
||||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||||
@@ -309,9 +309,11 @@ class EntryActivity : LockingActivity() {
|
|||||||
entryContentsView?.assignNotes(entry.notes)
|
entryContentsView?.assignNotes(entry.notes)
|
||||||
|
|
||||||
// Assign custom fields
|
// Assign custom fields
|
||||||
if (entry.allowCustomFields()) {
|
if (mDatabase?.allowEntryCustomFields() == true) {
|
||||||
entryContentsView?.clearExtraFields()
|
entryContentsView?.clearExtraFields()
|
||||||
for ((label, value) in entry.customFields) {
|
entry.getExtraFields().forEach { field ->
|
||||||
|
val label = field.name
|
||||||
|
val value = field.protectedValue
|
||||||
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
|
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
|
||||||
if (allowCopyProtectedField) {
|
if (allowCopyProtectedField) {
|
||||||
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
|
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {
|
||||||
@@ -427,7 +429,7 @@ class EntryActivity : LockingActivity() {
|
|||||||
|
|
||||||
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
|
||||||
menu: Menu) {
|
menu: Menu) {
|
||||||
val entryFieldCopyView = findViewById<View>(R.id.entry_field_copy)
|
val entryFieldCopyView = entryContentsView?.firstEntryFieldCopyView()
|
||||||
val entryCopyEducationPerformed = entryFieldCopyView != null
|
val entryCopyEducationPerformed = entryFieldCopyView != null
|
||||||
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
|
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
|
||||||
entryFieldCopyView,
|
entryFieldCopyView,
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ import android.widget.TimePicker
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.NestedScrollView
|
import androidx.core.widget.NestedScrollView
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.*
|
import com.kunzisoft.keepass.activities.dialogs.*
|
||||||
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
|
import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Companion.MAX_WARNING_BINARY_FILE
|
||||||
@@ -44,12 +46,13 @@ import com.kunzisoft.keepass.activities.lock.LockingActivity
|
|||||||
import com.kunzisoft.keepass.database.element.*
|
import com.kunzisoft.keepass.database.element.*
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||||
|
import com.kunzisoft.keepass.database.element.node.Node
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
|
||||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||||
import com.kunzisoft.keepass.model.*
|
import com.kunzisoft.keepass.model.*
|
||||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||||
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||||
@@ -60,11 +63,12 @@ import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
|||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import com.kunzisoft.keepass.view.EntryEditContentsView
|
import com.kunzisoft.keepass.view.asError
|
||||||
import com.kunzisoft.keepass.view.showActionError
|
import com.kunzisoft.keepass.view.showActionError
|
||||||
import com.kunzisoft.keepass.view.updateLockPaddingLeft
|
import com.kunzisoft.keepass.view.updateLockPaddingLeft
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class EntryEditActivity : LockingActivity(),
|
class EntryEditActivity : LockingActivity(),
|
||||||
IconPickerDialogFragment.IconPickerListener,
|
IconPickerDialogFragment.IconPickerListener,
|
||||||
@@ -81,24 +85,21 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
// Refs of an entry and group in database, are not modifiable
|
// Refs of an entry and group in database, are not modifiable
|
||||||
private var mEntry: Entry? = null
|
private var mEntry: Entry? = null
|
||||||
private var mParent: Group? = null
|
private var mParent: Group? = null
|
||||||
// New or copy of mEntry in the database to be modifiable
|
|
||||||
private var mNewEntry: Entry? = null
|
|
||||||
private var mIsNew: Boolean = false
|
private var mIsNew: Boolean = false
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
private var coordinatorLayout: CoordinatorLayout? = null
|
private var coordinatorLayout: CoordinatorLayout? = null
|
||||||
private var scrollView: NestedScrollView? = null
|
private var scrollView: NestedScrollView? = null
|
||||||
private var entryEditContentsView: EntryEditContentsView? = null
|
private var entryEditFragment: EntryEditFragment? = null
|
||||||
private var entryEditAddToolBar: Toolbar? = null
|
private var entryEditAddToolBar: Toolbar? = null
|
||||||
private var validateButton: View? = null
|
private var validateButton: View? = null
|
||||||
private var lockView: View? = null
|
private var lockView: View? = null
|
||||||
|
|
||||||
private var mFocusedEditExtraField: FocusedEditField? = null
|
|
||||||
|
|
||||||
// To manage attachments
|
// To manage attachments
|
||||||
private var mSelectFileHelper: SelectFileHelper? = null
|
private var mSelectFileHelper: SelectFileHelper? = null
|
||||||
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||||
private var mAllowMultipleAttachments: Boolean = false
|
private var mAllowMultipleAttachments: Boolean = false
|
||||||
|
private var mTempAttachments = ArrayList<Attachment>()
|
||||||
|
|
||||||
// Education
|
// Education
|
||||||
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
||||||
@@ -118,22 +119,6 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
scrollView = findViewById(R.id.entry_edit_scroll)
|
scrollView = findViewById(R.id.entry_edit_scroll)
|
||||||
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||||
|
|
||||||
entryEditContentsView = findViewById(R.id.entry_edit_contents)
|
|
||||||
entryEditContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
|
||||||
entryEditContentsView?.onDateClickListener = View.OnClickListener {
|
|
||||||
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
|
|
||||||
val dateTime = DateTime(expiresDate)
|
|
||||||
val defaultYear = dateTime.year
|
|
||||||
val defaultMonth = dateTime.monthOfYear-1
|
|
||||||
val defaultDay = dateTime.dayOfMonth
|
|
||||||
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
|
|
||||||
.show(supportFragmentManager, "DatePickerFragment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entryEditContentsView?.entryPasswordGeneratorView?.setOnClickListener {
|
|
||||||
openPasswordGenerator()
|
|
||||||
}
|
|
||||||
|
|
||||||
lockView = findViewById(R.id.lock_button)
|
lockView = findViewById(R.id.lock_button)
|
||||||
lockView?.setOnClickListener {
|
lockView?.setOnClickListener {
|
||||||
lockAndExit()
|
lockAndExit()
|
||||||
@@ -148,6 +133,8 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
// Likely the app has been killed exit the activity
|
// Likely the app has been killed exit the activity
|
||||||
mDatabase = Database.getInstance()
|
mDatabase = Database.getInstance()
|
||||||
|
|
||||||
|
var tempEntryInfo: EntryInfo? = null
|
||||||
|
|
||||||
// Entry is retrieve, it's an entry to update
|
// Entry is retrieve, it's an entry to update
|
||||||
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
|
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
|
||||||
mIsNew = false
|
mIsNew = false
|
||||||
@@ -163,70 +150,77 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
entry.parent = mParent
|
entry.parent = mParent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tempEntryInfo = mEntry?.getEntryInfo(mDatabase, true)
|
||||||
// Create the new entry from the current one
|
|
||||||
if (savedInstanceState?.containsKey(KEY_NEW_ENTRY) != true) {
|
|
||||||
mEntry?.let { entry ->
|
|
||||||
// Create a copy to modify
|
|
||||||
mNewEntry = Entry(entry).also { newEntry ->
|
|
||||||
// WARNING Remove the parent to keep memory with parcelable
|
|
||||||
newEntry.removeParent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent is retrieve, it's a new entry to create
|
// Parent is retrieve, it's a new entry to create
|
||||||
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
|
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
|
||||||
mIsNew = true
|
mIsNew = true
|
||||||
// Create an empty new entry
|
|
||||||
if (savedInstanceState?.containsKey(KEY_NEW_ENTRY) != true) {
|
|
||||||
mNewEntry = mDatabase?.createEntry()
|
|
||||||
}
|
|
||||||
mParent = mDatabase?.getGroupById(it)
|
mParent = mDatabase?.getGroupById(it)
|
||||||
// Add the default icon from parent if not a folder
|
// Add the default icon from parent if not a folder
|
||||||
val parentIcon = mParent?.icon
|
val parentIcon = mParent?.icon
|
||||||
|
tempEntryInfo = mDatabase?.createEntry()?.getEntryInfo(mDatabase, true)
|
||||||
|
// Set default icon
|
||||||
if (parentIcon != null
|
if (parentIcon != null
|
||||||
&& parentIcon.iconId != IconImage.UNKNOWN_ID
|
&& parentIcon.iconId != IconImage.UNKNOWN_ID
|
||||||
&& parentIcon.iconId != IconImageStandard.FOLDER) {
|
&& parentIcon.iconId != IconImageStandard.FOLDER) {
|
||||||
temporarilySaveAndShowSelectedIcon(parentIcon)
|
tempEntryInfo?.icon = parentIcon
|
||||||
} else {
|
|
||||||
mDatabase?.drawFactory?.let { iconFactory ->
|
|
||||||
entryEditContentsView?.setDefaultIcon(iconFactory)
|
|
||||||
}
|
}
|
||||||
|
// Set default username
|
||||||
|
tempEntryInfo?.username = mDatabase?.defaultUsername ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build fragment to manage entry modification
|
||||||
|
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
|
||||||
|
if (entryEditFragment == null) {
|
||||||
|
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo)
|
||||||
|
}
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG)
|
||||||
|
.commit()
|
||||||
|
entryEditFragment?.apply {
|
||||||
|
drawFactory = mDatabase?.drawFactory
|
||||||
|
setOnDateClickListener = View.OnClickListener {
|
||||||
|
expiryTime.date.let { expiresDate ->
|
||||||
|
val dateTime = DateTime(expiresDate)
|
||||||
|
val defaultYear = dateTime.year
|
||||||
|
val defaultMonth = dateTime.monthOfYear-1
|
||||||
|
val defaultDay = dateTime.dayOfMonth
|
||||||
|
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
|
||||||
|
.show(supportFragmentManager, "DatePickerFragment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnPasswordGeneratorClickListener = View.OnClickListener {
|
||||||
|
openPasswordGenerator()
|
||||||
|
}
|
||||||
|
// Add listener to the icon
|
||||||
|
setOnIconViewClickListener = View.OnClickListener {
|
||||||
|
IconPickerDialogFragment.launch(this@EntryEditActivity)
|
||||||
|
}
|
||||||
|
setOnRemoveAttachment = { attachment ->
|
||||||
|
mAttachmentFileBinderManager?.removeBinaryAttachment(attachment)
|
||||||
|
removeAttachment(EntryAttachmentState(attachment, StreamDirection.DOWNLOAD))
|
||||||
|
}
|
||||||
|
setOnEditCustomField = { field ->
|
||||||
|
editCustomField(field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the new entry after an orientation change
|
// Retrieve temp attachments in case of deletion
|
||||||
if (savedInstanceState?.containsKey(KEY_NEW_ENTRY) == true) {
|
if (savedInstanceState?.containsKey(TEMP_ATTACHMENTS) == true) {
|
||||||
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY)
|
mTempAttachments = savedInstanceState.getParcelableArrayList(TEMP_ATTACHMENTS) ?: mTempAttachments
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedInstanceState?.containsKey(EXTRA_FIELD_FOCUSED_ENTRY) == true) {
|
|
||||||
mFocusedEditExtraField = savedInstanceState.getParcelable(EXTRA_FIELD_FOCUSED_ENTRY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the activity if entry or parent can't be retrieve
|
|
||||||
if (mNewEntry == null || mParent == null) {
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
populateViewsWithEntry(mNewEntry!!)
|
|
||||||
|
|
||||||
// Assign title
|
// Assign title
|
||||||
title = if (mIsNew) getString(R.string.add_entry) else getString(R.string.edit_entry)
|
title = if (mIsNew) getString(R.string.add_entry) else getString(R.string.edit_entry)
|
||||||
|
|
||||||
// Add listener to the icon
|
|
||||||
entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
|
|
||||||
|
|
||||||
// Bottom Bar
|
// Bottom Bar
|
||||||
entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
|
entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
|
||||||
entryEditAddToolBar?.apply {
|
entryEditAddToolBar?.apply {
|
||||||
menuInflater.inflate(R.menu.entry_edit, menu)
|
menuInflater.inflate(R.menu.entry_edit, menu)
|
||||||
|
|
||||||
menu.findItem(R.id.menu_add_field).apply {
|
menu.findItem(R.id.menu_add_field).apply {
|
||||||
val allowCustomField = mNewEntry?.allowCustomFields() == true
|
val allowCustomField = mDatabase?.allowEntryCustomFields() == true
|
||||||
isEnabled = allowCustomField
|
isEnabled = allowCustomField
|
||||||
isVisible = allowCustomField
|
isVisible = allowCustomField
|
||||||
}
|
}
|
||||||
@@ -278,10 +272,24 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
when (actionTask) {
|
when (actionTask) {
|
||||||
ACTION_DATABASE_CREATE_ENTRY_TASK,
|
ACTION_DATABASE_CREATE_ENTRY_TASK,
|
||||||
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
|
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
|
||||||
if (result.isSuccess)
|
try {
|
||||||
|
if (result.isSuccess) {
|
||||||
|
var newNodes: List<Node> = ArrayList()
|
||||||
|
result.data?.getBundle(DatabaseTaskNotificationService.NEW_NODES_KEY)?.let { newNodesBundle ->
|
||||||
|
mDatabase?.let { database ->
|
||||||
|
newNodes = DatabaseTaskNotificationService.getListNodesFromBundle(database, newNodesBundle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newNodes.size == 1) {
|
||||||
|
mEntry = newNodes[0] as Entry?
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to retrieve entry after database action", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
coordinatorLayout?.showActionError(result)
|
coordinatorLayout?.showActionError(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,13 +313,12 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
|
override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
|
||||||
when (entryAttachmentState.downloadState) {
|
when (entryAttachmentState.downloadState) {
|
||||||
AttachmentState.START -> {
|
AttachmentState.START -> {
|
||||||
entryEditContentsView?.apply {
|
entryEditFragment?.apply {
|
||||||
// When only one attachment is allowed
|
// When only one attachment is allowed
|
||||||
if (!mAllowMultipleAttachments) {
|
if (!mAllowMultipleAttachments) {
|
||||||
clearAttachments()
|
clearAttachments()
|
||||||
}
|
}
|
||||||
putAttachment(entryAttachmentState)
|
putAttachment(entryAttachmentState)
|
||||||
requestLayout()
|
|
||||||
// Scroll to the attachment position
|
// Scroll to the attachment position
|
||||||
getAttachmentViewPosition(entryAttachmentState) {
|
getAttachmentViewPosition(entryAttachmentState) {
|
||||||
scrollView?.smoothScrollTo(0, it.toInt())
|
scrollView?.smoothScrollTo(0, it.toInt())
|
||||||
@@ -319,10 +326,10 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AttachmentState.IN_PROGRESS -> {
|
AttachmentState.IN_PROGRESS -> {
|
||||||
entryEditContentsView?.putAttachment(entryAttachmentState)
|
entryEditFragment?.putAttachment(entryAttachmentState)
|
||||||
}
|
}
|
||||||
AttachmentState.COMPLETE -> {
|
AttachmentState.COMPLETE -> {
|
||||||
entryEditContentsView?.apply {
|
entryEditFragment?.apply {
|
||||||
putAttachment(entryAttachmentState)
|
putAttachment(entryAttachmentState)
|
||||||
// Scroll to the attachment position
|
// Scroll to the attachment position
|
||||||
getAttachmentViewPosition(entryAttachmentState) {
|
getAttachmentViewPosition(entryAttachmentState) {
|
||||||
@@ -331,8 +338,10 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AttachmentState.ERROR -> {
|
AttachmentState.ERROR -> {
|
||||||
mDatabase?.removeAttachmentIfNotUsed(entryAttachmentState.attachment)
|
entryEditFragment?.removeAttachment(entryAttachmentState)
|
||||||
entryEditContentsView?.removeAttachment(entryAttachmentState)
|
coordinatorLayout?.let {
|
||||||
|
Snackbar.make(it, R.string.error_file_not_create, Snackbar.LENGTH_LONG).asError().show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@@ -347,79 +356,6 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateViewsWithEntry(newEntry: Entry) {
|
|
||||||
// Don't start the field reference manager, we want to see the raw ref
|
|
||||||
mDatabase?.stopManageEntry(newEntry)
|
|
||||||
|
|
||||||
// Set info in temp parameters
|
|
||||||
temporarilySaveAndShowSelectedIcon(newEntry.icon)
|
|
||||||
|
|
||||||
// Set info in view
|
|
||||||
entryEditContentsView?.apply {
|
|
||||||
title = newEntry.title
|
|
||||||
username = if (mIsNew && newEntry.username.isEmpty())
|
|
||||||
mDatabase?.defaultUsername ?: ""
|
|
||||||
else
|
|
||||||
newEntry.username
|
|
||||||
url = newEntry.url
|
|
||||||
password = newEntry.password
|
|
||||||
expires = newEntry.expires
|
|
||||||
if (expires)
|
|
||||||
expiresDate = newEntry.expiryTime
|
|
||||||
notes = newEntry.notes
|
|
||||||
assignExtraFields(newEntry.customFields.mapTo(ArrayList()) {
|
|
||||||
Field(it.key, it.value)
|
|
||||||
}, {
|
|
||||||
editCustomField(it)
|
|
||||||
}, mFocusedEditExtraField)
|
|
||||||
|
|
||||||
mDatabase?.binaryPool?.let { binaryPool ->
|
|
||||||
assignAttachments(newEntry.getAttachments(binaryPool).toSet(), StreamDirection.UPLOAD) { attachment ->
|
|
||||||
newEntry.removeAttachment(attachment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun populateEntryWithViews(newEntry: Entry) {
|
|
||||||
|
|
||||||
mDatabase?.startManageEntry(newEntry)
|
|
||||||
|
|
||||||
newEntry.apply {
|
|
||||||
// Build info from view
|
|
||||||
entryEditContentsView?.let { entryView ->
|
|
||||||
removeAllFields()
|
|
||||||
title = entryView.title
|
|
||||||
username = entryView.username
|
|
||||||
url = entryView.url
|
|
||||||
password = entryView.password
|
|
||||||
expires = entryView.expires
|
|
||||||
if (entryView.expires) {
|
|
||||||
expiryTime = entryView.expiresDate
|
|
||||||
}
|
|
||||||
notes = entryView.notes
|
|
||||||
entryView.getExtraFields().forEach { customField ->
|
|
||||||
putExtraField(customField.name, customField.protectedValue)
|
|
||||||
}
|
|
||||||
mDatabase?.binaryPool?.let { binaryPool ->
|
|
||||||
entryView.getAttachments().forEach {
|
|
||||||
putAttachment(it, binaryPool)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mFocusedEditExtraField = entryView.getExtraFieldFocused()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mDatabase?.stopManageEntry(newEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
|
|
||||||
mNewEntry?.icon = icon
|
|
||||||
mDatabase?.drawFactory?.let { iconDrawFactory ->
|
|
||||||
entryEditContentsView?.setIcon(iconDrawFactory, icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the password generator fragment
|
* Open the password generator fragment
|
||||||
*/
|
*/
|
||||||
@@ -439,20 +375,17 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewCustomFieldApproved(newField: Field) {
|
override fun onNewCustomFieldApproved(newField: Field) {
|
||||||
entryEditContentsView?.apply {
|
entryEditFragment?.apply {
|
||||||
putExtraField(newField)
|
putExtraField(newField)
|
||||||
getExtraFieldViewPosition(newField) { position ->
|
|
||||||
scrollView?.smoothScrollTo(0, position.toInt())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
|
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
|
||||||
entryEditContentsView?.replaceExtraField(oldField, newField)
|
entryEditFragment?.replaceExtraField(oldField, newField)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeleteCustomFieldApproved(oldField: Field) {
|
override fun onDeleteCustomFieldApproved(oldField: Field) {
|
||||||
entryEditContentsView?.removeExtraField(oldField)
|
entryEditFragment?.removeExtraField(oldField)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -469,8 +402,15 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?) {
|
override fun onValidateReplaceFile(attachmentToUploadUri: Uri?, attachment: Attachment?) {
|
||||||
|
startUploadAttachment(attachmentToUploadUri, attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startUploadAttachment(attachmentToUploadUri: Uri?, attachment: Attachment?) {
|
||||||
if (attachmentToUploadUri != null && attachment != null) {
|
if (attachmentToUploadUri != null && attachment != null) {
|
||||||
|
// Start uploading in service
|
||||||
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, attachment)
|
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, attachment)
|
||||||
|
// Add in temp list
|
||||||
|
mTempAttachments.add(attachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,12 +419,12 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
mDatabase?.buildNewBinary(applicationContext.filesDir, false, compression)?.let { binaryAttachment ->
|
mDatabase?.buildNewBinary(applicationContext.filesDir, false, compression)?.let { binaryAttachment ->
|
||||||
val entryAttachment = Attachment(fileName, binaryAttachment)
|
val entryAttachment = Attachment(fileName, binaryAttachment)
|
||||||
// Ask to replace the current attachment
|
// Ask to replace the current attachment
|
||||||
if ((mDatabase?.allowMultipleAttachments != true && entryEditContentsView?.containsAttachment() == true) ||
|
if ((mDatabase?.allowMultipleAttachments != true && entryEditFragment?.containsAttachment() == true) ||
|
||||||
entryEditContentsView?.containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD)) == true) {
|
entryEditFragment?.containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD)) == true) {
|
||||||
ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
|
ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
|
||||||
.show(supportFragmentManager, "replacementFileFragment")
|
.show(supportFragmentManager, "replacementFileFragment")
|
||||||
} else {
|
} else {
|
||||||
mAttachmentFileBinderManager?.startUploadAttachment(attachmentToUploadUri, entryAttachment)
|
startUploadAttachment(attachmentToUploadUri, entryAttachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,7 +455,7 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
private fun setupOTP() {
|
private fun setupOTP() {
|
||||||
// Retrieve the current otpElement if exists
|
// Retrieve the current otpElement if exists
|
||||||
// and open the dialog to set up the OTP
|
// and open the dialog to set up the OTP
|
||||||
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
|
SetOTPDialogFragment.build(entryEditFragment?.getEntryInfo()?.otpModel)
|
||||||
.show(supportFragmentManager, "addOTPDialog")
|
.show(supportFragmentManager, "addOTPDialog")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,18 +463,30 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
* Saves the new entry or update an existing entry in the database
|
* Saves the new entry or update an existing entry in the database
|
||||||
*/
|
*/
|
||||||
private fun saveEntry() {
|
private fun saveEntry() {
|
||||||
// Launch a validation and show the error if present
|
// Get the temp entry
|
||||||
if (entryEditContentsView?.isValid() == true) {
|
entryEditFragment?.getEntryInfo()?.let { newEntryInfo ->
|
||||||
// Clone the entry
|
|
||||||
mNewEntry?.let { newEntry ->
|
|
||||||
|
|
||||||
// WARNING Add the parent previously deleted
|
if (mIsNew) {
|
||||||
newEntry.parent = mEntry?.parent
|
// Create new one
|
||||||
|
mDatabase?.createEntry()
|
||||||
|
} else {
|
||||||
|
// Create a clone
|
||||||
|
Entry(mEntry!!)
|
||||||
|
}?.let { newEntry ->
|
||||||
|
|
||||||
|
newEntry.setEntryInfo(mDatabase, newEntryInfo)
|
||||||
// Build info
|
// Build info
|
||||||
newEntry.lastAccessTime = DateInstant()
|
newEntry.lastAccessTime = DateInstant()
|
||||||
newEntry.lastModificationTime = DateInstant()
|
newEntry.lastModificationTime = DateInstant()
|
||||||
|
|
||||||
populateEntryWithViews(newEntry)
|
// Delete temp attachment if not used
|
||||||
|
mTempAttachments.forEach {
|
||||||
|
mDatabase?.binaryPool?.let { binaryPool ->
|
||||||
|
if (!newEntry.getAttachments(binaryPool).contains(it)) {
|
||||||
|
mDatabase?.removeAttachmentIfNotUsed(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open a progress dialog and save entry
|
// Open a progress dialog and save entry
|
||||||
if (mIsNew) {
|
if (mIsNew) {
|
||||||
@@ -567,30 +519,23 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||||
|
|
||||||
entryEditActivityEducation?.let {
|
|
||||||
Handler().post { performedNextEducation(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
|
||||||
val passwordGeneratorView: View? = entryEditContentsView?.entryPasswordGeneratorView
|
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
||||||
val generatePasswordEducationPerformed = passwordGeneratorView != null
|
entryEditActivityEducation?.let {
|
||||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
Handler().post { performedNextEducation(it) }
|
||||||
passwordGeneratorView,
|
|
||||||
{
|
|
||||||
openPasswordGenerator()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
performedNextEducation(entryEditActivityEducation)
|
|
||||||
}
|
}
|
||||||
)
|
return super.onPrepareOptionsMenu(menu)
|
||||||
if (!generatePasswordEducationPerformed) {
|
}
|
||||||
|
|
||||||
|
fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||||
|
if (entryEditFragment?.generatePasswordEducationPerformed(entryEditActivityEducation) != true) {
|
||||||
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
|
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
|
||||||
val addNewFieldEducationPerformed = mNewEntry != null
|
val addNewFieldEducationPerformed = mDatabase?.allowEntryCustomFields() == true
|
||||||
&& mNewEntry!!.allowCustomFields() && addNewFieldView != null
|
&& addNewFieldView != null
|
||||||
&& addNewFieldView.visibility == View.VISIBLE
|
&& addNewFieldView.isVisible
|
||||||
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
|
||||||
addNewFieldView,
|
addNewFieldView,
|
||||||
{
|
{
|
||||||
@@ -602,7 +547,8 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
)
|
)
|
||||||
if (!addNewFieldEducationPerformed) {
|
if (!addNewFieldEducationPerformed) {
|
||||||
val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment)
|
val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment)
|
||||||
val addAttachmentEducationPerformed = attachmentView != null && attachmentView.visibility == View.VISIBLE
|
val addAttachmentEducationPerformed = attachmentView != null
|
||||||
|
&& attachmentView.isVisible
|
||||||
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation(
|
&& entryEditActivityEducation.checkAndPerformedAttachmentEducation(
|
||||||
attachmentView,
|
attachmentView,
|
||||||
{
|
{
|
||||||
@@ -614,7 +560,8 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
)
|
)
|
||||||
if (!addAttachmentEducationPerformed) {
|
if (!addAttachmentEducationPerformed) {
|
||||||
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
|
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
|
||||||
setupOtpView != null && setupOtpView.visibility == View.VISIBLE
|
setupOtpView != null
|
||||||
|
&& setupOtpView.isVisible
|
||||||
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
|
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
|
||||||
setupOtpView,
|
setupOtpView,
|
||||||
{
|
{
|
||||||
@@ -644,21 +591,28 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onOtpCreated(otpElement: OtpElement) {
|
override fun onOtpCreated(otpElement: OtpElement) {
|
||||||
// Update the otp field with otpauth:// url
|
var titleOTP: String? = null
|
||||||
val otpField = OtpEntryFields.buildOtpField(otpElement,
|
var usernameOTP: String? = null
|
||||||
mEntry?.title, mEntry?.username)
|
// Build a temp entry to get title and username (by ref)
|
||||||
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
|
entryEditFragment?.getEntryInfo()?.let { entryInfo ->
|
||||||
entryEditContentsView?.apply {
|
val entryTemp = mDatabase?.createEntry()
|
||||||
putExtraField(otpField)
|
entryTemp?.setEntryInfo(mDatabase, entryInfo)
|
||||||
getExtraFieldViewPosition(otpField) { position ->
|
mDatabase?.startManageEntry(entryTemp)
|
||||||
scrollView?.smoothScrollTo(0, position.toInt())
|
titleOTP = entryTemp?.title
|
||||||
|
usernameOTP = entryTemp?.username
|
||||||
|
mDatabase?.stopManageEntry(mEntry)
|
||||||
}
|
}
|
||||||
|
// Update the otp field with otpauth:// url
|
||||||
|
val otpField = OtpEntryFields.buildOtpField(otpElement, titleOTP, usernameOTP)
|
||||||
|
mEntry?.putExtraField(Field(otpField.name, otpField.protectedValue))
|
||||||
|
entryEditFragment?.apply {
|
||||||
|
putExtraField(otpField)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iconPicked(bundle: Bundle) {
|
override fun iconPicked(bundle: Bundle) {
|
||||||
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
|
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
|
||||||
temporarilySaveAndShowSelectedIcon(icon)
|
entryEditFragment?.icon = icon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,9 +620,9 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
// To fix android 4.4 issue
|
// To fix android 4.4 issue
|
||||||
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
|
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
|
||||||
if (datePicker?.isShown == true) {
|
if (datePicker?.isShown == true) {
|
||||||
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
|
entryEditFragment?.expiryTime?.date?.let { expiresDate ->
|
||||||
// Save the date
|
// Save the date
|
||||||
entryEditContentsView?.expiresDate =
|
entryEditFragment?.expiryTime =
|
||||||
DateInstant(DateTime(expiresDate)
|
DateInstant(DateTime(expiresDate)
|
||||||
.withYear(year)
|
.withYear(year)
|
||||||
.withMonthOfYear(month + 1)
|
.withMonthOfYear(month + 1)
|
||||||
@@ -685,9 +639,9 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
|
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
|
||||||
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
|
entryEditFragment?.expiryTime?.date?.let { expiresDate ->
|
||||||
// Save the date
|
// Save the date
|
||||||
entryEditContentsView?.expiresDate =
|
entryEditFragment?.expiryTime =
|
||||||
DateInstant(DateTime(expiresDate)
|
DateInstant(DateTime(expiresDate)
|
||||||
.withHourOfDay(hours)
|
.withHourOfDay(hours)
|
||||||
.withMinuteOfHour(minutes)
|
.withMinuteOfHour(minutes)
|
||||||
@@ -696,21 +650,15 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
mNewEntry?.let {
|
|
||||||
populateEntryWithViews(it)
|
|
||||||
outState.putParcelable(KEY_NEW_ENTRY, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
mFocusedEditExtraField?.let {
|
outState.putParcelableArrayList(TEMP_ATTACHMENTS, mTempAttachments)
|
||||||
outState.putParcelable(EXTRA_FIELD_FOCUSED_ENTRY, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun acceptPassword(bundle: Bundle) {
|
override fun acceptPassword(bundle: Bundle) {
|
||||||
bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID)?.let {
|
bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID)?.let {
|
||||||
entryEditContentsView?.password = it
|
entryEditFragment?.password = it
|
||||||
}
|
}
|
||||||
|
|
||||||
entryEditActivityEducation?.let {
|
entryEditActivityEducation?.let {
|
||||||
@@ -734,10 +682,10 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
override fun finish() {
|
override fun finish() {
|
||||||
// Assign entry callback as a result in all case
|
// Assign entry callback as a result in all case
|
||||||
try {
|
try {
|
||||||
mNewEntry?.let {
|
mEntry?.let { entry ->
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
val intentEntry = Intent()
|
val intentEntry = Intent()
|
||||||
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mNewEntry)
|
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry)
|
||||||
intentEntry.putExtras(bundle)
|
intentEntry.putExtras(bundle)
|
||||||
if (mIsNew) {
|
if (mIsNew) {
|
||||||
setResult(ADD_ENTRY_RESULT_CODE, intentEntry)
|
setResult(ADD_ENTRY_RESULT_CODE, intentEntry)
|
||||||
@@ -761,8 +709,7 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
const val KEY_PARENT = "parent"
|
const val KEY_PARENT = "parent"
|
||||||
|
|
||||||
// SaveInstanceState
|
// SaveInstanceState
|
||||||
const val KEY_NEW_ENTRY = "new_entry"
|
const val TEMP_ATTACHMENTS = "TEMP_ATTACHMENTS"
|
||||||
const val EXTRA_FIELD_FOCUSED_ENTRY = "EXTRA_FIELD_FOCUSED_ENTRY"
|
|
||||||
|
|
||||||
// Keys for callback
|
// Keys for callback
|
||||||
const val ADD_ENTRY_RESULT_CODE = 31
|
const val ADD_ENTRY_RESULT_CODE = 31
|
||||||
@@ -770,6 +717,8 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129
|
const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129
|
||||||
const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
|
const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
|
||||||
|
|
||||||
|
const val ENTRY_EDIT_FRAGMENT_TAG = "ENTRY_EDIT_FRAGMENT_TAG"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch EntryEditActivity to update an existing entry
|
* Launch EntryEditActivity to update an existing entry
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|||||||
@@ -26,15 +26,18 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
||||||
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||||
|
|
||||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||||
@@ -58,6 +61,10 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
private var mSelectFileHelper: SelectFileHelper? = null
|
private var mSelectFileHelper: SelectFileHelper? = null
|
||||||
|
|
||||||
|
private var mEmptyPasswordConfirmationDialog: AlertDialog? = null
|
||||||
|
private var mNoKeyConfirmationDialog: AlertDialog? = null
|
||||||
|
private var mEmptyKeyFileConfirmationDialog: AlertDialog? = null
|
||||||
|
|
||||||
private val passwordTextWatcher = object : TextWatcher {
|
private val passwordTextWatcher = object : TextWatcher {
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
|
|
||||||
@@ -85,6 +92,17 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mListener = null
|
||||||
|
mEmptyPasswordConfirmationDialog?.dismiss()
|
||||||
|
mEmptyPasswordConfirmationDialog = null
|
||||||
|
mNoKeyConfirmationDialog?.dismiss()
|
||||||
|
mNoKeyConfirmationDialog = null
|
||||||
|
mEmptyKeyFileConfirmationDialog?.dismiss()
|
||||||
|
mEmptyKeyFileConfirmationDialog = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
|
|
||||||
@@ -99,11 +117,15 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
rootView = inflater.inflate(R.layout.fragment_set_password, null)
|
rootView = inflater.inflate(R.layout.fragment_set_password, null)
|
||||||
builder.setView(rootView)
|
builder.setView(rootView)
|
||||||
.setTitle(R.string.assign_master_key)
|
|
||||||
// Add action buttons
|
// Add action buttons
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
|
||||||
|
val credentialsInfo: ImageView? = rootView?.findViewById(R.id.credentials_information)
|
||||||
|
credentialsInfo?.setOnClickListener {
|
||||||
|
UriUtil.gotoUrl(activity, R.string.credentials_explanation_url)
|
||||||
|
}
|
||||||
|
|
||||||
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
||||||
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
|
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
|
||||||
passwordView = rootView?.findViewById(R.id.pass_password)
|
passwordView = rootView?.findViewById(R.id.pass_password)
|
||||||
@@ -129,7 +151,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
mMasterPassword = ""
|
mMasterPassword = ""
|
||||||
mKeyFile = null
|
mKeyFile = null
|
||||||
|
|
||||||
var error = verifyPassword() || verifyFile()
|
var error = verifyPassword() || verifyKeyFile()
|
||||||
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
|
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
|
||||||
error = true
|
error = true
|
||||||
if (allowNoMasterKey)
|
if (allowNoMasterKey)
|
||||||
@@ -199,7 +221,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyFile(): Boolean {
|
private fun verifyKeyFile(): Boolean {
|
||||||
var error = false
|
var error = false
|
||||||
if (keyFileCheckBox != null
|
if (keyFileCheckBox != null
|
||||||
&& keyFileCheckBox!!.isChecked) {
|
&& keyFileCheckBox!!.isChecked) {
|
||||||
@@ -219,7 +241,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
val builder = AlertDialog.Builder(it)
|
val builder = AlertDialog.Builder(it)
|
||||||
builder.setMessage(R.string.warning_empty_password)
|
builder.setMessage(R.string.warning_empty_password)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
if (!verifyFile()) {
|
if (!verifyKeyFile()) {
|
||||||
mListener?.onAssignKeyDialogPositiveClick(
|
mListener?.onAssignKeyDialogPositiveClick(
|
||||||
passwordCheckBox!!.isChecked, mMasterPassword,
|
passwordCheckBox!!.isChecked, mMasterPassword,
|
||||||
keyFileCheckBox!!.isChecked, mKeyFile)
|
keyFileCheckBox!!.isChecked, mKeyFile)
|
||||||
@@ -227,7 +249,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
builder.create().show()
|
mEmptyPasswordConfirmationDialog = builder.create()
|
||||||
|
mEmptyPasswordConfirmationDialog?.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +265,28 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
this@AssignMasterKeyDialogFragment.dismiss()
|
this@AssignMasterKeyDialogFragment.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
builder.create().show()
|
mNoKeyConfirmationDialog = builder.create()
|
||||||
|
mNoKeyConfirmationDialog?.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEmptyKeyFileConfirmationDialog() {
|
||||||
|
activity?.let {
|
||||||
|
val builder = AlertDialog.Builder(it)
|
||||||
|
builder.setMessage(SpannableStringBuilder().apply {
|
||||||
|
append(getString(R.string.warning_empty_keyfile))
|
||||||
|
append("\n\n")
|
||||||
|
append(getString(R.string.warning_empty_keyfile_explanation))
|
||||||
|
append("\n\n")
|
||||||
|
append(getString(R.string.warning_sure_add_file))
|
||||||
|
})
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
keyFileCheckBox?.isChecked = false
|
||||||
|
keyFileSelectionView?.uri = null
|
||||||
|
}
|
||||||
|
mEmptyKeyFileConfirmationDialog = builder.create()
|
||||||
|
mEmptyKeyFileConfirmationDialog?.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,8 +295,14 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
mSelectFileHelper?.onActivityResultCallback(requestCode, resultCode, data) { uri ->
|
||||||
uri?.let { pathUri ->
|
uri?.let { pathUri ->
|
||||||
|
UriUtil.getFileData(requireContext(), uri)?.length()?.let { lengthFile ->
|
||||||
|
keyFileSelectionView?.error = null
|
||||||
keyFileCheckBox?.isChecked = true
|
keyFileCheckBox?.isChecked = true
|
||||||
keyFileSelectionView?.uri = pathUri
|
keyFileSelectionView?.uri = pathUri
|
||||||
|
if (lengthFile <= 0L) {
|
||||||
|
showEmptyKeyFileConfirmationDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ class DatePickerFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
// Create a new instance of DatePickerDialog and return it
|
// Create a new instance of DatePickerDialog and return it
|
||||||
return context?.let {
|
return context?.let {
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ class DeleteNodesDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
|
||||||
arguments?.apply {
|
arguments?.apply {
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ class EntryCustomFieldDialogFragment: DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
entryCustomFieldListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
val root = activity.layoutInflater.inflate(R.layout.fragment_entry_new_field, null)
|
val root = activity.layoutInflater.inflate(R.layout.fragment_entry_new_field, null)
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ class FileTooBigDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mActionChooseListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
// Use the Builder class for convenient dialog construction
|
// Use the Builder class for convenient dialog construction
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
throw ClassCastException(context.toString()
|
throw ClassCastException(context.toString()
|
||||||
+ " must implement " + GroupEditDialogFragment::class.java.name)
|
+ " must implement " + GroupEditDialogFragment::class.java.name)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
editGroupListener = null
|
||||||
|
super.onDetach()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.widget.ImageViewCompat
|
import androidx.core.widget.ImageViewCompat
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||||
import com.kunzisoft.keepass.icons.IconPack
|
import com.kunzisoft.keepass.icons.IconPack
|
||||||
@@ -56,6 +57,11 @@ class IconPickerDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
iconPickerListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
@@ -132,7 +138,7 @@ class IconPickerDialogFragment : DialogFragment() {
|
|||||||
return bundle.getParcelable(KEY_ICON_STANDARD)
|
return bundle.getParcelable(KEY_ICON_STANDARD)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launch(activity: AppCompatActivity) {
|
fun launch(activity: FragmentActivity) {
|
||||||
// Create an instance of the dialog fragment and show it
|
// Create an instance of the dialog fragment and show it
|
||||||
val dialog = IconPickerDialogFragment()
|
val dialog = IconPickerDialogFragment()
|
||||||
dialog.show(activity.supportFragmentManager, "IconPickerDialogFragment")
|
dialog.show(activity.supportFragmentManager, "IconPickerDialogFragment")
|
||||||
|
|||||||
@@ -21,20 +21,51 @@ package com.kunzisoft.keepass.activities.dialogs
|
|||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
class PasswordEncodingDialogFragment : DialogFragment() {
|
class PasswordEncodingDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
var positiveButtonClickListener: DialogInterface.OnClickListener? = null
|
private var mListener: Listener? = null
|
||||||
|
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
|
super.onAttach(context)
|
||||||
|
try {
|
||||||
|
mListener = context as Listener
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
throw ClassCastException(context.toString()
|
||||||
|
+ " must implement " + Listener::class.java.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
|
||||||
|
val databaseUri: Uri? = savedInstanceState?.getParcelable(DATABASE_URI_KEY)
|
||||||
|
val masterPasswordChecked: Boolean = savedInstanceState?.getBoolean(MASTER_PASSWORD_CHECKED_KEY) ?: false
|
||||||
|
val masterPassword: String? = savedInstanceState?.getString(MASTER_PASSWORD_KEY)
|
||||||
|
val keyFileChecked: Boolean = savedInstanceState?.getBoolean(KEY_FILE_CHECKED_KEY) ?: false
|
||||||
|
val keyFile: Uri? = savedInstanceState?.getParcelable(KEY_FILE_URI_KEY)
|
||||||
|
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
|
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
|
||||||
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener)
|
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
mListener?.onPasswordEncodingValidateListener(
|
||||||
|
databaseUri,
|
||||||
|
masterPasswordChecked,
|
||||||
|
masterPassword,
|
||||||
|
keyFileChecked,
|
||||||
|
keyFile
|
||||||
|
)
|
||||||
|
}
|
||||||
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
|
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
@@ -42,5 +73,36 @@ class PasswordEncodingDialogFragment : DialogFragment() {
|
|||||||
return super.onCreateDialog(savedInstanceState)
|
return super.onCreateDialog(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onPasswordEncodingValidateListener(databaseUri: Uri?,
|
||||||
|
masterPasswordChecked: Boolean,
|
||||||
|
masterPassword: String?,
|
||||||
|
keyFileChecked: Boolean,
|
||||||
|
keyFile: Uri?)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val DATABASE_URI_KEY = "DATABASE_URI_KEY"
|
||||||
|
private const val MASTER_PASSWORD_CHECKED_KEY = "MASTER_PASSWORD_CHECKED_KEY"
|
||||||
|
private const val MASTER_PASSWORD_KEY = "MASTER_PASSWORD_KEY"
|
||||||
|
private const val KEY_FILE_CHECKED_KEY = "KEY_FILE_CHECKED_KEY"
|
||||||
|
private const val KEY_FILE_URI_KEY = "KEY_FILE_URI_KEY"
|
||||||
|
|
||||||
|
fun getInstance(databaseUri: Uri,
|
||||||
|
masterPasswordChecked: Boolean,
|
||||||
|
masterPassword: String?,
|
||||||
|
keyFileChecked: Boolean,
|
||||||
|
keyFile: Uri?): SortDialogFragment {
|
||||||
|
val fragment = SortDialogFragment()
|
||||||
|
fragment.arguments = Bundle().apply {
|
||||||
|
putParcelable(DATABASE_URI_KEY, databaseUri)
|
||||||
|
putBoolean(MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
|
||||||
|
putString(MASTER_PASSWORD_KEY, masterPassword)
|
||||||
|
putBoolean(KEY_FILE_CHECKED_KEY, keyFileChecked)
|
||||||
|
putParcelable(KEY_FILE_URI_KEY, keyFile)
|
||||||
|
}
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ class ReplaceFileDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mActionChooseListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
// Use the Builder class for convenient dialog construction
|
// Use the Builder class for convenient dialog construction
|
||||||
|
|||||||
@@ -107,6 +107,11 @@ class SetOTPDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mCreateOTPElementListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ class SortDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ class TimePickerFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mListener = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
// Create a new instance of DatePickerDialog and return it
|
// Create a new instance of DatePickerDialog and return it
|
||||||
return context?.let {
|
return context?.let {
|
||||||
|
|||||||
@@ -20,7 +20,10 @@
|
|||||||
package com.kunzisoft.keepass.adapters
|
package com.kunzisoft.keepass.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.TypedArray
|
||||||
|
import android.graphics.Color
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
@@ -33,11 +36,24 @@ import com.kunzisoft.keepass.model.AttachmentState
|
|||||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
import com.kunzisoft.keepass.model.StreamDirection
|
import com.kunzisoft.keepass.model.StreamDirection
|
||||||
|
|
||||||
|
|
||||||
class EntryAttachmentsItemsAdapter(context: Context)
|
class EntryAttachmentsItemsAdapter(context: Context)
|
||||||
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
||||||
|
|
||||||
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
|
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
|
||||||
|
|
||||||
|
private var mTitleColor: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Get the primary text color of the theme
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
context.theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true)
|
||||||
|
val typedArray: TypedArray = context.obtainStyledAttributes(typedValue.data, intArrayOf(
|
||||||
|
android.R.attr.textColor))
|
||||||
|
mTitleColor = typedArray.getColor(0, -1)
|
||||||
|
typedArray.recycle()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
|
||||||
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
|
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
|
||||||
}
|
}
|
||||||
@@ -46,7 +62,20 @@ class EntryAttachmentsItemsAdapter(context: Context)
|
|||||||
val entryAttachmentState = itemsList[position]
|
val entryAttachmentState = itemsList[position]
|
||||||
|
|
||||||
holder.itemView.visibility = View.VISIBLE
|
holder.itemView.visibility = View.VISIBLE
|
||||||
|
holder.binaryFileBroken.apply {
|
||||||
|
setColorFilter(Color.RED)
|
||||||
|
visibility = if (entryAttachmentState.attachment.binaryAttachment.isCorrupted) {
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
holder.binaryFileTitle.text = entryAttachmentState.attachment.name
|
holder.binaryFileTitle.text = entryAttachmentState.attachment.name
|
||||||
|
if (entryAttachmentState.attachment.binaryAttachment.isCorrupted) {
|
||||||
|
holder.binaryFileTitle.setTextColor(Color.RED)
|
||||||
|
} else {
|
||||||
|
holder.binaryFileTitle.setTextColor(mTitleColor)
|
||||||
|
}
|
||||||
holder.binaryFileSize.text = Formatter.formatFileSize(context,
|
holder.binaryFileSize.text = Formatter.formatFileSize(context,
|
||||||
entryAttachmentState.attachment.binaryAttachment.length())
|
entryAttachmentState.attachment.binaryAttachment.length())
|
||||||
holder.binaryFileCompression.apply {
|
holder.binaryFileCompression.apply {
|
||||||
@@ -107,6 +136,7 @@ class EntryAttachmentsItemsAdapter(context: Context)
|
|||||||
|
|
||||||
class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
|
var binaryFileBroken: ImageView = itemView.findViewById(R.id.item_attachment_broken)
|
||||||
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
|
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
|
||||||
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
|
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
|
||||||
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
|
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
|
||||||
|
|||||||
@@ -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_ABOVE_CLIENT
|
||||||
import android.content.Context.BIND_NOT_FOREGROUND
|
import android.content.Context.BIND_NOT_FOREGROUND
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
@@ -46,6 +45,7 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
|
|||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
|
||||||
@@ -463,6 +463,13 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
|||||||
, ACTION_DATABASE_UPDATE_COMPRESSION_TASK)
|
, ACTION_DATABASE_UPDATE_COMPRESSION_TASK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startDatabaseRemoveUnlinkedData(save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int,
|
fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int,
|
||||||
newMaxHistoryItems: Int,
|
newMaxHistoryItems: Int,
|
||||||
save: Boolean) {
|
save: Boolean) {
|
||||||
|
|||||||
@@ -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) {
|
fun removeAttachmentIfNotUsed(attachment: Attachment) {
|
||||||
// No need in KDB database because unique attachment by entry
|
// No need in KDB database because unique attachment by entry
|
||||||
mDatabaseKDBX?.removeAttachmentIfNotUsed(attachment)
|
// Don't clear to fix upload multiple times
|
||||||
|
mDatabaseKDBX?.removeUnlinkedAttachment(attachment.binaryAttachment, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeUnlinkedAttachments() {
|
fun removeUnlinkedAttachments() {
|
||||||
// No check in database KDB because unique attachment by entry
|
// No check in database KDB because unique attachment by entry
|
||||||
mDatabaseKDBX?.removeUnlinkedAttachments()
|
mDatabaseKDBX?.removeUnlinkedAttachments(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(DatabaseOutputException::class)
|
@Throws(DatabaseOutputException::class)
|
||||||
@@ -822,18 +823,25 @@ class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startManageEntry(entry: Entry) {
|
fun startManageEntry(entry: Entry?) {
|
||||||
mDatabaseKDBX?.let {
|
mDatabaseKDBX?.let {
|
||||||
entry.startToManageFieldReferences(it)
|
entry?.startToManageFieldReferences(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopManageEntry(entry: Entry) {
|
fun stopManageEntry(entry: Entry?) {
|
||||||
mDatabaseKDBX?.let {
|
mDatabaseKDBX?.let {
|
||||||
entry.stopToManageFieldReferences()
|
entry?.stopToManageFieldReferences()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if database allows custom field
|
||||||
|
*/
|
||||||
|
fun allowEntryCustomFields(): Boolean {
|
||||||
|
return mDatabaseKDBX != null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove oldest history for each entry if more than max items or max memory
|
* Remove oldest history for each entry if more than max items or max memory
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import android.content.res.Resources
|
|||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.core.os.ConfigurationCompat
|
import androidx.core.os.ConfigurationCompat
|
||||||
|
import org.joda.time.Duration
|
||||||
|
import org.joda.time.Instant
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -95,6 +97,7 @@ class DateInstant : Parcelable {
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val NEVER_EXPIRE = neverExpire
|
val NEVER_EXPIRE = neverExpire
|
||||||
|
val IN_ONE_MONTH = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
|
||||||
private val dateFormat = SimpleDateFormat.getDateTimeInstance()
|
private val dateFormat = SimpleDateFormat.getDateTimeInstance()
|
||||||
|
|
||||||
private val neverExpire: DateInstant
|
private val neverExpire: DateInstant
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import com.kunzisoft.keepass.database.element.node.Node
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
|
||||||
import com.kunzisoft.keepass.model.EntryInfo
|
import com.kunzisoft.keepass.model.EntryInfo
|
||||||
import com.kunzisoft.keepass.model.Field
|
import com.kunzisoft.keepass.model.Field
|
||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
@@ -284,38 +283,44 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve custom fields to show, key is the label, value is the value of field (protected or not)
|
* Retrieve extra fields to show, key is the label, value is the value of field (protected or not)
|
||||||
* @return Map of label/value
|
* @return Map of label/value
|
||||||
*/
|
*/
|
||||||
val customFields: HashMap<String, ProtectedString>
|
fun getExtraFields(): List<Field> {
|
||||||
get() = entryKDBX?.customFields ?: HashMap()
|
val extraFields = ArrayList<Field>()
|
||||||
|
entryKDBX?.let {
|
||||||
/**
|
for (field in it.customFields) {
|
||||||
* To redefine if version of entry allow custom field,
|
extraFields.add(Field(field.key, field.value))
|
||||||
* @return true if entry allows custom field
|
|
||||||
*/
|
|
||||||
fun allowCustomFields(): Boolean {
|
|
||||||
return entryKDBX?.allowCustomFields() ?: false
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fun removeAllFields() {
|
return extraFields
|
||||||
entryKDBX?.removeAllFields()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update or add an extra field to the list (standard or custom)
|
* Update or add an extra field to the list (standard or custom)
|
||||||
* @param label Label of field, must be unique
|
|
||||||
* @param value Value of field
|
|
||||||
*/
|
*/
|
||||||
fun putExtraField(label: String, value: ProtectedString) {
|
fun putExtraField(field: Field) {
|
||||||
entryKDBX?.putExtraField(label, value)
|
entryKDBX?.putExtraField(field.name, field.protectedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addExtraFields(fields: List<Field>) {
|
||||||
|
fields.forEach {
|
||||||
|
putExtraField(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeAllFields() {
|
||||||
|
entryKDBX?.removeAllFields()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOtpElement(): OtpElement? {
|
fun getOtpElement(): OtpElement? {
|
||||||
|
entryKDBX?.let {
|
||||||
return OtpEntryFields.parseFields { key ->
|
return OtpEntryFields.parseFields { key ->
|
||||||
customFields[key]?.toString()
|
it.customFields[key]?.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
fun startToManageFieldReferences(database: DatabaseKDBX) {
|
fun startToManageFieldReferences(database: DatabaseKDBX) {
|
||||||
entryKDBX?.startToManageFieldReferences(database)
|
entryKDBX?.startToManageFieldReferences(database)
|
||||||
@@ -341,16 +346,27 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
|| entryKDBX?.containsAttachment() == true
|
|| entryKDBX?.containsAttachment() == true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
|
private fun addAttachments(binaryPool: BinaryPool, attachments: List<Attachment>) {
|
||||||
entryKDB?.putAttachment(attachment)
|
attachments.forEach {
|
||||||
entryKDBX?.putAttachment(attachment, binaryPool)
|
putAttachment(it, binaryPool)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAttachment(attachment: Attachment) {
|
private fun removeAttachment(attachment: Attachment) {
|
||||||
entryKDB?.removeAttachment(attachment)
|
entryKDB?.removeAttachment(attachment)
|
||||||
entryKDBX?.removeAttachment(attachment)
|
entryKDBX?.removeAttachment(attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeAllAttachments() {
|
||||||
|
entryKDB?.removeAttachment()
|
||||||
|
entryKDBX?.removeAttachments()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun putAttachment(attachment: Attachment, binaryPool: BinaryPool) {
|
||||||
|
entryKDB?.putAttachment(attachment)
|
||||||
|
entryKDBX?.putAttachment(attachment, binaryPool)
|
||||||
|
}
|
||||||
|
|
||||||
fun getHistory(): ArrayList<Entry> {
|
fun getHistory(): ArrayList<Entry> {
|
||||||
val history = ArrayList<Entry>()
|
val history = ArrayList<Entry>()
|
||||||
val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
|
val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
|
||||||
@@ -404,26 +420,54 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
database?.stopManageEntry(this)
|
database?.stopManageEntry(this)
|
||||||
else
|
else
|
||||||
database?.startManageEntry(this)
|
database?.startManageEntry(this)
|
||||||
|
|
||||||
entryInfo.id = nodeId.toString()
|
entryInfo.id = nodeId.toString()
|
||||||
entryInfo.title = title
|
entryInfo.title = title
|
||||||
entryInfo.icon = icon
|
entryInfo.icon = icon
|
||||||
entryInfo.username = username
|
entryInfo.username = username
|
||||||
entryInfo.password = password
|
entryInfo.password = password
|
||||||
|
entryInfo.expires = expires
|
||||||
|
entryInfo.expiryTime = expiryTime
|
||||||
entryInfo.url = url
|
entryInfo.url = url
|
||||||
entryInfo.notes = notes
|
entryInfo.notes = notes
|
||||||
for (entry in customFields.entries) {
|
entryInfo.customFields = getExtraFields()
|
||||||
entryInfo.customFields.add(
|
|
||||||
Field(entry.key, entry.value))
|
|
||||||
}
|
|
||||||
// Add otpElement to generate token
|
// Add otpElement to generate token
|
||||||
entryInfo.otpModel = getOtpElement()?.otpModel
|
entryInfo.otpModel = getOtpElement()?.otpModel
|
||||||
|
if (!raw) {
|
||||||
// Replace parameter fields by generated OTP fields
|
// Replace parameter fields by generated OTP fields
|
||||||
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
|
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
|
||||||
|
}
|
||||||
|
database?.binaryPool?.let { binaryPool ->
|
||||||
|
entryInfo.attachments = getAttachments(binaryPool)
|
||||||
|
}
|
||||||
|
|
||||||
if (!raw)
|
if (!raw)
|
||||||
database?.stopManageEntry(this)
|
database?.stopManageEntry(this)
|
||||||
return entryInfo
|
return entryInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setEntryInfo(database: Database?, newEntryInfo: EntryInfo) {
|
||||||
|
database?.startManageEntry(this)
|
||||||
|
|
||||||
|
removeAllFields()
|
||||||
|
removeAllAttachments()
|
||||||
|
// NodeId stay as is
|
||||||
|
title = newEntryInfo.title
|
||||||
|
icon = newEntryInfo.icon
|
||||||
|
username = newEntryInfo.username
|
||||||
|
password = newEntryInfo.password
|
||||||
|
expires = newEntryInfo.expires
|
||||||
|
expiryTime = newEntryInfo.expiryTime
|
||||||
|
url = newEntryInfo.url
|
||||||
|
notes = newEntryInfo.notes
|
||||||
|
addExtraFields(newEntryInfo.customFields)
|
||||||
|
database?.binaryPool?.let { binaryPool ->
|
||||||
|
addAttachments(binaryPool, newEntryInfo.attachments)
|
||||||
|
}
|
||||||
|
|
||||||
|
database?.stopManageEntry(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class BinaryAttachment : Parcelable {
|
|||||||
private set
|
private set
|
||||||
var isProtected: Boolean = false
|
var isProtected: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
var isCorrupted: Boolean = false
|
||||||
private var dataFile: File? = null
|
private var dataFile: File? = null
|
||||||
|
|
||||||
fun length(): Long {
|
fun length(): Long {
|
||||||
@@ -43,11 +44,7 @@ class BinaryAttachment : Parcelable {
|
|||||||
/**
|
/**
|
||||||
* Empty protected binary
|
* Empty protected binary
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor()
|
||||||
this.isCompressed = false
|
|
||||||
this.isProtected = false
|
|
||||||
this.dataFile = null
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean = false) {
|
constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean = false) {
|
||||||
this.isCompressed = compressed
|
this.isCompressed = compressed
|
||||||
@@ -59,6 +56,7 @@ class BinaryAttachment : Parcelable {
|
|||||||
val compressedByte = parcel.readByte().toInt()
|
val compressedByte = parcel.readByte().toInt()
|
||||||
isCompressed = compressedByte != 0
|
isCompressed = compressedByte != 0
|
||||||
isProtected = parcel.readByte().toInt() != 0
|
isProtected = parcel.readByte().toInt() != 0
|
||||||
|
isCorrupted = parcel.readByte().toInt() != 0
|
||||||
parcel.readString()?.let {
|
parcel.readString()?.let {
|
||||||
dataFile = File(it)
|
dataFile = File(it)
|
||||||
}
|
}
|
||||||
@@ -164,6 +162,7 @@ class BinaryAttachment : Parcelable {
|
|||||||
|
|
||||||
return isCompressed == other.isCompressed
|
return isCompressed == other.isCompressed
|
||||||
&& isProtected == other.isProtected
|
&& isProtected == other.isProtected
|
||||||
|
&& isCorrupted == other.isCorrupted
|
||||||
&& sameData
|
&& sameData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +171,7 @@ class BinaryAttachment : Parcelable {
|
|||||||
var result = 0
|
var result = 0
|
||||||
result = 31 * result + if (isCompressed) 1 else 0
|
result = 31 * result + if (isCompressed) 1 else 0
|
||||||
result = 31 * result + if (isProtected) 1 else 0
|
result = 31 * result + if (isProtected) 1 else 0
|
||||||
|
result = 31 * result + if (isCorrupted) 1 else 0
|
||||||
result = 31 * result + dataFile!!.hashCode()
|
result = 31 * result + dataFile!!.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -187,6 +187,7 @@ class BinaryAttachment : Parcelable {
|
|||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
dest.writeByte((if (isCompressed) 1 else 0).toByte())
|
dest.writeByte((if (isCompressed) 1 else 0).toByte())
|
||||||
dest.writeByte((if (isProtected) 1 else 0).toByte())
|
dest.writeByte((if (isProtected) 1 else 0).toByte())
|
||||||
|
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
|
||||||
dest.writeString(dataFile?.absolutePath)
|
dest.writeString(dataFile?.absolutePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
// Retrieve backup group in index
|
// Retrieve backup group in index
|
||||||
val backupGroup: GroupKDB?
|
val backupGroup: GroupKDB?
|
||||||
get() {
|
get() {
|
||||||
if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
|
|
||||||
ensureBackupExists()
|
|
||||||
return if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
|
return if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
@@ -186,6 +184,9 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
override fun isInRecycleBin(group: GroupKDB): Boolean {
|
override fun isInRecycleBin(group: GroupKDB): Boolean {
|
||||||
var currentGroup: GroupKDB? = group
|
var currentGroup: GroupKDB? = group
|
||||||
|
|
||||||
|
if (backupGroup == null)
|
||||||
|
return false
|
||||||
|
|
||||||
if (currentGroup == backupGroup)
|
if (currentGroup == backupGroup)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -229,6 +230,8 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
* @return true if node can be recycle, false elsewhere
|
* @return true if node can be recycle, false elsewhere
|
||||||
*/
|
*/
|
||||||
fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
|
fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
|
||||||
|
if (backupGroup == null)
|
||||||
|
ensureBackupExists()
|
||||||
if (node == backupGroup)
|
if (node == backupGroup)
|
||||||
return false
|
return false
|
||||||
backupGroup?.let {
|
backupGroup?.let {
|
||||||
@@ -239,14 +242,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun recycle(group: GroupKDB) {
|
fun recycle(group: GroupKDB) {
|
||||||
ensureBackupExists()
|
|
||||||
removeGroupFrom(group, group.parent)
|
removeGroupFrom(group, group.parent)
|
||||||
addGroupTo(group, backupGroup)
|
addGroupTo(group, backupGroup)
|
||||||
group.afterAssignNewParent()
|
group.afterAssignNewParent()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun recycle(entry: EntryKDB) {
|
fun recycle(entry: EntryKDB) {
|
||||||
ensureBackupExists()
|
|
||||||
removeEntryFrom(entry, entry.parent)
|
removeEntryFrom(entry, entry.parent)
|
||||||
addEntryTo(entry, backupGroup)
|
addEntryTo(entry, backupGroup)
|
||||||
entry.afterAssignNewParent()
|
entry.afterAssignNewParent()
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
|||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
||||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||||
import com.kunzisoft.keepass.database.element.Attachment
|
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||||
@@ -42,7 +42,6 @@ import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
|||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
||||||
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
@@ -570,12 +569,17 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
return binaryAttachment
|
return binaryAttachment
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAttachmentIfNotUsed(attachment: Attachment) {
|
fun removeUnlinkedAttachment(binary: BinaryAttachment, clear: Boolean) {
|
||||||
// Remove attachment from pool
|
val listBinaries = ArrayList<BinaryAttachment>()
|
||||||
removeUnlinkedAttachments(attachment.binaryAttachment)
|
listBinaries.add(binary)
|
||||||
|
removeUnlinkedAttachments(listBinaries, clear)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeUnlinkedAttachments(vararg binaries: BinaryAttachment) {
|
fun removeUnlinkedAttachments(clear: Boolean) {
|
||||||
|
removeUnlinkedAttachments(emptyList(), clear)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnlinkedAttachments(binaries: List<BinaryAttachment>, clear: Boolean) {
|
||||||
// Build binaries to remove with all binaries known
|
// Build binaries to remove with all binaries known
|
||||||
val binariesToRemove = ArrayList<BinaryAttachment>()
|
val binariesToRemove = ArrayList<BinaryAttachment>()
|
||||||
if (binaries.isEmpty()) {
|
if (binaries.isEmpty()) {
|
||||||
@@ -598,6 +602,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
binariesToRemove.forEach {
|
binariesToRemove.forEach {
|
||||||
try {
|
try {
|
||||||
binaryPool.remove(it)
|
binaryPool.remove(it)
|
||||||
|
if (clear)
|
||||||
|
it.clear()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Unable to clean binaries", e)
|
Log.w(TAG, "Unable to clean binaries", e)
|
||||||
}
|
}
|
||||||
@@ -632,7 +638,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
private const val KeyElementName = "Key"
|
private const val KeyElementName = "Key"
|
||||||
private const val KeyDataElementName = "Data"
|
private const val KeyDataElementName = "Data"
|
||||||
|
|
||||||
const val BASE_64_FLAG = Base64.NO_WRAP
|
const val BASE_64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP
|
||||||
|
|
||||||
const val BUFFER_SIZE_BYTES = 3 * 128
|
const val BUFFER_SIZE_BYTES = 3 * 128
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
|||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||||
import com.kunzisoft.keepass.database.exception.KeyFileEmptyDatabaseException
|
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
@@ -136,7 +135,6 @@ abstract class DatabaseVersioned<
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (keyData.size.toLong()) {
|
when (keyData.size.toLong()) {
|
||||||
0L -> throw KeyFileEmptyDatabaseException()
|
|
||||||
32L -> return keyData
|
32L -> return keyData
|
||||||
64L -> try {
|
64L -> try {
|
||||||
return hexStringToByteArray(String(keyData))
|
return hexStringToByteArray(String(keyData))
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
|
|||||||
this.binaryData = attachment.binaryAttachment
|
this.binaryData = attachment.binaryAttachment
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAttachment(attachment: Attachment) {
|
fun removeAttachment(attachment: Attachment? = null) {
|
||||||
if (this.binaryDescription == attachment.name) {
|
if (attachment == null || this.binaryDescription == attachment.name) {
|
||||||
this.binaryDescription = ""
|
this.binaryDescription = ""
|
||||||
this.binaryData = null
|
this.binaryData = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import com.kunzisoft.keepass.utils.ParcelableUtil
|
|||||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashSet
|
|
||||||
import kotlin.collections.LinkedHashMap
|
import kotlin.collections.LinkedHashMap
|
||||||
|
|
||||||
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
||||||
@@ -272,10 +271,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allowCustomFields(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeAllFields() {
|
fun removeAllFields() {
|
||||||
fields.clear()
|
fields.clear()
|
||||||
}
|
}
|
||||||
@@ -314,6 +309,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
binaries.remove(attachment.name)
|
binaries.remove(attachment.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeAttachments() {
|
||||||
|
binaries.clear()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getAttachmentsSize(binaryPool: BinaryPool): Long {
|
private fun getAttachmentsSize(binaryPool: BinaryPool): Long {
|
||||||
var size = 0L
|
var size = 0L
|
||||||
for ((label, poolId) in binaries) {
|
for ((label, poolId) in binaries) {
|
||||||
@@ -323,11 +322,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove ?
|
|
||||||
fun sizeOfHistory(): Int {
|
|
||||||
return history.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun putCustomData(key: String, value: String) {
|
override fun putCustomData(key: String, value: String) {
|
||||||
customData[key] = value
|
customData[key] = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,13 +116,6 @@ class InvalidCredentialsDatabaseException : LoadDatabaseException {
|
|||||||
constructor(exception: Throwable) : super(exception)
|
constructor(exception: Throwable) : super(exception)
|
||||||
}
|
}
|
||||||
|
|
||||||
class KeyFileEmptyDatabaseException : LoadDatabaseException {
|
|
||||||
@StringRes
|
|
||||||
override var errorId: Int = R.string.keyfile_is_empty
|
|
||||||
constructor() : super()
|
|
||||||
constructor(exception: Throwable) : super(exception)
|
|
||||||
}
|
|
||||||
|
|
||||||
class NoMemoryDatabaseException: LoadDatabaseException {
|
class NoMemoryDatabaseException: LoadDatabaseException {
|
||||||
@StringRes
|
@StringRes
|
||||||
override var errorId: Int = R.string.error_out_of_memory
|
override var errorId: Int = R.string.error_out_of_memory
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package com.kunzisoft.keepass.database.file.input
|
package com.kunzisoft.keepass.database.file.input
|
||||||
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import android.util.Log
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
import com.kunzisoft.keepass.crypto.StreamCipherFactory
|
import com.kunzisoft.keepass.crypto.StreamCipherFactory
|
||||||
@@ -742,8 +743,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
if (entryInHistory) {
|
if (entryInHistory) {
|
||||||
ctxEntry = ctxHistoryBase
|
ctxEntry = ctxHistoryBase
|
||||||
return KdbContext.EntryHistory
|
return KdbContext.EntryHistory
|
||||||
}
|
} else if (ctxEntry != null) {
|
||||||
else if (ctxEntry != null) {
|
|
||||||
// Add entry to the index only when close the XML element
|
// Add entry to the index only when close the XML element
|
||||||
mDatabase.addEntryIndex(ctxEntry!!)
|
mDatabase.addEntryIndex(ctxEntry!!)
|
||||||
}
|
}
|
||||||
@@ -879,9 +879,14 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
if (encoded.isEmpty()) {
|
if (encoded.isEmpty()) {
|
||||||
return DatabaseVersioned.UUID_ZERO
|
return DatabaseVersioned.UUID_ZERO
|
||||||
}
|
}
|
||||||
val buf = Base64.decode(encoded, BASE_64_FLAG)
|
|
||||||
|
|
||||||
return bytes16ToUuid(buf)
|
return try {
|
||||||
|
val buf = Base64.decode(encoded, BASE_64_FLAG)
|
||||||
|
bytes16ToUuid(buf)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to read base 64 UUID, create a random one", e)
|
||||||
|
UUID.randomUUID()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
@@ -981,12 +986,19 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
val base64 = readString(xpp)
|
val base64 = readString(xpp)
|
||||||
if (base64.isEmpty())
|
if (base64.isEmpty())
|
||||||
return null
|
return null
|
||||||
val data = Base64.decode(base64, BASE_64_FLAG)
|
|
||||||
|
|
||||||
// Build the new binary and compress
|
// Build the new binary and compress
|
||||||
val binaryAttachment = mDatabase.buildNewBinary(cacheDirectory, protected, compressed, binaryId)
|
val binaryAttachment = mDatabase.buildNewBinary(cacheDirectory, protected, compressed, binaryId)
|
||||||
|
try {
|
||||||
binaryAttachment.getOutputDataStream().use { outputStream ->
|
binaryAttachment.getOutputDataStream().use { outputStream ->
|
||||||
outputStream.write(data)
|
outputStream.write(Base64.decode(base64, BASE_64_FLAG))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to read base 64 attachment", e)
|
||||||
|
binaryAttachment.isCorrupted = true
|
||||||
|
binaryAttachment.getOutputDataStream().use { outputStream ->
|
||||||
|
outputStream.write(base64.toByteArray())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return binaryAttachment
|
return binaryAttachment
|
||||||
}
|
}
|
||||||
@@ -1045,6 +1057,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
private val TAG = DatabaseInputKDBX::class.java.name
|
||||||
|
|
||||||
private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
|
private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
|
||||||
|
|
||||||
@Throws(XmlPullParserException::class)
|
@Throws(XmlPullParserException::class)
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ package com.kunzisoft.keepass.model
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
|
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -30,12 +33,15 @@ class EntryInfo : Parcelable {
|
|||||||
|
|
||||||
var id: String = ""
|
var id: String = ""
|
||||||
var title: String = ""
|
var title: String = ""
|
||||||
var icon: IconImage? = null
|
var icon: IconImage = IconImageStandard()
|
||||||
var username: String = ""
|
var username: String = ""
|
||||||
var password: String = ""
|
var password: String = ""
|
||||||
|
var expires: Boolean = false
|
||||||
|
var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH
|
||||||
var url: String = ""
|
var url: String = ""
|
||||||
var notes: String = ""
|
var notes: String = ""
|
||||||
var customFields: MutableList<Field> = ArrayList()
|
var customFields: List<Field> = ArrayList()
|
||||||
|
var attachments: List<Attachment> = ArrayList()
|
||||||
var otpModel: OtpModel? = null
|
var otpModel: OtpModel? = null
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
@@ -43,12 +49,15 @@ class EntryInfo : Parcelable {
|
|||||||
private constructor(parcel: Parcel) {
|
private constructor(parcel: Parcel) {
|
||||||
id = parcel.readString() ?: id
|
id = parcel.readString() ?: id
|
||||||
title = parcel.readString() ?: title
|
title = parcel.readString() ?: title
|
||||||
icon = parcel.readParcelable(IconImage::class.java.classLoader)
|
icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
|
||||||
username = parcel.readString() ?: username
|
username = parcel.readString() ?: username
|
||||||
password = parcel.readString() ?: password
|
password = parcel.readString() ?: password
|
||||||
|
expires = parcel.readInt() != 0
|
||||||
|
expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
|
||||||
url = parcel.readString() ?: url
|
url = parcel.readString() ?: url
|
||||||
notes = parcel.readString() ?: notes
|
notes = parcel.readString() ?: notes
|
||||||
parcel.readList(customFields as List<Field>, Field::class.java.classLoader)
|
parcel.readList(customFields, Field::class.java.classLoader)
|
||||||
|
parcel.readList(attachments, Attachment::class.java.classLoader)
|
||||||
otpModel = parcel.readParcelable(OtpModel::class.java.classLoader) ?: otpModel
|
otpModel = parcel.readParcelable(OtpModel::class.java.classLoader) ?: otpModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +71,12 @@ class EntryInfo : Parcelable {
|
|||||||
parcel.writeParcelable(icon, flags)
|
parcel.writeParcelable(icon, flags)
|
||||||
parcel.writeString(username)
|
parcel.writeString(username)
|
||||||
parcel.writeString(password)
|
parcel.writeString(password)
|
||||||
|
parcel.writeInt(if (expires) 1 else 0)
|
||||||
|
parcel.writeParcelable(expiryTime, flags)
|
||||||
parcel.writeString(url)
|
parcel.writeString(url)
|
||||||
parcel.writeString(notes)
|
parcel.writeString(notes)
|
||||||
parcel.writeArray(customFields.toTypedArray())
|
parcel.writeArray(customFields.toTypedArray())
|
||||||
|
parcel.writeArray(attachments.toTypedArray())
|
||||||
parcel.writeParcelable(otpModel, flags)
|
parcel.writeParcelable(otpModel, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,15 +58,9 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
|
|
||||||
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
|
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||||
mActionTaskListeners.add(actionTaskListener)
|
mActionTaskListeners.add(actionTaskListener)
|
||||||
attachmentNotificationList.forEach {
|
|
||||||
it.attachmentFileAction?.listener = attachmentFileActionListener
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
|
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||||
attachmentNotificationList.forEach {
|
|
||||||
it.attachmentFileAction?.listener = null
|
|
||||||
}
|
|
||||||
mActionTaskListeners.remove(actionTaskListener)
|
mActionTaskListeners.remove(actionTaskListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,6 +101,13 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
intent,
|
intent,
|
||||||
StreamDirection.DOWNLOAD)
|
StreamDirection.DOWNLOAD)
|
||||||
}
|
}
|
||||||
|
ACTION_ATTACHMENT_REMOVE -> {
|
||||||
|
intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
|
||||||
|
attachmentNotificationList.firstOrNull { it.entryAttachmentState.attachment == entryAttachment }?.let { elementToRemove ->
|
||||||
|
attachmentNotificationList.remove(elementToRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
if (downloadFileUri != null) {
|
if (downloadFileUri != null) {
|
||||||
attachmentNotificationList.firstOrNull { it.uri == downloadFileUri }?.let { elementToRemove ->
|
attachmentNotificationList.firstOrNull { it.uri == downloadFileUri }?.let { elementToRemove ->
|
||||||
@@ -265,6 +266,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
?.notificationId ?: notificationId) + 1
|
?.notificationId ?: notificationId) + 1
|
||||||
val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection)
|
val entryAttachmentState = EntryAttachmentState(entryAttachment, streamDirection)
|
||||||
val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState)
|
val attachmentNotification = AttachmentNotification(downloadFileUri, nextNotificationId, entryAttachmentState)
|
||||||
|
|
||||||
|
// Add action to the list on start
|
||||||
attachmentNotificationList.add(attachmentNotification)
|
attachmentNotificationList.add(attachmentNotification)
|
||||||
|
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
@@ -293,15 +296,18 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun executeAction() {
|
suspend fun executeAction() {
|
||||||
TimeoutHelper.temporarilyDisableTimeout()
|
|
||||||
|
|
||||||
// on pre execute
|
// on pre execute
|
||||||
attachmentNotification.attachmentFileAction = this
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
TimeoutHelper.temporarilyDisableTimeout()
|
||||||
|
|
||||||
|
attachmentNotification.attachmentFileAction = this@AttachmentFileAction
|
||||||
attachmentNotification.entryAttachmentState.apply {
|
attachmentNotification.entryAttachmentState.apply {
|
||||||
downloadState = AttachmentState.START
|
downloadState = AttachmentState.START
|
||||||
downloadProgression = 0
|
downloadProgression = 0
|
||||||
}
|
}
|
||||||
listener?.onUpdate(attachmentNotification)
|
listener?.onUpdate(attachmentNotification)
|
||||||
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
// on Progress with thread
|
// on Progress with thread
|
||||||
@@ -426,6 +432,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
|||||||
|
|
||||||
const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD"
|
const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD"
|
||||||
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
|
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
|
||||||
|
const val ACTION_ATTACHMENT_REMOVE = "ACTION_ATTACHMENT_REMOVE"
|
||||||
|
|
||||||
const val FILE_URI_KEY = "FILE_URI_KEY"
|
const val FILE_URI_KEY = "FILE_URI_KEY"
|
||||||
const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
|
const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent)
|
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent)
|
||||||
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent)
|
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent)
|
||||||
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent)
|
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent)
|
||||||
|
ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK -> buildDatabaseRemoveUnlinkedDataActionTask(intent)
|
||||||
ACTION_DATABASE_UPDATE_NAME_TASK,
|
ACTION_DATABASE_UPDATE_NAME_TASK,
|
||||||
ACTION_DATABASE_UPDATE_DESCRIPTION_TASK,
|
ACTION_DATABASE_UPDATE_DESCRIPTION_TASK,
|
||||||
ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK,
|
ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK,
|
||||||
@@ -711,6 +712,22 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildDatabaseRemoveUnlinkedDataActionTask(intent: Intent): ActionRunnable? {
|
||||||
|
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
|
||||||
|
|
||||||
|
return RemoveUnlinkedDataDatabaseRunnable(this,
|
||||||
|
mDatabase,
|
||||||
|
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
|
||||||
|
).apply {
|
||||||
|
mAfterSaveDatabase = { result ->
|
||||||
|
result.data = intent.extras
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? {
|
private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? {
|
||||||
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
|
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
|
||||||
return SaveDatabaseRunnable(this,
|
return SaveDatabaseRunnable(this,
|
||||||
@@ -760,6 +777,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
|||||||
const val ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK"
|
const val ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK"
|
||||||
const val ACTION_DATABASE_UPDATE_COLOR_TASK = "ACTION_DATABASE_UPDATE_COLOR_TASK"
|
const val ACTION_DATABASE_UPDATE_COLOR_TASK = "ACTION_DATABASE_UPDATE_COLOR_TASK"
|
||||||
const val ACTION_DATABASE_UPDATE_COMPRESSION_TASK = "ACTION_DATABASE_UPDATE_COMPRESSION_TASK"
|
const val ACTION_DATABASE_UPDATE_COMPRESSION_TASK = "ACTION_DATABASE_UPDATE_COMPRESSION_TASK"
|
||||||
|
const val ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK = "ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK"
|
||||||
const val ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK"
|
const val ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK"
|
||||||
const val ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK"
|
const val ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK"
|
||||||
const val ACTION_DATABASE_UPDATE_ENCRYPTION_TASK = "ACTION_DATABASE_UPDATE_ENCRYPTION_TASK"
|
const val ACTION_DATABASE_UPDATE_ENCRYPTION_TASK = "ACTION_DATABASE_UPDATE_ENCRYPTION_TASK"
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ object OtpEntryFields {
|
|||||||
* Build new generated fields in a new list from [fieldsToParse] in parameter,
|
* Build new generated fields in a new list from [fieldsToParse] in parameter,
|
||||||
* Remove parameters fields use to generate auto fields
|
* Remove parameters fields use to generate auto fields
|
||||||
*/
|
*/
|
||||||
fun generateAutoFields(fieldsToParse: MutableList<Field>): MutableList<Field> {
|
fun generateAutoFields(fieldsToParse: List<Field>): MutableList<Field> {
|
||||||
val newCustomFields: MutableList<Field> = ArrayList(fieldsToParse)
|
val newCustomFields: MutableList<Field> = ArrayList(fieldsToParse)
|
||||||
// Remove parameter fields
|
// Remove parameter fields
|
||||||
val otpField = Field(OTP_FIELD)
|
val otpField = Field(OTP_FIELD)
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ class MainPreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mCallback = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
findPreference<Preference>(getString(R.string.database_version_key))
|
findPreference<Preference>(getString(R.string.database_version_key))
|
||||||
?.summary = mDatabase.version
|
?.summary = mDatabase.version
|
||||||
|
|
||||||
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_compression_key))
|
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_data_key))
|
||||||
|
|
||||||
// Database compression
|
// Database compression
|
||||||
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
|
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
|
||||||
@@ -482,6 +482,9 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
getString(R.string.database_data_compression_key) -> {
|
getString(R.string.database_data_compression_key) -> {
|
||||||
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
}
|
}
|
||||||
|
getString(R.string.database_data_remove_unlinked_attachments_key) -> {
|
||||||
|
dialogFragment = DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
}
|
||||||
getString(R.string.max_history_items_key) -> {
|
getString(R.string.max_history_items_key) -> {
|
||||||
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ import com.kunzisoft.keepass.view.showActionError
|
|||||||
open class SettingsActivity
|
open class SettingsActivity
|
||||||
: LockingActivity(),
|
: LockingActivity(),
|
||||||
MainPreferenceFragment.Callback,
|
MainPreferenceFragment.Callback,
|
||||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||||
|
PasswordEncodingDialogFragment.Listener {
|
||||||
|
|
||||||
private var backupManager: BackupManager? = null
|
private var backupManager: BackupManager? = null
|
||||||
|
|
||||||
@@ -50,23 +51,6 @@ open class SettingsActivity
|
|||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
private var lockView: View? = null
|
private var lockView: View? = null
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private const val SHOW_LOCK = "SHOW_LOCK"
|
|
||||||
private const val TAG_NESTED = "TAG_NESTED"
|
|
||||||
|
|
||||||
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
|
|
||||||
val intent = Intent(activity, SettingsActivity::class.java)
|
|
||||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
|
||||||
intent.putExtra(TIMEOUT_ENABLE_KEY, timeoutEnable)
|
|
||||||
if (!timeoutEnable) {
|
|
||||||
activity.startActivity(intent)
|
|
||||||
} else if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
|
||||||
activity.startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the main fragment to show in first
|
* Retrieve the main fragment to show in first
|
||||||
* @return The main fragment
|
* @return The main fragment
|
||||||
@@ -83,7 +67,11 @@ open class SettingsActivity
|
|||||||
|
|
||||||
coordinatorLayout = findViewById(R.id.toolbar_coordinator)
|
coordinatorLayout = findViewById(R.id.toolbar_coordinator)
|
||||||
toolbar = findViewById(R.id.toolbar)
|
toolbar = findViewById(R.id.toolbar)
|
||||||
|
|
||||||
|
if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty())
|
||||||
toolbar?.setTitle(R.string.settings)
|
toolbar?.setTitle(R.string.settings)
|
||||||
|
else
|
||||||
|
toolbar?.title = savedInstanceState?.getString(TITLE_KEY)
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
@@ -128,6 +116,22 @@ open class SettingsActivity
|
|||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
|
||||||
|
masterPasswordChecked: Boolean,
|
||||||
|
masterPassword: String?,
|
||||||
|
keyFileChecked: Boolean,
|
||||||
|
keyFile: Uri?) {
|
||||||
|
databaseUri?.let {
|
||||||
|
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
|
||||||
|
databaseUri,
|
||||||
|
masterPasswordChecked,
|
||||||
|
masterPassword,
|
||||||
|
keyFileChecked,
|
||||||
|
keyFile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean,
|
override fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean,
|
||||||
masterPassword: String?,
|
masterPassword: String?,
|
||||||
keyFileChecked: Boolean,
|
keyFileChecked: Boolean,
|
||||||
@@ -144,18 +148,12 @@ open class SettingsActivity
|
|||||||
keyFile
|
keyFile
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
PasswordEncodingDialogFragment().apply {
|
PasswordEncodingDialogFragment.getInstance(databaseUri,
|
||||||
positiveButtonClickListener = DialogInterface.OnClickListener { _, _ ->
|
|
||||||
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
|
|
||||||
databaseUri,
|
|
||||||
masterPasswordChecked,
|
masterPasswordChecked,
|
||||||
masterPassword,
|
masterPassword,
|
||||||
keyFileChecked,
|
keyFileChecked,
|
||||||
keyFile
|
keyFile
|
||||||
)
|
).show(supportFragmentManager, "passwordEncodingTag")
|
||||||
}
|
|
||||||
show(supportFragmentManager, "passwordEncodingTag")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,8 +162,7 @@ open class SettingsActivity
|
|||||||
override fun onAssignKeyDialogNegativeClick(masterPasswordChecked: Boolean,
|
override fun onAssignKeyDialogNegativeClick(masterPasswordChecked: Boolean,
|
||||||
masterPassword: String?,
|
masterPassword: String?,
|
||||||
keyFileChecked: Boolean,
|
keyFileChecked: Boolean,
|
||||||
keyFile: Uri?) {
|
keyFile: Uri?) {}
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideOrShowLockButton(key: NestedSettingsFragment.Screen) {
|
private fun hideOrShowLockButton(key: NestedSettingsFragment.Screen) {
|
||||||
if (PreferencesUtil.showLockDatabaseButton(this)) {
|
if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||||
@@ -220,5 +217,24 @@ open class SettingsActivity
|
|||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
outState.putBoolean(SHOW_LOCK, lockView?.visibility == View.VISIBLE)
|
outState.putBoolean(SHOW_LOCK, lockView?.visibility == View.VISIBLE)
|
||||||
|
outState.putString(TITLE_KEY, toolbar?.title?.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val SHOW_LOCK = "SHOW_LOCK"
|
||||||
|
private const val TITLE_KEY = "TITLE_KEY"
|
||||||
|
private const val TAG_NESTED = "TAG_NESTED"
|
||||||
|
|
||||||
|
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
|
||||||
|
val intent = Intent(activity, SettingsActivity::class.java)
|
||||||
|
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||||
|
intent.putExtra(TIMEOUT_ENABLE_KEY, timeoutEnable)
|
||||||
|
if (!timeoutEnable) {
|
||||||
|
activity.startActivity(intent)
|
||||||
|
} else if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
|
activity.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
mProgressDatabaseTaskProvider = null
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "DbSavePrefDialog"
|
private const val TAG = "DbSavePrefDialog"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.kunzisoft.keepass.model.EntryAttachmentState
|
|||||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_DOWNLOAD
|
||||||
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_FILE_START_UPLOAD
|
||||||
|
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService.Companion.ACTION_ATTACHMENT_REMOVE
|
||||||
|
|
||||||
class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
|||||||
AttachmentState.COMPLETE,
|
AttachmentState.COMPLETE,
|
||||||
AttachmentState.ERROR -> {
|
AttachmentState.ERROR -> {
|
||||||
// Finish the action when capture by activity
|
// Finish the action when capture by activity
|
||||||
consummeAttachmentAction(entryAttachmentState)
|
consumeAttachmentAction(entryAttachmentState)
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@@ -99,7 +100,7 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun consummeAttachmentAction(attachment: EntryAttachmentState) {
|
fun consumeAttachmentAction(attachment: EntryAttachmentState) {
|
||||||
mBinder?.getService()?.removeAttachmentAction(attachment)
|
mBinder?.getService()?.removeAttachmentAction(attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,4 +127,10 @@ class AttachmentFileBinderManager(private val activity: FragmentActivity) {
|
|||||||
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
|
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
|
||||||
}, ACTION_ATTACHMENT_FILE_START_DOWNLOAD)
|
}, ACTION_ATTACHMENT_FILE_START_DOWNLOAD)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeBinaryAttachment(attachment: Attachment) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(AttachmentFileNotificationService.ATTACHMENT_KEY, attachment)
|
||||||
|
}, ACTION_ATTACHMENT_REMOVE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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.LinearLayout
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||||
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
|
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
|
||||||
|
import com.kunzisoft.keepass.database.element.Attachment
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.element.Attachment
|
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.database.search.UuidUtil
|
import com.kunzisoft.keepass.database.search.UuidUtil
|
||||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||||
@@ -133,6 +134,17 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
|||||||
this.fontInVisibility = fontInVisibility
|
this.fontInVisibility = fontInVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun firstEntryFieldCopyView(): View? {
|
||||||
|
return when {
|
||||||
|
userNameFieldView.isVisible && userNameFieldView.copyButtonView.isVisible -> userNameFieldView.copyButtonView
|
||||||
|
passwordFieldView.isVisible && passwordFieldView.copyButtonView.isVisible -> passwordFieldView.copyButtonView
|
||||||
|
otpFieldView.isVisible && otpFieldView.copyButtonView.isVisible -> otpFieldView.copyButtonView
|
||||||
|
urlFieldView.isVisible && urlFieldView.copyButtonView.isVisible -> urlFieldView.copyButtonView
|
||||||
|
notesFieldView.isVisible && notesFieldView.copyButtonView.isVisible -> notesFieldView.copyButtonView
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun assignUserName(userName: String?,
|
fun assignUserName(userName: String?,
|
||||||
onClickListener: OnClickListener?) {
|
onClickListener: OnClickListener?) {
|
||||||
userNameFieldView.apply {
|
userNameFieldView.apply {
|
||||||
|
|||||||
@@ -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 labelView: TextView
|
||||||
private val valueView: TextView
|
private val valueView: TextView
|
||||||
private val showButtonView: ImageView
|
private val showButtonView: ImageView
|
||||||
private val copyButtonView: ImageView
|
val copyButtonView: ImageView
|
||||||
private var isProtected = false
|
private var isProtected = false
|
||||||
|
|
||||||
var hiddenProtectedValue: Boolean
|
var hiddenProtectedValue: Boolean
|
||||||
|
|||||||
@@ -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
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
<com.kunzisoft.keepass.view.EntryEditContentsView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/entry_edit_contents"
|
android:id="@+id/entry_edit_contents"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -209,14 +209,12 @@
|
|||||||
android:layout_marginBottom="@dimen/card_view_margin_bottom"
|
android:layout_marginBottom="@dimen/card_view_margin_bottom"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/entry_edit_container"
|
app:layout_constraintTop_toBottomOf="@+id/entry_edit_container"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/entry_attachments_container">
|
app:layout_constraintBottom_toTopOf="@+id/entry_attachments_container">
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<LinearLayout
|
||||||
android:id="@+id/extra_fields_list"
|
android:id="@+id/extra_fields_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:descendantFocusability="afterDescendants"
|
|
||||||
android:layout_margin="@dimen/card_view_padding"
|
android:layout_margin="@dimen/card_view_padding"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical" />
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
@@ -28,6 +28,29 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:targetApi="o">
|
tools:targetApi="o">
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal" >
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/credentials_information"
|
||||||
|
android:text="@string/assign_master_key"
|
||||||
|
style="@style/KeepassDXStyle.TextAppearance.Large"/>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/credentials_information"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:src="@drawable/ic_info_white_24dp"
|
||||||
|
style="@style/KeepassDXStyle.ImageButton.Simple"
|
||||||
|
android:contentDescription="@string/content_description_credentials_information"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:id="@+id/card_view_master_password"
|
android:id="@+id/card_view_master_password"
|
||||||
android:layout_margin="4dp"
|
android:layout_margin="4dp"
|
||||||
|
|||||||
@@ -27,10 +27,21 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/item_attachment_broken"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:src="@drawable/ic_attach_file_broken_white_24dp"
|
||||||
|
android:contentDescription="@string/entry_attachments" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/item_attachment_title"
|
android:id="@+id/item_attachment_title"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/item_attachment_broken"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
|
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -105,6 +116,7 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
style="@style/KeepassDXStyle.ProgressBar.Circle"
|
style="@style/KeepassDXStyle.ProgressBar.Circle"
|
||||||
android:layout_width="36dp"
|
android:layout_width="36dp"
|
||||||
android:layout_height="36dp"
|
android:layout_height="36dp"
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_edit">
|
app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_edit">
|
||||||
|
|
||||||
<com.kunzisoft.keepass.view.EditTextSelectable
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/entry_extra_field_value"
|
android:id="@+id/entry_extra_field_value"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="@dimen/card_view_padding"
|
android:layout_margin="@dimen/card_view_padding"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical" />
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
<string name="list_password_generator_options_summary">Défini les caractères autorisés du générateur de mot de passe</string>
|
<string name="list_password_generator_options_summary">Défini les caractères autorisés du générateur de mot de passe</string>
|
||||||
<string name="clipboard">Presse-papier</string>
|
<string name="clipboard">Presse-papier</string>
|
||||||
<string name="clipboard_notifications_title">Notifications du presse-papier</string>
|
<string name="clipboard_notifications_title">Notifications du presse-papier</string>
|
||||||
<string name="clipboard_notifications_summary">Active les notifications du presse-papier pour copier les champs lors de 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="clipboard_warning">Si la suppression automatique du presse-papier échoue, supprimer son historique manuellement.</string>
|
||||||
<string name="lock">Verrouiller</string>
|
<string name="lock">Verrouiller</string>
|
||||||
<string name="lock_database_screen_off_title">Verrouillage d’écran</string>
|
<string name="lock_database_screen_off_title">Verrouillage d’écran</string>
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
<string name="biometric_delete_all_key_summary">Supprime toutes les clés de chiffrement liées à la reconnaissance biométrique</string>
|
<string name="biometric_delete_all_key_summary">Supprime toutes les clés de chiffrement liées à la reconnaissance biométrique</string>
|
||||||
<string name="biometric_delete_all_key_warning">Supprimer toutes les clés de chiffrement liées à la reconnaissance biométrique \?</string>
|
<string name="biometric_delete_all_key_warning">Supprimer toutes les clés de chiffrement liées à la reconnaissance biométrique \?</string>
|
||||||
<string name="unavailable_feature_text">Impossible de démarrer cette fonctionnalité.</string>
|
<string name="unavailable_feature_text">Impossible de démarrer cette fonctionnalité.</string>
|
||||||
<string name="unavailable_feature_version">Votre version 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="unavailable_feature_hardware">Impossible de trouver le matériel correspondant.</string>
|
||||||
<string name="file_name">Nom de fichier</string>
|
<string name="file_name">Nom de fichier</string>
|
||||||
<string name="path">Chemin d’accès</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>
|
\nLes groupes (≈dossiers) organisent les entrées dans votre base de données.</string>
|
||||||
<string name="education_search_title">Rechercher dans les entrées</string>
|
<string name="education_search_title">Rechercher dans les entrées</string>
|
||||||
<string name="education_search_summary">Saisir le titre, le nom d’utilisateur ou le contenu des autres champs pour récupérer vos mots de passe.</string>
|
<string name="education_search_summary">Saisir le titre, le nom 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_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_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>
|
<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_paste">Coller</string>
|
||||||
<string name="menu_cancel">Annuler</string>
|
<string name="menu_cancel">Annuler</string>
|
||||||
<string name="allow_no_password_title">Autoriser l\'absence de clé principale</string>
|
<string name="allow_no_password_title">Autoriser l\'absence de clé principale</string>
|
||||||
<string name="allow_no_password_summary">Active le bouton « Ouvrir » si aucun identifiant 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_file_selection_read_only">Protéger en écriture</string>
|
||||||
<string name="menu_open_file_read_and_write">Modifiable</string>
|
<string name="menu_open_file_read_and_write">Modifiable</string>
|
||||||
<string name="enable_read_only_title">Protégé en écriture</string>
|
<string name="enable_read_only_title">Protégé en écriture</string>
|
||||||
@@ -454,24 +454,24 @@
|
|||||||
<string name="download_initialization">Initialisation…</string>
|
<string name="download_initialization">Initialisation…</string>
|
||||||
<string name="download_progression">En cours : %1$d%%</string>
|
<string name="download_progression">En cours : %1$d%%</string>
|
||||||
<string name="download_finalization">Finalisation…</string>
|
<string name="download_finalization">Finalisation…</string>
|
||||||
<string name="download_complete">Terminé !</string>
|
<string name="download_complete">Terminé !</string>
|
||||||
<string name="hide_expired_entries_title">Masquer les entrées expirées</string>
|
<string name="hide_expired_entries_title">Masquer les entrées expirées</string>
|
||||||
<string name="hide_expired_entries_summary">Les entrées expirées sont cachées</string>
|
<string name="hide_expired_entries_summary">Les entrées expirées ne sont pas affichées</string>
|
||||||
<string name="contact">Contact</string>
|
<string name="contact">Contact</string>
|
||||||
<string name="contribution">Contribution</string>
|
<string name="contribution">Contribution</string>
|
||||||
<string name="html_about_contribution">Afin de <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="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_title">Recherche rapide</string>
|
||||||
<string name="auto_focus_search_summary">Demander une recherche lors de l\'ouverture d\'une base de données</string>
|
<string name="auto_focus_search_summary">Demander une recherche lors de l\'ouverture d\'une base de données</string>
|
||||||
<string name="remember_database_locations_title">Enregistrer l\'emplacement des bases de données</string>
|
<string name="remember_database_locations_title">Mémoriser 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_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">Enregistrer l\'emplacement des fichiers clés</string>
|
<string name="remember_keyfile_locations_title">Mémoriser les emplacements des fichiers clé</string>
|
||||||
<string name="remember_keyfile_locations_summary">Se souvenir de l\'emplacement des fichiers de clés des bases de données</string>
|
<string name="remember_keyfile_locations_summary">Garde en mémoire 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_title">Afficher les fichiers récents</string>
|
||||||
<string name="show_recent_files_summary">Afficher les emplacements des bases de données récentes</string>
|
<string name="show_recent_files_summary">Afficher les emplacements des bases de données récentes</string>
|
||||||
<string name="hide_broken_locations_title">Masquer les liens rompus de base de données</string>
|
<string name="hide_broken_locations_title">Masquer les liens rompus de base de données</string>
|
||||||
<string name="hide_broken_locations_summary">Masquer les liens rompus dans la liste des bases de données récentes</string>
|
<string name="hide_broken_locations_summary">Masquer les liens rompus dans la liste des bases de données récentes</string>
|
||||||
<string name="warning_database_read_only">Accorder un accès en écriture au fichier pour enregistrer les modifications de la base de données</string>
|
<string name="warning_database_read_only">Accorder un accès en écriture au fichier pour enregistrer les modifications de la base de données</string>
|
||||||
<string name="education_setup_OTP_summary">Définir le mot de passe à usage unique (temporel ou évènementiel) pour générer un jeton demandé par 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="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="error_create_database">Impossible de créer le fichier de base de données.</string>
|
||||||
<string name="entry_add_attachment">Ajouter une pièce jointe</string>
|
<string name="entry_add_attachment">Ajouter une pièce jointe</string>
|
||||||
@@ -498,4 +498,12 @@
|
|||||||
<string name="subdomain_search_title">Recherche de sous-domaine</string>
|
<string name="subdomain_search_title">Recherche de sous-domaine</string>
|
||||||
<string name="error_string_type">Ce texte ne correspond pas à l\'élément demandé.</string>
|
<string name="error_string_type">Ce texte ne correspond pas à l\'élément demandé.</string>
|
||||||
<string name="content_description_add_item">Ajouter un élément</string>
|
<string name="content_description_add_item">Ajouter un élément</string>
|
||||||
|
<string name="upload_attachment">Téléverser %1$s</string>
|
||||||
|
<string name="education_add_attachment_summary">Téléverse une pièce-jointe à votre entrée pour enregistrer 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>
|
</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_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka</string>
|
||||||
<string name="keyboard_previous_database_credentials_title">Ekran za unos podataka za prijavu u bazu podataka</string>
|
<string name="keyboard_previous_database_credentials_title">Ekran za unos podataka za prijavu u bazu podataka</string>
|
||||||
<string name="keyboard_change">Promijeni tipkovnicu</string>
|
<string name="keyboard_change">Promijeni tipkovnicu</string>
|
||||||
|
<string name="warning_file_too_big">Baza podataka za KeePass trebala bi sadržavati samo male datoteke uslužnih programa (poput PGP datoteke ključeva).
|
||||||
|
\n
|
||||||
|
\nOvaj prijenos će možda neizmjerno povećati tvoju baza podataka i smanjiti performansu.</string>
|
||||||
|
<string name="upload_attachment">Prenesi %1$s</string>
|
||||||
|
<string name="education_add_attachment_summary">Za spremanje važnih vanjskih podataka, prenesi privitak u tvoj unos.</string>
|
||||||
|
<string name="education_add_attachment_title">Dodaj privitak</string>
|
||||||
|
<string name="warning_sure_add_file">Svejedno dodati datoteku\?</string>
|
||||||
|
<string name="warning_replace_file">Prijenosom ove datoteke zamijenit će se postojeća.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -176,4 +176,11 @@
|
|||||||
<string name="feedback">Umpan Balik</string>
|
<string name="feedback">Umpan Balik</string>
|
||||||
<string name="contribution">Kontribusi</string>
|
<string name="contribution">Kontribusi</string>
|
||||||
<string name="contact">Kontak</string>
|
<string name="contact">Kontak</string>
|
||||||
|
<string name="auto_focus_search_summary">Minta pencarian saat membuka database</string>
|
||||||
|
<string name="auto_focus_search_title">Pencarian cepat</string>
|
||||||
|
<string name="omit_backup_search_summary">Menghilangkan grup \"Cadangan\" dan \"Tempat sampah\" dari hasil penelusuran</string>
|
||||||
|
<string name="omit_backup_search_title">Jangan mencari melalui entri cadangan</string>
|
||||||
|
<string name="create_keepass_file">Buat basisdata baru</string>
|
||||||
|
<string name="select_database_file">Buka basisdata yang sudah ada</string>
|
||||||
|
<string name="no_url_handler">Pasang browser web untuk membuka URL ini.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -39,17 +39,17 @@
|
|||||||
<string name="decrypting_db">データベースの内容を復号しています…</string>
|
<string name="decrypting_db">データベースの内容を復号しています…</string>
|
||||||
<string name="default_checkbox">デフォルトのデータベースとして使用</string>
|
<string name="default_checkbox">デフォルトのデータベースとして使用</string>
|
||||||
<string name="digits">数字</string>
|
<string name="digits">数字</string>
|
||||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft は<strong>オープンソース</strong>/<strong>広告なし</strong>です。
|
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft は<strong>オープンソース</strong>かつ<strong>広告なし</strong>です。
|
||||||
\nそのままの状態で、<strong>GPLv3</strong> ライセンスの下、いかなる保証もなく提供されます。</string>
|
\nそのままの状態で、<strong>GPLv3</strong> ライセンスの下、いかなる保証もなく提供されます。</string>
|
||||||
<string name="select_database_file">既存のデータベースを開く</string>
|
<string name="select_database_file">既存のデータベースを開く</string>
|
||||||
<string name="entry_accessed">アクセス日時</string>
|
<string name="entry_accessed">最終アクセス</string>
|
||||||
<string name="entry_cancel">キャンセル</string>
|
<string name="entry_cancel">キャンセル</string>
|
||||||
<string name="entry_notes">備考</string>
|
<string name="entry_notes">備考</string>
|
||||||
<string name="entry_confpassword">パスワードを確認</string>
|
<string name="entry_confpassword">パスワードを確認</string>
|
||||||
<string name="entry_created">作成日時</string>
|
<string name="entry_created">作成日</string>
|
||||||
<string name="entry_expires">有効期限</string>
|
<string name="entry_expires">有効期限</string>
|
||||||
<string name="entry_keyfile">キーファイル</string>
|
<string name="entry_keyfile">キーファイル</string>
|
||||||
<string name="entry_modified">変更日時</string>
|
<string name="entry_modified">変更日</string>
|
||||||
<string name="entry_password">パスワード</string>
|
<string name="entry_password">パスワード</string>
|
||||||
<string name="entry_save">保存</string>
|
<string name="entry_save">保存</string>
|
||||||
<string name="entry_title">タイトル</string>
|
<string name="entry_title">タイトル</string>
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
<string name="error_out_of_memory">データベース全体を読み込むメモリがありません。</string>
|
<string name="error_out_of_memory">データベース全体を読み込むメモリがありません。</string>
|
||||||
<string name="error_pass_gen_type">少なくとも 1 つのパスワード生成タイプを選択する必要があります。</string>
|
<string name="error_pass_gen_type">少なくとも 1 つのパスワード生成タイプを選択する必要があります。</string>
|
||||||
<string name="error_pass_match">パスワードが一致しません。</string>
|
<string name="error_pass_match">パスワードが一致しません。</string>
|
||||||
<string name="error_rounds_too_large">「変換ラウンド」が多すぎます。2147483648 に設定します。</string>
|
<string name="error_rounds_too_large">[変換ラウンド] が多すぎます。2147483648 に設定します。</string>
|
||||||
<string name="error_wrong_length">「長さ」フィールドには正の整数を入力してください。</string>
|
<string name="error_wrong_length">[長さ] フィールドには正の整数を入力してください。</string>
|
||||||
<string name="file_browser">ファイル マネージャー</string>
|
<string name="file_browser">ファイル マネージャー</string>
|
||||||
<string name="generate_password">パスワードを生成</string>
|
<string name="generate_password">パスワードを生成</string>
|
||||||
<string name="hint_conf_pass">パスワードを確認</string>
|
<string name="hint_conf_pass">パスワードを確認</string>
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
<string name="no_results">検索結果なし</string>
|
<string name="no_results">検索結果なし</string>
|
||||||
<string name="no_url_handler">この URL を開くにはウェブブラウザをインストールしてください。</string>
|
<string name="no_url_handler">この URL を開くにはウェブブラウザをインストールしてください。</string>
|
||||||
<string name="omit_backup_search_title">バックアップ エントリーを検索しない</string>
|
<string name="omit_backup_search_title">バックアップ エントリーを検索しない</string>
|
||||||
<string name="omit_backup_search_summary">検索結果から「バックアップ」と「ごみ箱」のグループを省きます</string>
|
<string name="omit_backup_search_summary">検索結果から [バックアップ] と [ゴミ箱] のグループを省きます</string>
|
||||||
<string name="progress_create">新しいデータベースを作成しています…</string>
|
<string name="progress_create">新しいデータベースを作成しています…</string>
|
||||||
<string name="progress_title">実行しています…</string>
|
<string name="progress_title">実行しています…</string>
|
||||||
<string name="content_description_remove_from_list">削除</string>
|
<string name="content_description_remove_from_list">削除</string>
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
<string name="clipboard_error_clear">クリップボードを消去できませんでした</string>
|
<string name="clipboard_error_clear">クリップボードを消去できませんでした</string>
|
||||||
<string name="entry_not_found">エントリーのデータが見つかりませんでした。</string>
|
<string name="entry_not_found">エントリーのデータが見つかりませんでした。</string>
|
||||||
<string name="error_load_database">データベースを読み込めませんでした。</string>
|
<string name="error_load_database">データベースを読み込めませんでした。</string>
|
||||||
<string name="error_load_database_KDF_memory">鍵を読み込めませんでした。鍵導出関数の「メモリ使用量」を下げてみてください。</string>
|
<string name="error_load_database_KDF_memory">鍵を読み込めませんでした。鍵導出関数の [メモリ使用量] を下げてみてください。</string>
|
||||||
<string name="error_string_key">各文字列にはフィールド名が必要です。</string>
|
<string name="error_string_key">各文字列にはフィールド名が必要です。</string>
|
||||||
<string name="error_autofill_enable_service">自動入力サービスを有効にできませんでした。</string>
|
<string name="error_autofill_enable_service">自動入力サービスを有効にできませんでした。</string>
|
||||||
<string name="error_move_folder_in_itself">グループを自分自身の中に移動することはできません。</string>
|
<string name="error_move_folder_in_itself">グループを自分自身の中に移動することはできません。</string>
|
||||||
@@ -177,13 +177,13 @@
|
|||||||
<string name="read_only_warning">ファイル マネージャーによっては、KeePassDX によるストレージへの書き込みを許可していない場合があります。</string>
|
<string name="read_only_warning">ファイル マネージャーによっては、KeePassDX によるストレージへの書き込みを許可していない場合があります。</string>
|
||||||
<string name="encryption_explanation">すべてのデータで使用するデータベース暗号化アルゴリズムです。</string>
|
<string name="encryption_explanation">すべてのデータで使用するデータベース暗号化アルゴリズムです。</string>
|
||||||
<string name="sort_menu">並び替え</string>
|
<string name="sort_menu">並び替え</string>
|
||||||
<string name="sort_ascending">低い順 ↓</string>
|
<string name="sort_ascending">昇順 ↓</string>
|
||||||
<string name="sort_recycle_bin_bottom">ごみ箱を一番下にする</string>
|
<string name="sort_recycle_bin_bottom">ゴミ箱を一番下にする</string>
|
||||||
<string name="sort_title">タイトル</string>
|
<string name="sort_title">タイトル</string>
|
||||||
<string name="sort_username">ユーザー名</string>
|
<string name="sort_username">ユーザー名</string>
|
||||||
<string name="sort_creation_time">作成日</string>
|
<string name="sort_creation_time">作成日</string>
|
||||||
<string name="sort_last_modify_time">変更日</string>
|
<string name="sort_last_modify_time">変更日</string>
|
||||||
<string name="sort_last_access_time">アクセス日</string>
|
<string name="sort_last_access_time">最終アクセス</string>
|
||||||
<string name="warning">警告</string>
|
<string name="warning">警告</string>
|
||||||
<string name="content_description_open_file">ファイルを開く</string>
|
<string name="content_description_open_file">ファイルを開く</string>
|
||||||
<string name="content_description_add_entry">エントリーを追加</string>
|
<string name="content_description_add_entry">エントリーを追加</string>
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
<string name="content_description_remove_field">フィールドを削除</string>
|
<string name="content_description_remove_field">フィールドを削除</string>
|
||||||
<string name="error_move_entry_here">ここではエントリーを移動することはできません。</string>
|
<string name="error_move_entry_here">ここではエントリーを移動することはできません。</string>
|
||||||
<string name="error_copy_entry_here">ここではエントリーをコピーすることはできません。</string>
|
<string name="error_copy_entry_here">ここではエントリーをコピーすることはできません。</string>
|
||||||
<string name="configure_biometric">生体認証プロンプト対応端末ですが未設定です。</string>
|
<string name="configure_biometric">生体認証プロンプト対応ですが、未設定です。</string>
|
||||||
<string name="master_key">マスターキー</string>
|
<string name="master_key">マスターキー</string>
|
||||||
<string name="entry_history">履歴</string>
|
<string name="entry_history">履歴</string>
|
||||||
<string name="otp_type">OTP の種類</string>
|
<string name="otp_type">OTP の種類</string>
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
<string name="menu_master_key_settings">マスターキーの設定</string>
|
<string name="menu_master_key_settings">マスターキーの設定</string>
|
||||||
<string name="error_save_database">データベースを保存できませんでした。</string>
|
<string name="error_save_database">データベースを保存できませんでした。</string>
|
||||||
<string name="menu_save_database">データベースを保存</string>
|
<string name="menu_save_database">データベースを保存</string>
|
||||||
<string name="menu_empty_recycle_bin">ごみ箱を空にする</string>
|
<string name="menu_empty_recycle_bin">ゴミ箱を空にする</string>
|
||||||
<string name="command_execution">コマンドを実行しています…</string>
|
<string name="command_execution">コマンドを実行しています…</string>
|
||||||
<string name="warning_permanently_delete_nodes">選択したノードを完全に削除しますか?</string>
|
<string name="warning_permanently_delete_nodes">選択したノードを完全に削除しますか?</string>
|
||||||
<string name="auto_focus_search_summary">データベースを開いたとき、検索を促します</string>
|
<string name="auto_focus_search_summary">データベースを開いたとき、検索を促します</string>
|
||||||
@@ -255,20 +255,20 @@
|
|||||||
<string name="parallelism">並列処理</string>
|
<string name="parallelism">並列処理</string>
|
||||||
<string name="memory_usage_explanation">鍵導出関数が使用するメモリの量(バイト単位)です。</string>
|
<string name="memory_usage_explanation">鍵導出関数が使用するメモリの量(バイト単位)です。</string>
|
||||||
<string name="memory_usage">メモリ使用量</string>
|
<string name="memory_usage">メモリ使用量</string>
|
||||||
<string name="kdf_explanation">暗号化アルゴリズム用の鍵を生成するために、マスターキーはランダムにソルト化された鍵導出関数を使用して変換されます。</string>
|
<string name="kdf_explanation">暗号化アルゴリズム用の鍵を生成するために、マスターキーはランダムなソルトを加える鍵導出関数を使用して変換されます。</string>
|
||||||
<string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string>
|
<string name="hide_broken_locations_summary">最近使ったデータベースの一覧で、壊れたリンクを非表示にします</string>
|
||||||
<string name="hide_broken_locations_title">データベースへの壊れたリンクを非表示にする</string>
|
<string name="hide_broken_locations_title">データベースへの壊れたリンクを非表示にする</string>
|
||||||
<string name="show_recent_files_summary">最近使ったデータベースの場所を表示します</string>
|
<string name="show_recent_files_summary">最近使ったデータベースの場所を表示します</string>
|
||||||
<string name="remember_keyfile_locations_summary">キーファイルの保存先を追跡します</string>
|
<string name="remember_keyfile_locations_summary">キーファイルの保存先を保持します</string>
|
||||||
<string name="remember_keyfile_locations_title">キーファイルの場所を記憶</string>
|
<string name="remember_keyfile_locations_title">キーファイルの場所を記憶</string>
|
||||||
<string name="remember_database_locations_summary">データベースの保存先を追跡します</string>
|
<string name="remember_database_locations_summary">データベースの保存先を保持します</string>
|
||||||
<string name="remember_database_locations_title">データベースの場所を記憶</string>
|
<string name="remember_database_locations_title">データベースの場所を記憶</string>
|
||||||
<string name="selection_mode">選択モード</string>
|
<string name="selection_mode">選択モード</string>
|
||||||
<string name="contains_duplicate_uuid_procedure">重複したエントリーに対する新しい UUID を生成して、問題を解決し続行しますか?</string>
|
<string name="contains_duplicate_uuid_procedure">重複したエントリーに対する新しい UUID を生成して、問題を解決し続行しますか?</string>
|
||||||
<string name="contains_duplicate_uuid">データベースには重複する UUID が含まれています。</string>
|
<string name="contains_duplicate_uuid">データベースには重複する UUID が含まれています。</string>
|
||||||
<string name="subdomain_search_title">サブドメイン検索</string>
|
<string name="subdomain_search_title">サブドメイン検索</string>
|
||||||
<string name="menu_advanced_unlock_settings">高度なロック解除</string>
|
<string name="menu_advanced_unlock_settings">高度なロック解除</string>
|
||||||
<string name="invalid_db_same_uuid">同じ UUID %2$s を持つ %1$s が既に存在します。</string>
|
<string name="invalid_db_same_uuid">同じ UUID %2$s を持つ %1$s がすでに存在します。</string>
|
||||||
<string name="error_string_type">このテキストは指定された項目と整合しません。</string>
|
<string name="error_string_type">このテキストは指定された項目と整合しません。</string>
|
||||||
<string name="error_otp_counter">カウンターは %1$d から %2$d の間でなければなりません。</string>
|
<string name="error_otp_counter">カウンターは %1$d から %2$d の間でなければなりません。</string>
|
||||||
<string name="otp_counter">カウンター</string>
|
<string name="otp_counter">カウンター</string>
|
||||||
@@ -294,7 +294,7 @@
|
|||||||
<string name="biometric_auto_open_prompt_title">生体認証プロンプトを自動で開く</string>
|
<string name="biometric_auto_open_prompt_title">生体認証プロンプトを自動で開く</string>
|
||||||
<string name="biometric_delete_all_key_warning">生体認証に関するすべての暗号鍵を削除しますか?</string>
|
<string name="biometric_delete_all_key_warning">生体認証に関するすべての暗号鍵を削除しますか?</string>
|
||||||
<string name="biometric_delete_all_key_summary">生体認証に関するすべての暗号鍵を削除します</string>
|
<string name="biometric_delete_all_key_summary">生体認証に関するすべての暗号鍵を削除します</string>
|
||||||
<string name="advanced_unlock_explanation_summary">高度なロック解除を使用すると、データベースがより簡単に開きます</string>
|
<string name="advanced_unlock_explanation_summary">高度なロック解除を使用して、データベースをより簡単に開きます</string>
|
||||||
<string name="education_lock_summary">データベースをすばやくロックします。時間が経ったり画面がオフになったときロックするよう、アプリを設定することもできます。</string>
|
<string name="education_lock_summary">データベースをすばやくロックします。時間が経ったり画面がオフになったときロックするよう、アプリを設定することもできます。</string>
|
||||||
<string name="education_lock_title">データベースをロック</string>
|
<string name="education_lock_title">データベースをロック</string>
|
||||||
<string name="education_sort_summary">エントリーとグループの並べ替え方法を選択します。</string>
|
<string name="education_sort_summary">エントリーとグループの並べ替え方法を選択します。</string>
|
||||||
@@ -307,7 +307,7 @@
|
|||||||
<string name="education_sort_title">項目の並び替え</string>
|
<string name="education_sort_title">項目の並び替え</string>
|
||||||
<string name="education_new_node_title">データベースに項目を追加</string>
|
<string name="education_new_node_title">データベースに項目を追加</string>
|
||||||
<string name="assign_master_key">マスターキーを設定</string>
|
<string name="assign_master_key">マスターキーを設定</string>
|
||||||
<string name="education_select_database_summary">ファイル ブラウザからこれまで使ってきたデータベース ファイルを開いて、引き続き使用します。</string>
|
<string name="education_select_database_summary">これまで使ってきたデータベース ファイルを、ファイルブラウザから開いて引き続き使用します。</string>
|
||||||
<string name="education_select_database_title">既存のデータペースを開く</string>
|
<string name="education_select_database_title">既存のデータペースを開く</string>
|
||||||
<string name="list_password_generator_options_title">パスワードの文字種</string>
|
<string name="list_password_generator_options_title">パスワードの文字種</string>
|
||||||
<string name="build_label">Build %1$s</string>
|
<string name="build_label">Build %1$s</string>
|
||||||
@@ -318,7 +318,7 @@
|
|||||||
<string name="enable_read_only_title">書き込み禁止</string>
|
<string name="enable_read_only_title">書き込み禁止</string>
|
||||||
<string name="delete_entered_password_summary">入力されたパスワードをデータベースへの接続試行後に削除します</string>
|
<string name="delete_entered_password_summary">入力されたパスワードをデータベースへの接続試行後に削除します</string>
|
||||||
<string name="delete_entered_password_title">パスワードを削除</string>
|
<string name="delete_entered_password_title">パスワードを削除</string>
|
||||||
<string name="allow_no_password_summary">認証情報が選択されていない場合でも、「開く」ボタンのタップを許可します</string>
|
<string name="allow_no_password_summary">認証情報が選択されていない場合でも、[開く] ボタンのタップを許可します</string>
|
||||||
<string name="autofill_block_restart">ブロッキングを有効にするには、そのフォームを含むアプリを再起動します。</string>
|
<string name="autofill_block_restart">ブロッキングを有効にするには、そのフォームを含むアプリを再起動します。</string>
|
||||||
<string name="autofill_block">自動入力をブロック</string>
|
<string name="autofill_block">自動入力をブロック</string>
|
||||||
<string name="autofill_web_domain_blocklist_title">ウェブドメインのブロックリスト</string>
|
<string name="autofill_web_domain_blocklist_title">ウェブドメインのブロックリスト</string>
|
||||||
@@ -336,7 +336,7 @@
|
|||||||
<string name="settings_database_recommend_changing_master_key_summary">マスターキーの変更を推奨します(日数)</string>
|
<string name="settings_database_recommend_changing_master_key_summary">マスターキーの変更を推奨します(日数)</string>
|
||||||
<string name="settings_database_recommend_changing_master_key_title">更新を推奨</string>
|
<string name="settings_database_recommend_changing_master_key_title">更新を推奨</string>
|
||||||
<string name="max_history_size_summary">エントリーあたりの履歴のサイズ(バイト単位)を制限します</string>
|
<string name="max_history_size_summary">エントリーあたりの履歴のサイズ(バイト単位)を制限します</string>
|
||||||
<string name="lock_database_back_root_title">「戻る」を押してロック</string>
|
<string name="lock_database_back_root_title">[戻る] を押してロック</string>
|
||||||
<string name="lock_database_screen_off_summary">画面がオフのとき、データベースをロックします</string>
|
<string name="lock_database_screen_off_summary">画面がオフのとき、データベースをロックします</string>
|
||||||
<string name="lock_database_screen_off_title">画面ロック</string>
|
<string name="lock_database_screen_off_title">画面ロック</string>
|
||||||
<string name="lock">ロック</string>
|
<string name="lock">ロック</string>
|
||||||
@@ -350,14 +350,14 @@
|
|||||||
<string name="autofill_preference_title">自動入力の設定</string>
|
<string name="autofill_preference_title">自動入力の設定</string>
|
||||||
<string name="set_autofill_service_title">デフォルトの自動入力サービスに設定</string>
|
<string name="set_autofill_service_title">デフォルトの自動入力サービスに設定</string>
|
||||||
<string name="autofill_explanation_summary">自動入力を有効にして、他のアプリ内のフォームにすばやく入力します</string>
|
<string name="autofill_explanation_summary">自動入力を有効にして、他のアプリ内のフォームにすばやく入力します</string>
|
||||||
<string name="autofill_sign_in_prompt">KeePassDX でサインイン</string>
|
<string name="autofill_sign_in_prompt">KeePassDX でログイン</string>
|
||||||
<string name="autofill_service_name">KeePassDX フォーム自動入力</string>
|
<string name="autofill_service_name">KeePassDX フォーム自動入力</string>
|
||||||
<string name="autofill">自動入力</string>
|
<string name="autofill">自動入力</string>
|
||||||
<string name="general">全般</string>
|
<string name="general">全般</string>
|
||||||
<string name="biometric">生体認証</string>
|
<string name="biometric">生体認証</string>
|
||||||
<string name="menu_appearance_settings">外観</string>
|
<string name="menu_appearance_settings">デザイン</string>
|
||||||
<string name="database_history">履歴</string>
|
<string name="database_history">履歴</string>
|
||||||
<string name="credential_before_click_biometric_button">パスワードを入力し、「生体認証」ボタンをタップします。</string>
|
<string name="credential_before_click_biometric_button">パスワードを入力し、[生体認証] ボタンをタップします。</string>
|
||||||
<string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string>
|
<string name="no_credentials_stored">データベースの保存済み認証情報はありません。</string>
|
||||||
<string name="biometric_scanning_error">生体認証エラー:%1$s</string>
|
<string name="biometric_scanning_error">生体認証エラー:%1$s</string>
|
||||||
<string name="biometric_not_recognized">生体情報を認識できませんでした</string>
|
<string name="biometric_not_recognized">生体情報を認識できませんでした</string>
|
||||||
@@ -370,7 +370,7 @@
|
|||||||
<string name="keyboard_notification_entry_clear_close_title">データベースを閉じて消去</string>
|
<string name="keyboard_notification_entry_clear_close_title">データベースを閉じて消去</string>
|
||||||
<string name="clear_clipboard_notification_title">データベースを閉じて消去</string>
|
<string name="clear_clipboard_notification_title">データベースを閉じて消去</string>
|
||||||
<string name="keyboard_notification_entry_clear_close_summary">通知を閉じるとき、データベースも閉じます</string>
|
<string name="keyboard_notification_entry_clear_close_summary">通知を閉じるとき、データベースも閉じます</string>
|
||||||
<string name="clear_clipboard_notification_summary">クリップボードでの保存期間が終了したとき、またはこの機能の使用中に通知が閉じられたとき、データベースをロックします</string>
|
<string name="clear_clipboard_notification_summary">クリップボードでの保存期間が終了したとき、または使用開始後に通知が閉じられたとき、データベースをロックします</string>
|
||||||
<string name="html_text_dev_feature_encourage">あなたの意見によって、デベロッパーは<strong>新機能</strong>の開発や<strong>バグの修正</strong>を速く進めることができます。</string>
|
<string name="html_text_dev_feature_encourage">あなたの意見によって、デベロッパーは<strong>新機能</strong>の開発や<strong>バグの修正</strong>を速く進めることができます。</string>
|
||||||
<string name="reset_education_screens_summary">すべての教育的な情報をもう一度表示します</string>
|
<string name="reset_education_screens_summary">すべての教育的な情報をもう一度表示します</string>
|
||||||
<string name="html_text_dev_feature">この機能は<strong>開発中</strong>であり、早期に提供するにはあなたの<strong>貢献</strong>が必要です。</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_previous_fill_in_title">自動キーアクション</string>
|
||||||
<string name="keyboard_key_sound_title">キー操作音</string>
|
<string name="keyboard_key_sound_title">キー操作音</string>
|
||||||
<string name="keyboard_key_vibrate_title">キー操作バイブレーション</string>
|
<string name="keyboard_key_vibrate_title">キー操作バイブレーション</string>
|
||||||
<string name="keyboard_auto_go_action_summary">「フィールド」 キーを押した後に「移動」キーアクションを行います</string>
|
<string name="keyboard_auto_go_action_summary">[フィールド] キーを押した後に [移動] キーアクションを行います</string>
|
||||||
<string name="keyboard_auto_go_action_title">自動キーアクション</string>
|
<string name="keyboard_auto_go_action_title">自動キーアクション</string>
|
||||||
<string name="keyboard_notification_entry_content_title">%1$s が Magikeyboard で利用可能</string>
|
<string name="keyboard_notification_entry_content_title">%1$s が Magikeyboard で利用可能</string>
|
||||||
<string name="keyboard_entry_timeout_summary">キーボードのエントリーを消去するまでの時間</string>
|
<string name="keyboard_entry_timeout_summary">キーボードのエントリーを消去するまでの時間</string>
|
||||||
<string name="keyboard_search_share_title">共有から送られた情報の検索</string>
|
<string name="keyboard_search_share_title">共有情報の検索</string>
|
||||||
<string name="keyboard_search_share_summary">共有から送られた情報を自動的に検索して、結果をキーボードに格納します</string>
|
<string name="keyboard_search_share_summary">共有情報を自動的に検索して、結果をキーボードに格納します</string>
|
||||||
<string name="keyboard_notification_entry_summary">エントリーが利用可能なとき、通知を表示します</string>
|
<string name="keyboard_notification_entry_summary">エントリーが利用可能なとき、通知を表示します</string>
|
||||||
<string name="device_keyboard_setting_title">デバイス キーボードの設定</string>
|
<string name="device_keyboard_setting_title">デバイス キーボードの設定</string>
|
||||||
<string name="settings_database_force_changing_master_key_next_time_summary">次回マスターキーの変更を必須にします(1 回のみ)</string>
|
<string name="settings_database_force_changing_master_key_next_time_summary">次回マスターキーの変更を必須にします(1 回のみ)</string>
|
||||||
<string name="recycle_bin_summary">グループとエントリーを削除する前に「ごみ箱」グループに移動します</string>
|
<string name="recycle_bin_summary">グループとエントリーを削除する前に [ゴミ箱] グループに移動します</string>
|
||||||
<string name="keyboard_selection_entry_summary">エントリーを開いているとき、Magikeyboard に入力フィールドを表示します</string>
|
<string name="keyboard_selection_entry_summary">エントリーを開いているとき、Magikeyboard に入力フィールドを表示します</string>
|
||||||
<string name="lock_database_show_button_title">ロックボタンを表示</string>
|
<string name="lock_database_show_button_title">ロックボタンを表示</string>
|
||||||
<string name="download_finalization">終了しています…</string>
|
<string name="download_finalization">終了しています…</string>
|
||||||
@@ -401,7 +401,7 @@
|
|||||||
<string name="education_field_copy_summary">コピーしたフィールドはどこにでも貼り付けることができます。
|
<string name="education_field_copy_summary">コピーしたフィールドはどこにでも貼り付けることができます。
|
||||||
\n
|
\n
|
||||||
\nお好みのフォーム入力方法を使用してください。</string>
|
\nお好みのフォーム入力方法を使用してください。</string>
|
||||||
<string name="warning_database_link_revoked">ファイル マネージャーによって取り消されたファイルにアクセスします</string>
|
<string name="warning_database_link_revoked">ファイルへのアクセス権がファイル マネージャーによって取り消されました</string>
|
||||||
<string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string>
|
<string name="clipboard_explanation_summary">デバイスのクリップボードを使用して、エントリーのフィールドをコピーします</string>
|
||||||
<string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string>
|
<string name="html_text_dev_feature_work_hard">この機能をすばやくリリースするために開発に勤しんでいます。</string>
|
||||||
<string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string>
|
<string name="magic_keyboard_explanation_summary">パスワードとすべての ID フィールドを格納するカスタム キーボードを有効にします</string>
|
||||||
@@ -416,7 +416,7 @@
|
|||||||
<string name="html_text_dev_feature_contibute"><strong>貢献</strong>による、</string>
|
<string name="html_text_dev_feature_contibute"><strong>貢献</strong>による、</string>
|
||||||
<string name="html_text_donation">自由を維持し活発に開発し続けるために、私たちはあなたの<strong>貢献</strong>に期待しています。</string>
|
<string name="html_text_donation">自由を維持し活発に開発し続けるために、私たちはあなたの<strong>貢献</strong>に期待しています。</string>
|
||||||
<string name="html_text_ad_free">多くのパスワード管理アプリとは異なり、このアプリは<strong>広告なし</strong>かつ<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="allow_no_password_title">空のマスターキーを許可</string>
|
||||||
<string name="autofill_auto_search_title">自動検索</string>
|
<string name="autofill_auto_search_title">自動検索</string>
|
||||||
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
||||||
@@ -440,7 +440,7 @@
|
|||||||
<string name="education_read_only_title">データベースの書き込みを禁止</string>
|
<string name="education_read_only_title">データベースの書き込みを禁止</string>
|
||||||
<string name="education_entry_new_field_summary">追加フィールドを登録し、値を追加し、必要に応じて保護します。</string>
|
<string name="education_entry_new_field_summary">追加フィールドを登録し、値を追加し、必要に応じて保護します。</string>
|
||||||
<string name="education_entry_new_field_title">カスタム フィールドを追加</string>
|
<string name="education_entry_new_field_title">カスタム フィールドを追加</string>
|
||||||
<string name="education_entry_edit_summary">カスタム フィールドを使ってエントリーを編集します。共有データは異なるエントリーのフィールド間で参照することができます。</string>
|
<string name="education_entry_edit_summary">エントリーをカスタム フィールドとともに編集します。共有データは異なるエントリーのフィールド間で参照することができます。</string>
|
||||||
<string name="education_entry_edit_title">エントリーを編集</string>
|
<string name="education_entry_edit_title">エントリーを編集</string>
|
||||||
<string name="education_biometric_title">生体認証によるロック解除</string>
|
<string name="education_biometric_title">生体認証によるロック解除</string>
|
||||||
<string name="education_create_database_summary">最初のパスワード管理ファイルを作成します。</string>
|
<string name="education_create_database_summary">最初のパスワード管理ファイルを作成します。</string>
|
||||||
@@ -448,8 +448,8 @@
|
|||||||
<string name="enable_education_screens_summary">要素をハイライトしてアプリの動作を学びます</string>
|
<string name="enable_education_screens_summary">要素をハイライトしてアプリの動作を学びます</string>
|
||||||
<string name="education_read_only_summary">セッションのロック解除モードを変更します。
|
<string name="education_read_only_summary">セッションのロック解除モードを変更します。
|
||||||
\n
|
\n
|
||||||
\n「書き込み禁止」では、データベースに対する意図しない変更を防ぐことができます。
|
\n[書き込み禁止] では、データベースに対する意図しない変更を防ぐことができます。
|
||||||
\n「変更可能」では、すべての要素を追加、削除、変更できます。</string>
|
\n[変更可能] では、すべての要素を追加、削除、変更できます。</string>
|
||||||
<string name="allow_copy_password_title">クリップボードの信頼</string>
|
<string name="allow_copy_password_title">クリップボードの信頼</string>
|
||||||
<string name="encryption_chacha20">ChaCha20</string>
|
<string name="encryption_chacha20">ChaCha20</string>
|
||||||
<string name="kdf_Argon2">Argon2</string>
|
<string name="kdf_Argon2">Argon2</string>
|
||||||
@@ -459,7 +459,7 @@
|
|||||||
<string name="keyboard_change">キーボードの切り替え</string>
|
<string name="keyboard_change">キーボードの切り替え</string>
|
||||||
<string name="keyboard_keys_category">キー</string>
|
<string name="keyboard_keys_category">キー</string>
|
||||||
<string name="keyboard_theme_title">キーボードのテーマ</string>
|
<string name="keyboard_theme_title">キーボードのテーマ</string>
|
||||||
<string name="keyboard_appearance_category">外観</string>
|
<string name="keyboard_appearance_category">デザイン</string>
|
||||||
<string name="keyboard_notification_entry_content_text">%1$s</string>
|
<string name="keyboard_notification_entry_content_text">%1$s</string>
|
||||||
<string name="keyboard_notification_entry_content_title_text">エントリー</string>
|
<string name="keyboard_notification_entry_content_title_text">エントリー</string>
|
||||||
<string name="keyboard_entry_timeout_title">タイムアウト</string>
|
<string name="keyboard_entry_timeout_title">タイムアウト</string>
|
||||||
@@ -486,7 +486,15 @@
|
|||||||
<string name="database_default_username_title">デフォルトのユーザー名</string>
|
<string name="database_default_username_title">デフォルトのユーザー名</string>
|
||||||
<string name="style_choose_title">アプリのテーマ</string>
|
<string name="style_choose_title">アプリのテーマ</string>
|
||||||
<string name="reset_education_screens_title">教育的なヒントをリセット</string>
|
<string name="reset_education_screens_title">教育的なヒントをリセット</string>
|
||||||
<string name="recycle_bin">ごみ箱</string>
|
<string name="recycle_bin">ゴミ箱</string>
|
||||||
<string name="recycle_bin_group_title">ごみ箱グループ</string>
|
<string name="recycle_bin_group_title">ゴミ箱グループ</string>
|
||||||
<string name="recycle_bin_title">ごみ箱の使用</string>
|
<string name="recycle_bin_title">ゴミ箱の使用</string>
|
||||||
|
<string name="warning_file_too_big">KeePass データベースに含まれると想定されるのは、小さなユーティリティ ファイル(PGP 鍵ファイルなど)のみです。
|
||||||
|
\n
|
||||||
|
\nこのアップロードによりデータベースが非常に大きくなり、パフォーマンスが低下する可能性があります。</string>
|
||||||
|
<string name="warning_replace_file">このファイルをアップロードすると、既存のファイルが置き換えられます。</string>
|
||||||
|
<string name="upload_attachment">%1$s をアップロード</string>
|
||||||
|
<string name="education_add_attachment_summary">エントリーに添付ファイルをアップロードして、重要な外部データを保存します。</string>
|
||||||
|
<string name="education_add_attachment_title">添付ファイルを追加</string>
|
||||||
|
<string name="warning_sure_add_file">それでもファイルを追加しますか?</string>
|
||||||
</resources>
|
</resources>
|
||||||
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_string_type">ഈ വാചകം അഭ്യർത്ഥിച്ച ഇനവുമായി പൊരുത്തപ്പെടുന്നില്ല.</string>
|
||||||
<string name="error_otp_counter">%1$dന്റെയും %2$dന്റെയും ഇടയിൽ ആയിരിക്കണം കൌണ്ടർ.</string>
|
<string name="error_otp_counter">%1$dന്റെയും %2$dന്റെയും ഇടയിൽ ആയിരിക്കണം കൌണ്ടർ.</string>
|
||||||
<string name="file_manager_install_description">ഉദ്ദേശിക്കുന്ന പ്രവര്ത്തനം സ്വീകരിക്കുന്ന ഒരു ഫയല് മാനേജര്. ഡാറ്റാബേസ് ഫയലുകള് നിര്മ്മിക്കാനും തുറക്കാനും സൂക്ഷിക്കാനും ACTION_CREATE_DOCUMENT നോടൊപ്പം ACTION_OPEN_DOCUMENT കൂടെ ആവശ്യമുണ്ട്.</string>
|
<string name="file_manager_install_description">ഉദ്ദേശിക്കുന്ന പ്രവര്ത്തനം സ്വീകരിക്കുന്ന ഒരു ഫയല് മാനേജര്. ഡാറ്റാബേസ് ഫയലുകള് നിര്മ്മിക്കാനും തുറക്കാനും സൂക്ഷിക്കാനും ACTION_CREATE_DOCUMENT നോടൊപ്പം ACTION_OPEN_DOCUMENT കൂടെ ആവശ്യമുണ്ട്.</string>
|
||||||
|
<string name="autofill_service_name">KeePassDX ഫോം ഓട്ടോഫില്ലിംഗ്</string>
|
||||||
|
<string name="biometric">ബയോമെട്രിക്</string>
|
||||||
|
<string name="credential_before_click_biometric_button">പാസ്വേഡ് ടൈപ്പുചെയ്യുക, തുടർന്ന് \"ബയോമെട്രിക്\" ബട്ടൺ ക്ലിക്കുചെയ്യുക.</string>
|
||||||
|
<string name="no_credentials_stored">ഈ ഡാറ്റാബേസിൽ ഇതുവരെ ക്രെഡൻഷ്യൽ സംഭരിച്ചിട്ടില്ല.</string>
|
||||||
|
<string name="biometric_invalid_key">ബയോമെട്രിക് കീ വായിക്കാൻ കഴിയില്ല. ദയവായി ഇത് ഇല്ലാതാക്കി ബയോമെട്രിക് തിരിച്ചറിയൽ നടപടിക്രമം ആവർത്തിക്കുക.</string>
|
||||||
|
<string name="biometric_prompt_extract_credential_message">ബയോമെട്രിക് ഡാറ്റ ഉപയോഗിച്ച് ഡാറ്റാബേസ് ക്രെഡൻഷ്യൽ എക്സ്ട്രാക്റ്റുചെയ്യുക</string>
|
||||||
|
<string name="biometric_prompt_store_credential_title">ബയോമെട്രിക് തിരിച്ചറിയൽ സംരക്ഷിക്കുക</string>
|
||||||
|
<string name="open_biometric_prompt_store_credential">ക്രെഡൻഷ്യലുകൾ സംഭരിക്കുന്നതിന് ബയോമെട്രിക് പ്രോംപ്റ്റ് തുറക്കുക</string>
|
||||||
|
<string name="open_biometric_prompt_unlock_database">ഡാറ്റാബേസ് അൺലോക്കുചെയ്യുന്നതിന് ബയോമെട്രിക് പ്രോംപ്റ്റ് തുറക്കുക</string>
|
||||||
|
<string name="configure_biometric">ബയോമെട്രിക് പ്രോംപ്റ്റ് പിന്തുണയ്ക്കുന്നു, പക്ഷേ സജ്ജീകരിച്ചിട്ടില്ല.</string>
|
||||||
|
<string name="warning_sure_add_file">എന്തായാലും ഫയൽ ചേർക്കണോ\?</string>
|
||||||
|
<string name="warning_replace_file">ഈ ഫയൽ അപ്ലോഡുചെയ്യുന്നത് നിലവിലുള്ള ഫയലിനെ മാറ്റിസ്ഥാപിക്കും.</string>
|
||||||
|
<string name="warning_database_link_revoked">ഫയൽ മാനേജർ റദ്ദാക്കിയ ഫയലിലേക്കുള്ള ആക്സസ്</string>
|
||||||
|
<string name="warning_database_read_only">ഡാറ്റാബേസ് മാറ്റങ്ങൾ സംരക്ഷിക്കുന്നതിന് ഫയൽ റൈറ്റ് ആക്സസ് അനുവദിക്കുക</string>
|
||||||
|
<string name="sort_groups_before">മുമ്പുള്ള ഗ്രൂപ്പുകൾ</string>
|
||||||
|
<string name="sort_ascending">ആദ്യത്തേത് ഏറ്റവും കുറഞ്ഞത്↓</string>
|
||||||
|
<string name="parallelism_explanation">കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന സമാന്തരതയുടെ ബിരുദം (അതായത് ത്രെഡുകളുടെ എണ്ണം).</string>
|
||||||
|
<string name="memory_usage_explanation">കീ ഡെറിവേഷൻ ഫംഗ്ഷൻ ഉപയോഗിക്കുന്ന മെമ്മറിയുടെ അളവ് (ബൈറ്റുകളിൽ).</string>
|
||||||
|
<string name="encryption_explanation">എല്ലാ ഡാറ്റയ്ക്കും ഉപയോഗിക്കുന്ന ഡാറ്റാബേസ് എൻക്രിപ്ഷൻ അൽഗോരിതം.</string>
|
||||||
|
<string name="hide_broken_locations_summary">സമീപകാല ഡാറ്റാബേസുകളുടെ പട്ടികയിൽ നിന്ന് തകർന്ന ലിങ്കുകൾ മറയ്ക്കുക</string>
|
||||||
|
<string name="hide_broken_locations_title">തകർന്ന ഡാറ്റാബേസ് ലിങ്കുകൾ മറയ്ക്കുക</string>
|
||||||
|
<string name="show_recent_files_summary">സമീപകാല ഡാറ്റാബേസുകളുടെ സ്ഥാനങ്ങൾ കാണിക്കുക</string>
|
||||||
|
<string name="remember_keyfile_locations_summary">കീ ഫയലുകൾ എവിടെയാണ് സൂക്ഷിച്ചിരിക്കുന്നതെന്ന് ഓർത്തുവെയ്ക്കുന്നു</string>
|
||||||
|
<string name="remember_keyfile_locations_title">കീ ഫയൽ ലൊക്കേഷനുകൾ ഓർത്തുവെയ്ക്കുക</string>
|
||||||
|
<string name="remember_database_locations_summary">ഡാറ്റാബേസുകൾ എവിടെയാണ് സൂക്ഷിച്ചിരിക്കുന്നതെന്ന് ട്രാക്ക് ചെയ്യുന്നു</string>
|
||||||
|
<string name="remember_database_locations_title">ഡാറ്റാബേസ് ലൊക്കേഷനുകൾ ഓർത്തുവെയ്ക്കുക</string>
|
||||||
|
<string name="contains_duplicate_uuid">ഡാറ്റാബേസിൽ തനിപ്പകർപ്പ് UUID-കൾ അടങ്ങിയിരിക്കുന്നു.</string>
|
||||||
|
<string name="read_only_warning">നിങ്ങളുടെ ഫയൽ മാനേജരെ ആശ്രയിച്ച്, നിങ്ങളുടെ സ്റ്റോറേജിൽ എഴുതാൻ KeepassDX-നെ അനുവദിച്ചേക്കില്ല.</string>
|
||||||
|
<string name="auto_focus_search_summary">ഡാറ്റാബേസ് തുറക്കുമ്പോൾ തിരയൽ അഭ്യർത്ഥിക്കുക</string>
|
||||||
|
<string name="omit_backup_search_summary">തിരയൽ ഫലങ്ങളിൽ നിന്ന് \"ബാക്കപ്പ്\", \"റീസൈക്കിൾ ബിൻ\" ഗ്രൂപ്പുകൾ ഒഴിവാക്കുക</string>
|
||||||
|
<string name="omit_backup_search_title">ബാക്കപ്പ് എൻട്രികളിലൂടെ തിരയരുത്</string>
|
||||||
|
<string name="menu_open_file_read_and_write">പരിഷ്ക്കരിക്കാവുന്ന</string>
|
||||||
|
<string name="menu_file_selection_read_only">റൈറ്റ്-പരിരക്ഷിതമാണ്</string>
|
||||||
|
<string name="error_otp_digits">ടോക്കണിൽ %1$ മുതൽ %2$d അക്കങ്ങൾ ഉണ്ടായിരിക്കണം.</string>
|
||||||
|
<string name="database_custom_color_title">ഇഷ്ടാനുസൃത ഡാറ്റാബേസ് നിറം</string>
|
||||||
|
<string name="keyboard_notification_entry_summary">ഒരു എൻട്രി ലഭ്യമാകുമ്പോൾ, അറിയിപ്പ് കാണിക്കുക</string>
|
||||||
|
<string name="keyboard_previous_database_credentials_title">ഡാറ്റാബേസ് ക്രെഡൻഷ്യലുകൾ സ്ക്രീൻ</string>
|
||||||
|
<string name="autofill_application_id_blocklist_title">അപ്ലിക്കേഷൻ ബ്ലോക്ക്ലിസ്റ്റ്</string>
|
||||||
|
<string name="autofill_web_domain_blocklist_title">വെബ് ഡൊമെയ്ൻ ബ്ലോക്ക്ലിസ്റ്റ്</string>
|
||||||
|
<string name="education_donation_summary">സ്ഥിരത, സുരക്ഷ എന്നിവ വർദ്ധിപ്പിക്കുന്നതിനും കൂടുതൽ സവിശേഷതകൾ ചേർക്കുന്നതിനും സഹായിക്കുക.</string>
|
||||||
|
<string name="html_text_feature_generosity">ഞങ്ങളുടെ സ്വാതന്ത്ര്യം നിലനിർത്തുന്നതിനും എല്ലായ്പ്പോഴും സജീവമായിരിക്കുന്നതിനും, നിങ്ങളുടെ<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>
|
</resources>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string>
|
<string name="error_arc4">De Arcfour stream-versleuteling wordt niet ondersteund.</string>
|
||||||
<string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string>
|
<string name="error_can_not_handle_uri">KeePassDX kan deze URI niet verwerken.</string>
|
||||||
<string name="error_file_not_create">Bestand is niet aangemaakt</string>
|
<string name="error_file_not_create">Bestand is niet aangemaakt</string>
|
||||||
<string name="error_invalid_db">Kan database niet uitlezen.</string>
|
<string name="error_invalid_db">Kan de database niet uitlezen.</string>
|
||||||
<string name="error_invalid_path">Zorg ervoor dat het pad juist is.</string>
|
<string name="error_invalid_path">Zorg ervoor dat het pad juist is.</string>
|
||||||
<string name="error_no_name">Voer een naam in.</string>
|
<string name="error_no_name">Voer een naam in.</string>
|
||||||
<string name="error_nokeyfile">Kies een sleutelbestand.</string>
|
<string name="error_nokeyfile">Kies een sleutelbestand.</string>
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
<string name="biometric_not_recognized">Biometrie niet herkend</string>
|
<string name="biometric_not_recognized">Biometrie niet herkend</string>
|
||||||
<string name="biometric_scanning_error">Probleem met biometrie: %1$s</string>
|
<string name="biometric_scanning_error">Probleem met biometrie: %1$s</string>
|
||||||
<string name="open_biometric_prompt_store_credential">Biometrische herkenning gebruiken om wachtwoorden op te slaan</string>
|
<string name="open_biometric_prompt_store_credential">Biometrische herkenning gebruiken om wachtwoorden op te slaan</string>
|
||||||
<string name="no_credentials_stored">Deze databank heeft nog geen wachtwoord.</string>
|
<string name="no_credentials_stored">Deze database heeft nog geen opgeslagen gegevens.</string>
|
||||||
<string name="database_history">Geschiedenis</string>
|
<string name="database_history">Geschiedenis</string>
|
||||||
<string name="menu_appearance_settings">Uiterlijk</string>
|
<string name="menu_appearance_settings">Uiterlijk</string>
|
||||||
<string name="general">Algemeen</string>
|
<string name="general">Algemeen</string>
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
<string name="list_password_generator_options_summary">Toegestane wachtwoordtekens instellen</string>
|
<string name="list_password_generator_options_summary">Toegestane wachtwoordtekens instellen</string>
|
||||||
<string name="clipboard">Klembord</string>
|
<string name="clipboard">Klembord</string>
|
||||||
<string name="clipboard_notifications_title">Klembordmeldingen</string>
|
<string name="clipboard_notifications_title">Klembordmeldingen</string>
|
||||||
<string name="clipboard_notifications_summary">Schakel klembordmeldingen in om velden te kopiëren bij het bekijken van een item</string>
|
<string name="clipboard_notifications_summary">Toon klembordmeldingen om velden te kopiëren bij het bekijken van een item</string>
|
||||||
<string name="clipboard_warning">Als automatisch wissen van het klembord mislukt, doe dit dan handmatig.</string>
|
<string name="clipboard_warning">Als automatisch wissen van het klembord mislukt, doe dit dan handmatig.</string>
|
||||||
<string name="lock">Vergrendelen</string>
|
<string name="lock">Vergrendelen</string>
|
||||||
<string name="lock_database_screen_off_title">Schermvergrendeling</string>
|
<string name="lock_database_screen_off_title">Schermvergrendeling</string>
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
<string name="biometric_delete_all_key_summary">Alle sleutels voor biometrische herkenning verwijderen</string>
|
<string name="biometric_delete_all_key_summary">Alle sleutels voor biometrische herkenning verwijderen</string>
|
||||||
<string name="biometric_delete_all_key_warning">Alle coderingssleutels voor biometrische herkenning verwijderen\?</string>
|
<string name="biometric_delete_all_key_warning">Alle coderingssleutels voor biometrische herkenning verwijderen\?</string>
|
||||||
<string name="unavailable_feature_text">Kan deze functie niet starten.</string>
|
<string name="unavailable_feature_text">Kan deze functie niet starten.</string>
|
||||||
<string name="unavailable_feature_version">Je Android-versie, %1$s, voldoet niet aan de minimumvereiste %2$s.</string>
|
<string name="unavailable_feature_version">Het apparaat draait op Android %1$s, maar %2$s of hoger is vereist.</string>
|
||||||
<string name="unavailable_feature_hardware">De bijbehorende hardware werd niet gevonden.</string>
|
<string name="unavailable_feature_hardware">De bijbehorende hardware werd niet gevonden.</string>
|
||||||
<string name="file_name">Bestandsnaam</string>
|
<string name="file_name">Bestandsnaam</string>
|
||||||
<string name="path">Pad</string>
|
<string name="path">Pad</string>
|
||||||
@@ -254,8 +254,8 @@
|
|||||||
<string name="keyboard">Toetsenbord</string>
|
<string name="keyboard">Toetsenbord</string>
|
||||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||||
<string name="magic_keyboard_explanation_summary">Aangepast toetsenbord met je wachtwoorden en alle identiteitsvelden activeren</string>
|
<string name="magic_keyboard_explanation_summary">Aangepast toetsenbord met je wachtwoorden en alle identiteitsvelden activeren</string>
|
||||||
<string name="allow_no_password_title">Geen hoofdwachtwoord</string>
|
<string name="allow_no_password_title">Geen hoofdwachtwoord toestaan</string>
|
||||||
<string name="allow_no_password_summary">Schakel de knop \"Openen\" in als er geen referenties zijn geselecteerd</string>
|
<string name="allow_no_password_summary">Maakt het mogelijk op de knop \"Openen\" te tikken als er geen inloggegevens zijn geselecteerd</string>
|
||||||
<string name="enable_read_only_title">Alleen-lezen</string>
|
<string name="enable_read_only_title">Alleen-lezen</string>
|
||||||
<string name="enable_read_only_summary">Open de database standaard alleen-lezen</string>
|
<string name="enable_read_only_summary">Open de database standaard alleen-lezen</string>
|
||||||
<string name="enable_education_screens_title">Informatieve tips</string>
|
<string name="enable_education_screens_title">Informatieve tips</string>
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
\nGroepen (~mappen) organiseren de items in je database.</string>
|
\nGroepen (~mappen) organiseren de items in je database.</string>
|
||||||
<string name="education_search_title">Doorzoek al je items</string>
|
<string name="education_search_title">Doorzoek al je items</string>
|
||||||
<string name="education_search_summary">Doorzoek items op titel, gebruikersnaam of andere velden om wachtwoorden te vinden.</string>
|
<string name="education_search_summary">Doorzoek items op titel, gebruikersnaam of andere velden om wachtwoorden te vinden.</string>
|
||||||
<string name="education_biometric_title">Database-ontgrendeling met biometrie</string>
|
<string name="education_biometric_title">Biometrische database -ontgrendeling</string>
|
||||||
<string name="education_biometric_summary">Koppel je wachtwoord aan je biometrie om de database snel te ontgrendelen.</string>
|
<string name="education_biometric_summary">Koppel je wachtwoord aan je biometrie om de database snel te ontgrendelen.</string>
|
||||||
<string name="education_entry_edit_title">Item bewerken</string>
|
<string name="education_entry_edit_title">Item bewerken</string>
|
||||||
<string name="education_entry_edit_summary">Bewerk het item met aangepaste velden. Referenties kunnen worden toegevoegd tussen velden van verschillende items.</string>
|
<string name="education_entry_edit_summary">Bewerk het item met aangepaste velden. Referenties kunnen worden toegevoegd tussen velden van verschillende items.</string>
|
||||||
@@ -297,7 +297,7 @@
|
|||||||
<string name="education_sort_summary">Kies hoe items en groepen worden gesorteerd.</string>
|
<string name="education_sort_summary">Kies hoe items en groepen worden gesorteerd.</string>
|
||||||
<string name="education_donation_title">Bijdragen</string>
|
<string name="education_donation_title">Bijdragen</string>
|
||||||
<string name="education_donation_summary">Draag bij om de stabiliteit en veiligheid te vergroten en door meer functies toe te voegen.</string>
|
<string name="education_donation_summary">Draag bij om de stabiliteit en veiligheid te vergroten en door meer functies toe te voegen.</string>
|
||||||
<string name="html_text_ad_free">In tegenstelling tot veel apps voor wachtwoordbeheer, is deze <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_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_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>
|
<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="compression_gzip">Gzip</string>
|
||||||
<string name="device_keyboard_setting_title">Toetsenbordinstellingen</string>
|
<string name="device_keyboard_setting_title">Toetsenbordinstellingen</string>
|
||||||
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
|
<string name="enable_auto_save_database_summary">Sla de database op na elke belangrijke actie (in \"Schrijf\" modus)</string>
|
||||||
<string name="education_setup_OTP_title">Instellingen OTP</string>
|
<string name="education_setup_OTP_title">OTP instellen</string>
|
||||||
<string name="remember_keyfile_locations_title">Locatie van sleutelbestanden opslaan</string>
|
<string name="remember_keyfile_locations_title">Locatie van sleutelbestanden opslaan</string>
|
||||||
<string name="remember_database_locations_title">Databaselocatie opslaan</string>
|
<string name="remember_database_locations_title">Databaselocaties onthouden</string>
|
||||||
<string name="hide_expired_entries_summary">Verlopen items worden verborgen</string>
|
<string name="hide_expired_entries_summary">Verlopen items worden niet getoond</string>
|
||||||
<string name="hide_expired_entries_title">Verberg verlopen items</string>
|
<string name="hide_expired_entries_title">Verberg verlopen items</string>
|
||||||
<string name="download_complete">Tik om het bestand te openen</string>
|
<string name="download_complete">Klaar!</string>
|
||||||
<string name="download_finalization">Voltooien…</string>
|
<string name="download_finalization">Voltooien…</string>
|
||||||
<string name="download_progression">Voortgang: %1$d%%</string>
|
<string name="download_progression">Voortgang: %1$d%%</string>
|
||||||
<string name="download_initialization">Initialiseren…</string>
|
<string name="download_initialization">Initialiseren…</string>
|
||||||
<string name="download_attachment">Download %1$s</string>
|
<string name="download_attachment">Download %1$s</string>
|
||||||
<string name="education_setup_OTP_summary">Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren dat is aangevraagd voor tweefactorauthenticatie (2FA).</string>
|
<string name="education_setup_OTP_summary">Stel eenmalig wachtwoordbeheer (HOTP / TOTP) in om een token te genereren dat wordt gevraagd voor tweefactorauthenticatie (2FA).</string>
|
||||||
<string name="enable_auto_save_database_title">Automatisch opslaan</string>
|
<string name="enable_auto_save_database_title">Automatisch opslaan</string>
|
||||||
<string name="autofill_auto_search_summary">Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID</string>
|
<string name="autofill_auto_search_summary">Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID</string>
|
||||||
<string name="autofill_auto_search_title">Automatisch zoeken</string>
|
<string name="autofill_auto_search_title">Automatisch zoeken</string>
|
||||||
@@ -452,8 +452,8 @@
|
|||||||
<string name="command_execution">Opdracht uitvoeren…</string>
|
<string name="command_execution">Opdracht uitvoeren…</string>
|
||||||
<string name="hide_broken_locations_summary">Verberg gebroken links in de lijst met recente databases</string>
|
<string name="hide_broken_locations_summary">Verberg gebroken links in de lijst met recente databases</string>
|
||||||
<string name="hide_broken_locations_title">Verberg corrupte databasekoppelingen</string>
|
<string name="hide_broken_locations_title">Verberg corrupte databasekoppelingen</string>
|
||||||
<string name="remember_keyfile_locations_summary">Onthoud de locatie van databasesleutelbestanden</string>
|
<string name="remember_keyfile_locations_summary">Onthoudt waar de databasesleutelbestanden zijn opgeslagen</string>
|
||||||
<string name="remember_database_locations_summary">Onthoud de locatie van databases</string>
|
<string name="remember_database_locations_summary">Onthoudt waar de databases zijn opgeslagen</string>
|
||||||
<string name="auto_focus_search_summary">Zoekopdracht aanmaken bij het openen van een database</string>
|
<string name="auto_focus_search_summary">Zoekopdracht aanmaken bij het openen van een database</string>
|
||||||
<string name="auto_focus_search_title">Snel zoeken</string>
|
<string name="auto_focus_search_title">Snel zoeken</string>
|
||||||
<string name="menu_delete_entry_history">Geschiedenis wissen</string>
|
<string name="menu_delete_entry_history">Geschiedenis wissen</string>
|
||||||
@@ -491,4 +491,12 @@
|
|||||||
<string name="keyboard_previous_database_credentials_summary">Schakel automatisch terug naar het vorige toetsenbord op het databasereferentiescherm</string>
|
<string name="keyboard_previous_database_credentials_summary">Schakel automatisch terug naar het vorige toetsenbord op het databasereferentiescherm</string>
|
||||||
<string name="keyboard_previous_database_credentials_title">Scherm Databasereferenties</string>
|
<string name="keyboard_previous_database_credentials_title">Scherm Databasereferenties</string>
|
||||||
<string name="keyboard_change">Van toetsenbord wisselen</string>
|
<string name="keyboard_change">Van toetsenbord wisselen</string>
|
||||||
|
<string name="upload_attachment">%1$s uploaden</string>
|
||||||
|
<string name="education_add_attachment_summary">Upload een bijlage bij dit item om belangrijke externe gegevens op te slaan.</string>
|
||||||
|
<string name="education_add_attachment_title">Bijlage toevoegen</string>
|
||||||
|
<string name="warning_sure_add_file">Toch het bestand toevoegen\?</string>
|
||||||
|
<string name="warning_file_too_big">Een KeePass-database mag alleen kleine hulpprogramma-bestanden bevatten (zoals PGP-sleutelbestanden).
|
||||||
|
\n
|
||||||
|
\nDe database kan erg groot worden en de prestaties kunnen verminderen bij deze upload.</string>
|
||||||
|
<string name="warning_replace_file">Als je dit bestand uploadt, wordt het bestaande vervangen.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -489,4 +489,12 @@
|
|||||||
<string name="keyboard_previous_database_credentials_summary">Automatycznie przełącz się z powrotem do poprzedniej klawiatury na ekranie poświadczeń bazy danych</string>
|
<string name="keyboard_previous_database_credentials_summary">Automatycznie przełącz się z powrotem do poprzedniej klawiatury na ekranie poświadczeń bazy danych</string>
|
||||||
<string name="keyboard_previous_database_credentials_title">Ekran poświadczeń bazy danych</string>
|
<string name="keyboard_previous_database_credentials_title">Ekran poświadczeń bazy danych</string>
|
||||||
<string name="keyboard_change">Przełącz klawiaturę</string>
|
<string name="keyboard_change">Przełącz klawiaturę</string>
|
||||||
|
<string name="upload_attachment">Prześlij %1$s</string>
|
||||||
|
<string name="education_add_attachment_summary">Prześlij załącznik do wpisu, aby zapisać ważne dane zewnętrzne.</string>
|
||||||
|
<string name="education_add_attachment_title">Dodawanie załącznika</string>
|
||||||
|
<string name="warning_sure_add_file">Czy mimo to dodać plik\?</string>
|
||||||
|
<string name="warning_replace_file">Przesłanie tego pliku spowoduje zastąpienie istniejącego.</string>
|
||||||
|
<string name="warning_file_too_big">Baza danych KeePass powinna zawierać tylko małe pliki narzędziowe (takie jak pliki kluczy PGP).
|
||||||
|
\n
|
||||||
|
\nTwoja baza danych może stać się bardzo duża i zmniejszyć wydajność dzięki temu wgrywaniu danych.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -425,7 +425,7 @@
|
|||||||
<string name="menu_save_database">Сохранить базу</string>
|
<string name="menu_save_database">Сохранить базу</string>
|
||||||
<string name="menu_empty_recycle_bin">Очистить \"корзину\"</string>
|
<string name="menu_empty_recycle_bin">Очистить \"корзину\"</string>
|
||||||
<string name="command_execution">Выполнение команды…</string>
|
<string name="command_execution">Выполнение команды…</string>
|
||||||
<string name="warning_permanently_delete_nodes">Безвозвратно удалить выбранные узлы\?</string>
|
<string name="warning_permanently_delete_nodes">Безвозвратно удалить выбранные элементы\?</string>
|
||||||
<string name="keystore_not_accessible">Хранилище ключей не инициализировано должным образом.</string>
|
<string name="keystore_not_accessible">Хранилище ключей не инициализировано должным образом.</string>
|
||||||
<string name="credential_before_click_biometric_button">Введите пароль, затем нажмите кнопку биометрии.</string>
|
<string name="credential_before_click_biometric_button">Введите пароль, затем нажмите кнопку биометрии.</string>
|
||||||
<string name="recycle_bin_group_title">Группа \"корзины\"</string>
|
<string name="recycle_bin_group_title">Группа \"корзины\"</string>
|
||||||
@@ -489,4 +489,12 @@
|
|||||||
<string name="keyboard_previous_fill_in_title">Автоматическое действие кнопки</string>
|
<string name="keyboard_previous_fill_in_title">Автоматическое действие кнопки</string>
|
||||||
<string name="keyboard_previous_database_credentials_summary">Автоматически переключаться на предыдущую клавиатуру на экране входа в базу</string>
|
<string name="keyboard_previous_database_credentials_summary">Автоматически переключаться на предыдущую клавиатуру на экране входа в базу</string>
|
||||||
<string name="keyboard_change">Переключение клавиатуры</string>
|
<string name="keyboard_change">Переключение клавиатуры</string>
|
||||||
|
<string name="upload_attachment">Добавить %1$s</string>
|
||||||
|
<string name="education_add_attachment_summary">В запись можно добавить файл как вложение, чтобы сохранить важные внешние данные.</string>
|
||||||
|
<string name="education_add_attachment_title">Добавление вложения</string>
|
||||||
|
<string name="warning_file_too_big">Предполагается, что база KeePass содержит только небольшие служебные файлы (например, файлы ключей PGP).
|
||||||
|
\n
|
||||||
|
\nЕсли добавить этот файл, база станет очень большой и снизится производительность.</string>
|
||||||
|
<string name="warning_replace_file">Добавление этого файла заменит существующий.</string>
|
||||||
|
<string name="warning_sure_add_file">Добавить файл в любом случае\?</string>
|
||||||
</resources>
|
</resources>
|
||||||
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_summary">Veri tabanı kimlik bilgileri ekranında otomatik olarak önceki klavyeye dön</string>
|
||||||
<string name="keyboard_previous_database_credentials_title">Veri tabanı kimlik bilgileri ekranı</string>
|
<string name="keyboard_previous_database_credentials_title">Veri tabanı kimlik bilgileri ekranı</string>
|
||||||
<string name="keyboard_change">Klavye değiştir</string>
|
<string name="keyboard_change">Klavye değiştir</string>
|
||||||
|
<string name="upload_attachment">%1$s yükle</string>
|
||||||
|
<string name="education_add_attachment_summary">Önemli harici verileri kaydetmek için girdinize bir ek yükleyin.</string>
|
||||||
|
<string name="education_add_attachment_title">Ek ekle</string>
|
||||||
|
<string name="warning_sure_add_file">Dosya yine de eklensin mi\?</string>
|
||||||
|
<string name="warning_replace_file">Bu dosyanın yüklenmesi mevcut dosyanın yerini alacaktır.</string>
|
||||||
|
<string name="warning_file_too_big">Bir KeePass veri tabanının sadece küçük yardımcı dosyaları (PGP anahtar dosyaları gibi) içermesi beklenmektedir.
|
||||||
|
\n
|
||||||
|
\nVeri tabanınız bu yüklemeyle çok büyük hale gelebilir ve performansı düşürebilir.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -489,4 +489,12 @@
|
|||||||
<string name="keyboard_previous_fill_in_title">Самочинне введення</string>
|
<string name="keyboard_previous_fill_in_title">Самочинне введення</string>
|
||||||
<string name="keyboard_previous_database_credentials_summary">Автоматичне перемикання до попередньої клавіатури, на екрані входу до бази даних</string>
|
<string name="keyboard_previous_database_credentials_summary">Автоматичне перемикання до попередньої клавіатури, на екрані входу до бази даних</string>
|
||||||
<string name="keyboard_previous_database_credentials_title">Екран входу до бази даних</string>
|
<string name="keyboard_previous_database_credentials_title">Екран входу до бази даних</string>
|
||||||
|
<string name="warning_file_too_big">База даних KeePass має містити лише невеликі файли утиліт (наприклад, файли ключів PGP).
|
||||||
|
\n
|
||||||
|
\nЧерез це завантаження, база даних може стати завеликою, що уповільнить роботу.</string>
|
||||||
|
<string name="education_add_attachment_summary">Завантажте вкладення до запису, щоб зберегти важливі зовнішні дані.</string>
|
||||||
|
<string name="upload_attachment">Завантажити %1$s</string>
|
||||||
|
<string name="education_add_attachment_title">Додати вкладення</string>
|
||||||
|
<string name="warning_sure_add_file">Все одно додати файл\?</string>
|
||||||
|
<string name="warning_replace_file">Завантаження цього файлу замінить наявний.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -489,4 +489,12 @@
|
|||||||
<string name="keyboard_previous_fill_in_title">自动键动作</string>
|
<string name="keyboard_previous_fill_in_title">自动键动作</string>
|
||||||
<string name="keyboard_previous_database_credentials_summary">如果显示数据库凭据屏幕,则自动返回到上一个键盘</string>
|
<string name="keyboard_previous_database_credentials_summary">如果显示数据库凭据屏幕,则自动返回到上一个键盘</string>
|
||||||
<string name="keyboard_change">切换键盘</string>
|
<string name="keyboard_change">切换键盘</string>
|
||||||
|
<string name="upload_attachment">上传 %1$s</string>
|
||||||
|
<string name="education_add_attachment_summary">将附件上传到您的条目以保存重要的外部数据。</string>
|
||||||
|
<string name="education_add_attachment_title">添加附件</string>
|
||||||
|
<string name="warning_sure_add_file">不论如何都要添加这个文件吗?</string>
|
||||||
|
<string name="warning_replace_file">上载此文件将替换现有文件。</string>
|
||||||
|
<string name="warning_file_too_big">KeePass数据库应该只包含小的实用程序文件(例如PGP密钥文件)。
|
||||||
|
\n
|
||||||
|
\n上传大文件会使增大数据库体积并降低性能。</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
<string name="contribution_url" translatable="false">https://www.keepassdx.com/contribution</string>
|
<string name="contribution_url" translatable="false">https://www.keepassdx.com/contribution</string>
|
||||||
<string name="homepage_url" translatable="false">https://www.keepassdx.com</string>
|
<string name="homepage_url" translatable="false">https://www.keepassdx.com</string>
|
||||||
<string name="issues_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/issues</string>
|
<string name="issues_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/issues</string>
|
||||||
|
<string name="credentials_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Credentials</string>
|
||||||
<string name="advanced_unlock_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Advanced-Unlocking</string>
|
<string name="advanced_unlock_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Advanced-Unlocking</string>
|
||||||
<string name="magic_keyboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Magikeyboard</string>
|
<string name="magic_keyboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Magikeyboard</string>
|
||||||
<string name="clipboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Clipboard</string>
|
<string name="clipboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Clipboard</string>
|
||||||
@@ -178,8 +179,9 @@
|
|||||||
<string name="database_custom_color_key" translatable="false">database_custom_color_key</string>
|
<string name="database_custom_color_key" translatable="false">database_custom_color_key</string>
|
||||||
<string name="database_version_key" translatable="false">database_version_key</string>
|
<string name="database_version_key" translatable="false">database_version_key</string>
|
||||||
|
|
||||||
<string name="database_category_compression_key" translatable="false">database_category_compression_key</string>
|
<string name="database_category_data_key" translatable="false">database_category_data_key</string>
|
||||||
<string name="database_data_compression_key" translatable="false">database_data_compression_key</string>
|
<string name="database_data_compression_key" translatable="false">database_data_compression_key</string>
|
||||||
|
<string name="database_data_remove_unlinked_attachments_key" translatable="false">database_data_remove_unlinked_attachments_key</string>
|
||||||
|
|
||||||
<string name="database_category_recycle_bin_key" translatable="false">database_category_recycle_bin_key</string>
|
<string name="database_category_recycle_bin_key" translatable="false">database_category_recycle_bin_key</string>
|
||||||
<string name="recycle_bin_enable_key" translatable="false">recycle_bin_enable_key</string>
|
<string name="recycle_bin_enable_key" translatable="false">recycle_bin_enable_key</string>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<string name="content_description_add_group">Add group</string>
|
<string name="content_description_add_group">Add group</string>
|
||||||
<string name="content_description_add_item">Add item</string>
|
<string name="content_description_add_item">Add item</string>
|
||||||
<string name="content_description_file_information">File info</string>
|
<string name="content_description_file_information">File info</string>
|
||||||
|
<string name="content_description_credentials_information">Credentials info</string>
|
||||||
<string name="content_description_password_checkbox">Password checkbox</string>
|
<string name="content_description_password_checkbox">Password checkbox</string>
|
||||||
<string name="content_description_keyfile_checkbox">Keyfile checkbox</string>
|
<string name="content_description_keyfile_checkbox">Keyfile checkbox</string>
|
||||||
<string name="content_description_repeat_toggle_password_visibility">Repeat toggle password visibility</string>
|
<string name="content_description_repeat_toggle_password_visibility">Repeat toggle password visibility</string>
|
||||||
@@ -260,6 +261,10 @@
|
|||||||
<string name="warning_file_too_big">A KeePass database is supposed to contain only small utility files (such as PGP key files).\n\nYour database may get very large and reduce performance with this upload.</string>
|
<string name="warning_file_too_big">A KeePass database is supposed to contain only small utility files (such as PGP key files).\n\nYour database may get very large and reduce performance with this upload.</string>
|
||||||
<string name="warning_replace_file">Uploading this file will replace the existing one.</string>
|
<string name="warning_replace_file">Uploading this file will replace the existing one.</string>
|
||||||
<string name="warning_sure_add_file">Add the file anyway?</string>
|
<string name="warning_sure_add_file">Add the file anyway?</string>
|
||||||
|
<string name="warning_remove_unlinked_attachment">Removing unlinked data may decrease the size of your database but may also delete data used for KeePass plugins.</string>
|
||||||
|
<string name="warning_sure_remove_data">Remove this data anyway?</string>
|
||||||
|
<string name="warning_empty_keyfile">It is not recommended to add an empty keyfile.</string>
|
||||||
|
<string name="warning_empty_keyfile_explanation">The content of the keyfile should never be changed, and in the best case, should contain randomly generated data.</string>
|
||||||
<string name="version_label">Version %1$s</string>
|
<string name="version_label">Version %1$s</string>
|
||||||
<string name="build_label">Build %1$s</string>
|
<string name="build_label">Build %1$s</string>
|
||||||
<string name="configure_biometric">Biometric prompt is supported, but not set up.</string>
|
<string name="configure_biometric">Biometric prompt is supported, but not set up.</string>
|
||||||
@@ -318,8 +323,11 @@
|
|||||||
<string name="file_name">Filename</string>
|
<string name="file_name">Filename</string>
|
||||||
<string name="path">Path</string>
|
<string name="path">Path</string>
|
||||||
<string name="assign_master_key">Assign a master key</string>
|
<string name="assign_master_key">Assign a master key</string>
|
||||||
|
<string name="data">Data</string>
|
||||||
<string name="database_data_compression_title">Data compression</string>
|
<string name="database_data_compression_title">Data compression</string>
|
||||||
<string name="database_data_compression_summary">Data compression reduces the size of the database.</string>
|
<string name="database_data_compression_summary">Data compression reduces the size of the database</string>
|
||||||
|
<string name="database_data_remove_unlinked_attachments_title">Remove unlinked data</string>
|
||||||
|
<string name="database_data_remove_unlinked_attachments_summary">Removes attachments contained in the database but not linked to an entry</string>
|
||||||
<string name="recycle_bin_title">Recycle bin usage</string>
|
<string name="recycle_bin_title">Recycle bin usage</string>
|
||||||
<string name="recycle_bin_summary">Moves groups and entries to \"Recycle bin\" group before deleting</string>
|
<string name="recycle_bin_summary">Moves groups and entries to \"Recycle bin\" group before deleting</string>
|
||||||
<string name="recycle_bin_group_title">Recycle bin group</string>
|
<string name="recycle_bin_group_title">Recycle bin group</string>
|
||||||
|
|||||||
@@ -59,14 +59,19 @@
|
|||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="@string/database_category_compression_key"
|
android:key="@string/database_category_data_key"
|
||||||
android:title="@string/compression">
|
android:title="@string/data">
|
||||||
|
|
||||||
<com.kunzisoft.keepass.settings.preference.DialogListExplanationPreference
|
<com.kunzisoft.keepass.settings.preference.DialogListExplanationPreference
|
||||||
android:key="@string/database_data_compression_key"
|
android:key="@string/database_data_compression_key"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/database_data_compression_title"/>
|
android:title="@string/database_data_compression_title"/>
|
||||||
|
|
||||||
|
<com.kunzisoft.keepass.settings.preference.TextPreference
|
||||||
|
android:key="@string/database_data_remove_unlinked_attachments_key"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/database_data_remove_unlinked_attachments_title"
|
||||||
|
android:summary="@string/database_data_remove_unlinked_attachments_summary"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|||||||
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