Merge branch 'feature/Entry_History' into develop

This commit is contained in:
J-Jamet
2019-09-23 10:30:20 +02:00
75 changed files with 1218 additions and 639 deletions

View File

@@ -21,6 +21,7 @@ package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.Handler
import com.google.android.material.appbar.CollapsingToolbarLayout
@@ -54,10 +55,13 @@ class EntryActivity : LockingHideActivity() {
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var titleIconView: ImageView? = null
private var historyView: View? = null
private var entryContentsView: EntryContentsView? = null
private var toolbar: Toolbar? = null
private var mEntry: EntryVersioned? = null
private var mIsHistory: Boolean = false
private var mShowPassword: Boolean = false
private var clipboardHelper: ClipboardHelper? = null
@@ -88,6 +92,12 @@ class EntryActivity : LockingHideActivity() {
Log.e(TAG, "Unable to retrieve the entry key")
}
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
if (historyPosition >= 0) {
mIsHistory = true
mEntry = mEntry?.getHistory()?.get(historyPosition)
}
if (mEntry == null) {
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show()
finish()
@@ -108,6 +118,7 @@ class EntryActivity : LockingHideActivity() {
// Get views
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
titleIconView = findViewById(R.id.entry_icon)
historyView = findViewById(R.id.history_container)
entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
@@ -238,18 +249,11 @@ class EntryActivity : LockingHideActivity() {
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
// Assign dates
entry.creationTime.date?.let {
entryContentsView?.assignCreationDate(it)
}
entry.lastModificationTime.date?.let {
entryContentsView?.assignModificationDate(it)
}
entry.lastAccessTime.date?.let {
entryContentsView?.assignLastAccessDate(it)
}
val expires = entry.expiryTime.date
if (entry.isExpires && expires != null) {
entryContentsView?.assignExpiresDate(expires)
entryContentsView?.assignCreationDate(entry.creationTime)
entryContentsView?.assignModificationDate(entry.lastModificationTime)
entryContentsView?.assignLastAccessDate(entry.lastAccessTime)
if (entry.isExpires) {
entryContentsView?.assignExpiresDate(entry.expiryTime)
} else {
entryContentsView?.assignExpiresDate(getString(R.string.never))
}
@@ -257,6 +261,24 @@ class EntryActivity : LockingHideActivity() {
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
// Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
if (mIsHistory) {
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
taColorAccent.recycle()
}
val entryHistory = entry.getHistory()
// isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) {
entryContentsView?.assignHistory(entryHistory)
entryContentsView?.onHistoryClick { historyItem, position ->
launch(this, historyItem, true, position)
}
}
database.stopManageEntry(entry)
}
@@ -412,13 +434,16 @@ class EntryActivity : LockingHideActivity() {
companion object {
private val TAG = EntryActivity::class.java.name
const val KEY_ENTRY = "entry"
const val KEY_ENTRY = "KEY_ENTRY"
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
fun launch(activity: Activity, pw: EntryVersioned, readOnly: Boolean) {
fun launch(activity: Activity, entry: EntryVersioned, readOnly: Boolean, historyPosition: Int? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, pw.nodeId)
intent.putExtra(KEY_ENTRY, entry.nodeId)
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
if (historyPosition != null)
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
}
}

View File

@@ -0,0 +1,50 @@
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.EntryVersioned
class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryHistoryList: MutableList<EntryVersioned> = ArrayList()
var onItemClickListener: ((item: EntryVersioned, position: Int)->Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder {
return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false))
}
override fun onBindViewHolder(holder: EntryHistoryViewHolder, position: Int) {
val entryHistory = entryHistoryList[position]
holder.lastModifiedView.text = entryHistory.lastModificationTime.getDateTimeString(context.resources)
holder.titleView.text = entryHistory.title
holder.usernameView.text = entryHistory.username
holder.urlView.text = entryHistory.url
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryHistory, position)
}
}
override fun getItemCount(): Int {
return entryHistoryList.size
}
fun clear() {
entryHistoryList.clear()
}
inner class EntryHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var lastModifiedView: TextView = itemView.findViewById(R.id.entry_history_last_modified)
var titleView: TextView = itemView.findViewById(R.id.entry_history_title)
var usernameView: TextView = itemView.findViewById(R.id.entry_history_username)
var urlView: TextView = itemView.findViewById(R.id.entry_history_url)
}
}

View File

@@ -32,12 +32,10 @@ class AesKdf internal constructor() : KdfEngine() {
override val defaultParameters: KdfParameters
get() {
val p = KdfParameters(uuid)
p.setParamUUID()
p.setUInt32(ParamRounds, DEFAULT_ROUNDS.toLong())
return p
return KdfParameters(uuid).apply {
setParamUUID()
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
}
}
override val defaultKeyRounds: Long
@@ -54,8 +52,8 @@ class AesKdf internal constructor() : KdfEngine() {
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
var currentMasterKey = masterKey
val rounds = p.getUInt64(ParamRounds)
var seed = p.getByteArray(ParamSeed)
val rounds = p.getUInt64(PARAM_ROUNDS)
var seed = p.getByteArray(PARAM_SEED)
if (currentMasterKey.size != 32) {
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
@@ -75,15 +73,15 @@ class AesKdf internal constructor() : KdfEngine() {
val seed = ByteArray(32)
random.nextBytes(seed)
p.setByteArray(ParamSeed, seed)
p.setByteArray(PARAM_SEED, seed)
}
override fun getKeyRounds(p: KdfParameters): Long {
return p.getUInt64(ParamRounds)
return p.getUInt64(PARAM_ROUNDS)
}
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
p.setUInt64(ParamRounds, keyRounds)
p.setUInt64(PARAM_ROUNDS, keyRounds)
}
companion object {
@@ -91,9 +89,24 @@ class AesKdf internal constructor() : KdfEngine() {
private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xC9.toByte(), 0xD9.toByte(), 0xF3.toByte(), 0x9A.toByte(), 0x62.toByte(), 0x8A.toByte(), 0x44.toByte(), 0x60.toByte(), 0xBF.toByte(), 0x74.toByte(), 0x0D.toByte(), 0x08.toByte(), 0xC1.toByte(), 0x8A.toByte(), 0x4F.toByte(), 0xEA.toByte()))
byteArrayOf(0xC9.toByte(),
0xD9.toByte(),
0xF3.toByte(),
0x9A.toByte(),
0x62.toByte(),
0x8A.toByte(),
0x44.toByte(),
0x60.toByte(),
0xBF.toByte(),
0x74.toByte(),
0x0D.toByte(),
0x08.toByte(),
0xC1.toByte(),
0x8A.toByte(),
0x4F.toByte(),
0xEA.toByte()))
const val ParamRounds = "R"
const val ParamSeed = "S"
const val PARAM_ROUNDS = "R"
const val PARAM_SEED = "S"
}
}

View File

@@ -33,16 +33,16 @@ class Argon2Kdf internal constructor() : KdfEngine() {
val p = KdfParameters(uuid)
p.setParamUUID()
p.setUInt32(ParamParallelism, DefaultParallelism)
p.setUInt64(ParamMemory, DefaultMemory)
p.setUInt64(ParamIterations, DefaultIterations)
p.setUInt32(ParamVersion, MaxVersion)
p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM)
p.setUInt64(PARAM_MEMORY, DEFAULT_MEMORY)
p.setUInt64(PARAM_ITERATIONS, DEFAULT_ITERATIONS)
p.setUInt32(PARAM_VERSION, MAX_VERSION)
return p
}
override val defaultKeyRounds: Long
get() = DefaultIterations
get() = DEFAULT_ITERATIONS
init {
uuid = CIPHER_UUID
@@ -55,13 +55,13 @@ class Argon2Kdf internal constructor() : KdfEngine() {
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
val salt = p.getByteArray(ParamSalt)
val parallelism = p.getUInt32(ParamParallelism).toInt()
val memory = p.getUInt64(ParamMemory)
val iterations = p.getUInt64(ParamIterations)
val version = p.getUInt32(ParamVersion)
val secretKey = p.getByteArray(ParamSecretKey)
val assocData = p.getByteArray(ParamAssocData)
val salt = p.getByteArray(PARAM_SALT)
val parallelism = p.getUInt32(PARAM_PARALLELISM).toInt()
val memory = p.getUInt64(PARAM_MEMORY)
val iterations = p.getUInt64(PARAM_ITERATIONS)
val version = p.getUInt32(PARAM_VERSION)
val secretKey = p.getByteArray(PARAM_SECRET_KEY)
val assocData = p.getByteArray(PARAM_ASSOC_DATA)
return Argon2Native.transformKey(masterKey, salt, parallelism, memory, iterations,
secretKey, assocData, version)
@@ -73,71 +73,102 @@ class Argon2Kdf internal constructor() : KdfEngine() {
val salt = ByteArray(32)
random.nextBytes(salt)
p.setByteArray(ParamSalt, salt)
p.setByteArray(PARAM_SALT, salt)
}
override fun getKeyRounds(p: KdfParameters): Long {
return p.getUInt64(ParamIterations)
return p.getUInt64(PARAM_ITERATIONS)
}
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
p.setUInt64(ParamIterations, keyRounds)
p.setUInt64(PARAM_ITERATIONS, keyRounds)
}
override val minKeyRounds: Long
get() = MIN_ITERATIONS
override val maxKeyRounds: Long
get() = MAX_ITERATIONS
override fun getMemoryUsage(p: KdfParameters): Long {
return p.getUInt64(ParamMemory)
return p.getUInt64(PARAM_MEMORY)
}
override fun setMemoryUsage(p: KdfParameters, memory: Long) {
p.setUInt64(ParamMemory, memory)
p.setUInt64(PARAM_MEMORY, memory)
}
override fun getDefaultMemoryUsage(): Long {
return DefaultMemory
}
override val defaultMemoryUsage: Long
get() = DEFAULT_MEMORY
override val minMemoryUsage: Long
get() = MIN_MEMORY
override val maxMemoryUsage: Long
get() = MAX_MEMORY
override fun getParallelism(p: KdfParameters): Int {
return p.getUInt32(ParamParallelism).toInt() // TODO Verify
return p.getUInt32(PARAM_PARALLELISM).toInt() // TODO Verify
}
override fun setParallelism(p: KdfParameters, parallelism: Int) {
p.setUInt32(ParamParallelism, parallelism.toLong())
p.setUInt32(PARAM_PARALLELISM, parallelism.toLong())
}
override fun getDefaultParallelism(): Int {
return DefaultParallelism.toInt() // TODO Verify
}
override val defaultParallelism: Int
get() = DEFAULT_PARALLELISM.toInt()
override val minParallelism: Int
get() = MIN_PARALLELISM
override val maxParallelism: Int
get() = MAX_PARALLELISM
companion object {
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xEF.toByte(), 0x63.toByte(), 0x6D.toByte(), 0xDF.toByte(), 0x8C.toByte(), 0x29.toByte(), 0x44.toByte(), 0x4B.toByte(), 0x91.toByte(), 0xF7.toByte(), 0xA9.toByte(), 0xA4.toByte(), 0x03.toByte(), 0xE3.toByte(), 0x0A.toByte(), 0x0C.toByte()))
byteArrayOf(0xEF.toByte(),
0x63.toByte(),
0x6D.toByte(),
0xDF.toByte(),
0x8C.toByte(),
0x29.toByte(),
0x44.toByte(),
0x4B.toByte(),
0x91.toByte(),
0xF7.toByte(),
0xA9.toByte(),
0xA4.toByte(),
0x03.toByte(),
0xE3.toByte(),
0x0A.toByte(),
0x0C.toByte()))
private const val ParamSalt = "S" // byte[]
private const val ParamParallelism = "P" // UInt32
private const val ParamMemory = "M" // UInt64
private const val ParamIterations = "I" // UInt64
private const val ParamVersion = "V" // UInt32
private const val ParamSecretKey = "K" // byte[]
private const val ParamAssocData = "A" // byte[]
private const val PARAM_SALT = "S" // byte[]
private const val PARAM_PARALLELISM = "P" // UInt32
private const val PARAM_MEMORY = "M" // UInt64
private const val PARAM_ITERATIONS = "I" // UInt64
private const val PARAM_VERSION = "V" // UInt32
private const val PARAM_SECRET_KEY = "K" // byte[]
private const val PARAM_ASSOC_DATA = "A" // byte[]
private const val MinVersion: Long = 0x10
private const val MaxVersion: Long = 0x13
private const val MIN_VERSION: Long = 0x10
private const val MAX_VERSION: Long = 0x13
private const val MinSalt = 8
private const val MaxSalt = Integer.MAX_VALUE
private const val MIN_SALT = 8
private const val MAX_SALT = Integer.MAX_VALUE
private const val MinIterations: Long = 1
private const val MaxIterations = 4294967295L
private const val MIN_ITERATIONS: Long = 1
private const val MAX_ITERATIONS = 4294967295L
private const val MinMemory = (1024 * 8).toLong()
private const val MaxMemory = Integer.MAX_VALUE.toLong()
private const val MIN_MEMORY = (1024 * 8).toLong()
private const val MAX_MEMORY = Integer.MAX_VALUE.toLong()
private const val MinParallelism = 1
private const val MaxParallelism = (1 shl 24) - 1
private const val MIN_PARALLELISM = 1
private const val MAX_PARALLELISM = (1 shl 24) - 1
private const val DefaultIterations: Long = 2
private const val DefaultMemory = (1024 * 1024).toLong()
private const val DefaultParallelism: Long = 2
private const val DEFAULT_ITERATIONS: Long = 2
private const val DEFAULT_MEMORY = (1024 * 1024).toLong()
private const val DEFAULT_PARALLELISM: Long = 2
}
}

View File

@@ -30,17 +30,31 @@ abstract class KdfEngine : ObjectNameResource {
abstract val defaultParameters: KdfParameters
abstract val defaultKeyRounds: Long
@Throws(IOException::class)
abstract fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray
abstract fun randomize(p: KdfParameters)
/*
* ITERATIONS
*/
abstract fun getKeyRounds(p: KdfParameters): Long
abstract fun setKeyRounds(p: KdfParameters, keyRounds: Long)
abstract val defaultKeyRounds: Long
open val minKeyRounds: Long
get() = 1
open val maxKeyRounds: Long
get() = Int.MAX_VALUE.toLong()
/*
* MEMORY
*/
open fun getMemoryUsage(p: KdfParameters): Long {
return UNKNOWN_VALUE.toLong()
}
@@ -49,9 +63,18 @@ abstract class KdfEngine : ObjectNameResource {
// Do nothing by default
}
open fun getDefaultMemoryUsage(): Long {
return UNKNOWN_VALUE.toLong()
}
open val defaultMemoryUsage: Long
get() = UNKNOWN_VALUE.toLong()
open val minMemoryUsage: Long
get() = 1
open val maxMemoryUsage: Long
get() = Int.MAX_VALUE.toLong()
/*
* PARALLELISM
*/
open fun getParallelism(p: KdfParameters): Int {
return UNKNOWN_VALUE
@@ -61,13 +84,16 @@ abstract class KdfEngine : ObjectNameResource {
// Do nothing by default
}
open fun getDefaultParallelism(): Int {
return UNKNOWN_VALUE
}
open val defaultParallelism: Int
get() = UNKNOWN_VALUE
open val minParallelism: Int
get() = 1
open val maxParallelism: Int
get() = Int.MAX_VALUE
companion object {
const val UNKNOWN_VALUE = -1
const val UNKNOWN_VALUE_STRING = (-1).toString()
}
}

View File

@@ -19,37 +19,7 @@
*/
package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.database.exception.UnknownKDF
import java.util.ArrayList
object KdfFactory {
var aesKdf = AesKdf()
var argon2Kdf = Argon2Kdf()
var kdfListV3: MutableList<KdfEngine> = ArrayList()
var kdfListV4: MutableList<KdfEngine> = ArrayList()
init {
kdfListV3.add(aesKdf)
kdfListV4.add(aesKdf)
kdfListV4.add(argon2Kdf)
}
@Throws(UnknownKDF::class)
fun getEngineV4(kdfParameters: KdfParameters?): KdfEngine {
val unknownKDFException = UnknownKDF()
if (kdfParameters == null) {
throw unknownKDFException
}
for (engine in kdfListV4) {
if (engine.uuid == kdfParameters.uuid) {
return engine
}
}
throw unknownKDFException
}
}

View File

@@ -140,7 +140,7 @@ enum class SortNodeEnum {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.creationTime.date
?.compareTo(object2.creationTime.date) ?: 0
.compareTo(object2.creationTime.date)
}
}
@@ -152,7 +152,7 @@ enum class SortNodeEnum {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.lastModificationTime.date
?.compareTo(object2.lastModificationTime.date) ?: 0
.compareTo(object2.lastModificationTime.date)
}
}
@@ -164,7 +164,7 @@ enum class SortNodeEnum {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.lastAccessTime.date
?.compareTo(object2.lastAccessTime.date) ?: 0
.compareTo(object2.lastAccessTime.date)
}
}
}

View File

@@ -33,11 +33,17 @@ class UpdateEntryRunnable constructor(
: ActionNodeDatabaseRunnable(context, database, finishRunnable, save) {
// Keep backup of original values in case save fails
private var mBackupEntry: EntryVersioned? = null
private var mBackupEntryHistory: EntryVersioned? = null
override fun nodeAction() {
mBackupEntry = database.addHistoryBackupTo(mOldEntry)
mOldEntry.touch(modified = true, touchParents = true)
mNewEntry.touch(modified = true, touchParents = true)
mBackupEntryHistory = EntryVersioned(mOldEntry)
// Create an entry history (an entry history don't have history)
mNewEntry.addEntryToHistory(EntryVersioned(mOldEntry, copyHistory = false))
database.removeOldestHistory(mNewEntry)
// Update entry with new values
mOldEntry.updateWith(mNewEntry)
}
@@ -45,7 +51,7 @@ class UpdateEntryRunnable constructor(
override fun nodeFinish(result: Result): ActionNodeValues {
if (!result.isSuccess) {
// If we fail to save, back out changes to global structure
mBackupEntry?.let {
mBackupEntryHistory?.let {
mOldEntry.updateWith(it)
}
}

View File

@@ -27,7 +27,6 @@ import android.net.Uri
import android.util.Log
import android.webkit.URLUtil
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.NodeHandler
import com.kunzisoft.keepass.database.cursor.EntryCursorV3
import com.kunzisoft.keepass.database.cursor.EntryCursorV4
@@ -88,32 +87,17 @@ class Database {
pwDatabaseV4?.defaultUserNameChanged = PwDate()
}
val encryptionAlgorithm: PwEncryptionAlgorithm?
get() {
return pwDatabaseV4?.encryptionAlgorithm
}
val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
get() = pwDatabaseV3?.availableEncryptionAlgorithms ?: pwDatabaseV4?.availableEncryptionAlgorithms ?: ArrayList()
val encryptionAlgorithm: PwEncryptionAlgorithm?
get() = pwDatabaseV3?.encryptionAlgorithm ?: pwDatabaseV4?.encryptionAlgorithm
val availableKdfEngines: List<KdfEngine>
get() {
if (pwDatabaseV3 != null) {
return KdfFactory.kdfListV3
}
if (pwDatabaseV4 != null) {
return KdfFactory.kdfListV4
}
return ArrayList()
}
get() = pwDatabaseV3?.kdfAvailableList ?: pwDatabaseV4?.kdfAvailableList ?: ArrayList()
val kdfEngine: KdfEngine
get() {
return pwDatabaseV4?.kdfEngine ?: return KdfFactory.aesKdf
}
val numberKeyEncryptionRoundsAsString: String
get() = numberKeyEncryptionRounds.toString()
val kdfEngine: KdfEngine?
get() = pwDatabaseV3?.kdfEngine ?: pwDatabaseV4?.kdfEngine
var numberKeyEncryptionRounds: Long
get() = pwDatabaseV3?.numberKeyEncryptionRounds ?: pwDatabaseV4?.numberKeyEncryptionRounds ?: 0
@@ -123,9 +107,6 @@ class Database {
pwDatabaseV4?.numberKeyEncryptionRounds = numberRounds
}
val memoryUsageAsString: String
get() = memoryUsage.toString()
var memoryUsage: Long
get() {
return pwDatabaseV4?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE.toLong()
@@ -134,9 +115,6 @@ class Database {
pwDatabaseV4?.memoryUsage = memory
}
val parallelismAsString: String
get() = parallelism.toString()
var parallelism: Int
get() = pwDatabaseV4?.parallelism ?: KdfEngine.UNKNOWN_VALUE
set(parallelism) {
@@ -161,6 +139,25 @@ class Database {
return null
}
val manageHistory: Boolean
get() = pwDatabaseV4 != null
var historyMaxItems: Int
get() {
return pwDatabaseV4?.historyMaxItems ?: 0
}
set(value) {
pwDatabaseV4?.historyMaxItems = value
}
var historyMaxSize: Long
get() {
return pwDatabaseV4?.historyMaxSize ?: 0
}
set(value) {
pwDatabaseV4?.historyMaxSize = value
}
/**
* Determine if RecycleBin is available or not for this version of database
* @return true if RecycleBin available
@@ -462,12 +459,12 @@ class Database {
if (pwDatabaseV4?.kdfParameters?.uuid != kdfEngine.defaultParameters.uuid)
pwDatabaseV4?.kdfParameters = kdfEngine.defaultParameters
numberKeyEncryptionRounds = kdfEngine.defaultKeyRounds
memoryUsage = kdfEngine.getDefaultMemoryUsage()
parallelism = kdfEngine.getDefaultParallelism()
memoryUsage = kdfEngine.defaultMemoryUsage
parallelism = kdfEngine.defaultParallelism
}
fun getKeyDerivationName(resources: Resources): String {
return kdfEngine.getName(resources)
return kdfEngine?.getName(resources) ?: ""
}
fun validatePasswordEncoding(key: String?): Boolean {
@@ -702,31 +699,28 @@ class Database {
}
}
fun addHistoryBackupTo(entry: EntryVersioned): EntryVersioned {
val backupEntry = EntryVersioned(entry)
fun removeOldestHistory(entry: EntryVersioned) {
entry.addBackupToHistory()
// Remove oldest backup if more than max items or max memory
// Remove oldest history if more than max items or max memory
pwDatabaseV4?.let {
val history = entry.getHistory()
val maxItems = it.historyMaxItems
val maxItems = historyMaxItems
if (maxItems >= 0) {
while (history.size > maxItems) {
entry.removeOldestEntryFromHistory()
}
}
val maxSize = it.historyMaxSize
val maxSize = historyMaxSize
if (maxSize >= 0) {
while (true) {
var histSize: Long = 0
for (backup in history) {
histSize += backup.size
var historySize: Long = 0
for (entryHistory in history) {
historySize += entryHistory.getSize()
}
if (histSize > maxSize) {
if (historySize > maxSize) {
entry.removeOldestEntryFromHistory()
} else {
break
@@ -734,8 +728,6 @@ class Database {
}
}
}
return backupEntry
}
companion object : SingletonHolder<Database>(::Database) {

View File

@@ -15,26 +15,26 @@ class EntryVersioned : NodeVersioned, PwEntryInterface<GroupVersioned> {
var pwEntryV4: PwEntryV4? = null
private set
fun updateWith(entry: EntryVersioned) {
fun updateWith(entry: EntryVersioned, copyHistory: Boolean = true) {
entry.pwEntryV3?.let {
this.pwEntryV3?.updateWith(it)
}
entry.pwEntryV4?.let {
this.pwEntryV4?.updateWith(it)
this.pwEntryV4?.updateWith(it, copyHistory)
}
}
/**
* Use this constructor to copy an Entry with exact same values
*/
constructor(entry: EntryVersioned) {
constructor(entry: EntryVersioned, copyHistory: Boolean = true) {
if (entry.pwEntryV3 != null) {
this.pwEntryV3 = PwEntryV3()
}
if (entry.pwEntryV4 != null) {
this.pwEntryV4 = PwEntryV4()
}
updateWith(entry)
updateWith(entry, copyHistory)
}
constructor(entry: PwEntryV3) {
@@ -258,20 +258,31 @@ class EntryVersioned : NodeVersioned, PwEntryInterface<GroupVersioned> {
pwEntryV4?.stopToManageFieldReferences()
}
fun addBackupToHistory() {
pwEntryV4?.let {
val entryHistory = PwEntryV4()
entryHistory.updateWith(it)
it.addEntryToHistory(entryHistory)
fun getHistory(): ArrayList<EntryVersioned> {
val history = ArrayList<EntryVersioned>()
val entryV4History = pwEntryV4?.history ?: ArrayList()
for (entryHistory in entryV4History) {
history.add(EntryVersioned(entryHistory))
}
return history
}
fun addEntryToHistory(entry: EntryVersioned) {
entry.pwEntryV4?.let {
pwEntryV4?.addEntryToHistory(it)
}
}
fun removeAllHistory() {
pwEntryV4?.removeAllHistory()
}
fun removeOldestEntryFromHistory() {
pwEntryV4?.removeOldestEntryFromHistory()
}
fun getHistory(): ArrayList<PwEntryV4> {
return pwEntryV4?.history ?: ArrayList()
fun getSize(): Long {
return pwEntryV4?.size ?: 0L
}
fun containsCustomData(): Boolean {

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.element
import android.util.Log
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException
import com.kunzisoft.keepass.database.exception.KeyFileEmptyException
import com.kunzisoft.keepass.utils.MemoryUtil
@@ -39,6 +40,10 @@ abstract class PwDatabase<Group : PwGroup<*, Group, Entry>, Entry : PwEntry<Grou
// Algorithm used to encrypt the database
protected var algorithm: PwEncryptionAlgorithm? = null
abstract val kdfEngine: KdfEngine?
abstract val kdfAvailableList: List<KdfEngine>
var masterKey = ByteArray(32)
var finalKey: ByteArray? = null
protected set

View File

@@ -20,6 +20,8 @@
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException
import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.IOException
@@ -28,18 +30,25 @@ import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
/**
* @author Naomaru Itoi <nao></nao>@phoneid.org>
* @author Bill Zwicky <wrzwicky></wrzwicky>@pobox.com>
* @author Dominik Reichl <dominik.reichl></dominik.reichl>@t-online.de>
*/
class PwDatabaseV3 : PwDatabase<PwGroupV3, PwEntryV3>() {
private var numKeyEncRounds: Int = 0
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
override val version: String
get() = "KeePass 1"
init {
kdfListV3.add(KdfFactory.aesKdf)
}
override val kdfEngine: KdfEngine?
get() = kdfListV3[0]
override val kdfAvailableList: List<KdfEngine>
get() = kdfListV3
override val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
get() {
val list = ArrayList<PwEncryptionAlgorithm>()

View File

@@ -26,9 +26,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.crypto.keyDerivation.*
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException
import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.PwCompressionAlgorithm
@@ -51,6 +49,7 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
private var dataEngine: CipherEngine = AesEngine()
var compressionAlgorithm = PwCompressionAlgorithm.Gzip
var kdfParameters: KdfParameters? = null
private var kdfV4List: MutableList<KdfEngine> = ArrayList()
private var numKeyEncRounds: Long = 0
var publicCustomData = VariantDictionary()
@@ -93,6 +92,11 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
var localizedAppName = "KeePassDX" // TODO resource
init {
kdfV4List.add(KdfFactory.aesKdf)
kdfV4List.add(KdfFactory.argon2Kdf)
}
constructor()
constructor(databaseName: String) {
@@ -107,6 +111,31 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
override val version: String
get() = "KeePass 2"
override val kdfEngine: KdfEngine?
get() = try {
getEngineV4(kdfParameters)
} catch (unknownKDF: UnknownKDF) {
Log.i(TAG, "Unable to retrieve KDF engine", unknownKDF)
null
}
override val kdfAvailableList: List<KdfEngine>
get() = kdfV4List
@Throws(UnknownKDF::class)
fun getEngineV4(kdfParameters: KdfParameters?): KdfEngine {
val unknownKDFException = UnknownKDF()
if (kdfParameters == null) {
throw unknownKDFException
}
for (engine in kdfV4List) {
if (engine.uuid == kdfParameters.uuid) {
return engine
}
}
throw unknownKDFException
}
override val availableEncryptionAlgorithms: List<PwEncryptionAlgorithm>
get() {
val list = ArrayList<PwEncryptionAlgorithm>()
@@ -116,45 +145,45 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
return list
}
val kdfEngine: KdfEngine?
get() {
return try {
KdfFactory.getEngineV4(kdfParameters)
} catch (unknownKDF: UnknownKDF) {
Log.i(TAG, "Unable to retrieve KDF engine", unknownKDF)
null
}
}
override var numberKeyEncryptionRounds: Long
get() {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
numKeyEncRounds = kdfEngine!!.getKeyRounds(kdfParameters!!)
numKeyEncRounds = kdfEngine.getKeyRounds(kdfParameters!!)
return numKeyEncRounds
}
@Throws(NumberFormatException::class)
set(rounds) {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
kdfEngine!!.setKeyRounds(kdfParameters!!, rounds)
kdfEngine.setKeyRounds(kdfParameters!!, rounds)
numKeyEncRounds = rounds
}
var memoryUsage: Long
get() = if (kdfEngine != null && kdfParameters != null) {
kdfEngine!!.getMemoryUsage(kdfParameters!!)
} else KdfEngine.UNKNOWN_VALUE.toLong()
get() {
val kdfEngine = kdfEngine
return if (kdfEngine != null && kdfParameters != null) {
kdfEngine.getMemoryUsage(kdfParameters!!)
} else KdfEngine.UNKNOWN_VALUE.toLong()
}
set(memory) {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
kdfEngine!!.setMemoryUsage(kdfParameters!!, memory)
kdfEngine.setMemoryUsage(kdfParameters!!, memory)
}
var parallelism: Int
get() = if (kdfEngine != null && kdfParameters != null) {
kdfEngine!!.getParallelism(kdfParameters!!)
} else KdfEngine.UNKNOWN_VALUE
get() {
val kdfEngine = kdfEngine
return if (kdfEngine != null && kdfParameters != null) {
kdfEngine.getParallelism(kdfParameters!!)
} else KdfEngine.UNKNOWN_VALUE
}
set(parallelism) {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
kdfEngine!!.setParallelism(kdfParameters!!, parallelism)
kdfEngine.setParallelism(kdfParameters!!, parallelism)
}
override val passwordEncoding: String
@@ -227,7 +256,7 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
fun makeFinalKey(masterSeed: ByteArray) {
kdfParameters?.let { keyDerivationFunctionParameters ->
val kdfEngine = KdfFactory.getEngineV4(keyDerivationFunctionParameters)
val kdfEngine = getEngineV4(keyDerivationFunctionParameters)
var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters)
if (transformedMasterKey.size != 32) {
@@ -360,9 +389,7 @@ class PwDatabaseV4 : PwDatabase<PwGroupV4, PwEntryV4> {
}
addGroupTo(recycleBinGroup, rootGroup)
recycleBinUUID = recycleBinGroup.id
recycleBinGroup.lastModificationTime.date?.let {
recycleBinChanged = it
}
recycleBinChanged = recycleBinGroup.lastModificationTime.date
}
}

View File

@@ -19,14 +19,12 @@
*/
package com.kunzisoft.keepass.database.element
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ConfigurationCompat
import com.kunzisoft.keepass.utils.Types
import java.util.Arrays
import java.util.Calendar
import java.util.Date
import java.util.*
/**
* Converting from the C Date format to the Java data format is
@@ -34,14 +32,14 @@ import java.util.Date
*/
class PwDate : Parcelable {
private var jDate: Date? = null
private var jDate: Date = Date()
private var jDateBuilt = false
@Transient
private var cDate: ByteArray? = null
@Transient
private var cDateBuilt = false
val date: Date?
val date: Date
get() {
if (!jDateBuilt) {
jDate = readTime(cDate, 0, calendar)
@@ -68,9 +66,7 @@ class PwDate : Parcelable {
}
constructor(source: PwDate) {
if (source.jDate != null) {
this.jDate = Date(source.jDate!!.time)
}
this.jDate = Date(source.jDate.time)
this.jDateBuilt = source.jDateBuilt
if (source.cDate != null) {
@@ -106,6 +102,10 @@ class PwDate : Parcelable {
return 0
}
fun getDateTimeString(resources: Resources): String {
return Companion.getDateTimeString(resources, this.date)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(date)
dest.writeByte((if (jDateBuilt) 1 else 0).toByte())
@@ -135,7 +135,7 @@ class PwDate : Parcelable {
}
override fun hashCode(): Int {
var result = jDate?.hashCode() ?: 0
var result = jDate.hashCode()
result = 31 * result + jDateBuilt.hashCode()
result = 31 * result + (cDate?.contentHashCode() ?: 0)
result = 31 * result + cDateBuilt.hashCode()
@@ -280,5 +280,13 @@ class PwDate : Parcelable {
cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND)
}
fun getDateTimeString(resources: Resources, date: Date): String {
return java.text.DateFormat.getDateTimeInstance(
java.text.DateFormat.MEDIUM,
java.text.DateFormat.MEDIUM,
ConfigurationCompat.getLocales(resources.configuration)[0])
.format(date)
}
}
}

View File

@@ -129,7 +129,7 @@ class PwEntryV4 : PwEntry<PwGroupV4, PwEntryV4>, PwNodeV4Interface {
* Update with deep copy of each entry element
* @param source
*/
fun updateWith(source: PwEntryV4) {
fun updateWith(source: PwEntryV4, copyHistory: Boolean = true) {
super.updateWith(source)
iconCustom = PwIconCustom(source.iconCustom)
usageCount = source.usageCount
@@ -146,7 +146,8 @@ class PwEntryV4 : PwEntry<PwGroupV4, PwEntryV4>, PwNodeV4Interface {
overrideURL = source.overrideURL
autoType = AutoType(source.autoType)
history.clear()
history.addAll(source.history)
if (copyHistory)
history.addAll(source.history)
url = source.url
additional = source.additional
tags = source.tags
@@ -287,6 +288,10 @@ class PwEntryV4 : PwEntry<PwGroupV4, PwEntryV4>, PwNodeV4Interface {
history.add(entry)
}
fun removeAllHistory() {
history.clear()
}
fun removeOldestEntryFromHistory() {
var min: Date? = null
var index = -1
@@ -294,7 +299,7 @@ class PwEntryV4 : PwEntry<PwGroupV4, PwEntryV4>, PwNodeV4Interface {
for (i in history.indices) {
val entry = history[i]
val lastMod = entry.lastModificationTime.date
if (min == null || lastMod == null || lastMod.before(min)) {
if (min == null || lastMod.before(min)) {
index = i
min = lastMod
}

View File

@@ -90,7 +90,7 @@ abstract class PwNode<IdType, Parent : PwGroupInterface<Parent, Entry>, Entry :
final override var isExpires: Boolean
// If expireDate is before NEVER_EXPIRE date less 1 month (to be sure)
get() = expiryTime.date
?.before(LocalDate.fromDateFields(PwDate.NEVER_EXPIRE).minusMonths(1).toDate()) ?: true
.before(LocalDate.fromDateFields(PwDate.NEVER_EXPIRE).minusMonths(1).toDate())
set(value) {
if (!value) {
expiryTime = PwDate.PW_NEVER_EXPIRE

View File

@@ -51,10 +51,10 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() {
// version < FILE_VERSION_32_4)
var transformSeed: ByteArray?
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.ParamSeed)
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED)
private set(seed) {
assignAesKdfEngineIfNotExists()
databaseV4.kdfParameters?.setByteArray(AesKdf.ParamSeed, seed)
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, seed)
}
object PwDbHeaderV4Fields {
@@ -229,7 +229,9 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() {
}
private fun assignAesKdfEngineIfNotExists() {
if (databaseV4.kdfParameters == null || databaseV4.kdfParameters!!.uuid != KdfFactory.aesKdf.uuid) {
val kdfParams = databaseV4.kdfParameters
if (kdfParams == null
|| kdfParams.uuid != KdfFactory.aesKdf.uuid) {
databaseV4.kdfParameters = KdfFactory.aesKdf.defaultParameters
}
}
@@ -246,7 +248,7 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() {
private fun setTransformRound(roundsByte: ByteArray?) {
assignAesKdfEngineIfNotExists()
val rounds = LEDataInputStream.readLong(roundsByte!!, 0)
databaseV4.kdfParameters?.setUInt64(AesKdf.ParamRounds, rounds)
databaseV4.kdfParameters?.setUInt64(AesKdf.PARAM_ROUNDS, rounds)
databaseV4.numberKeyEncryptionRounds = rounds
}

View File

@@ -259,7 +259,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt
}
try {
val kdf = KdfFactory.getEngineV4(mDatabaseV4.kdfParameters)
val kdf = mDatabaseV4.getEngineV4(mDatabaseV4.kdfParameters)
kdf.randomize(mDatabaseV4.kdfParameters!!)
} catch (unknownKDF: UnknownKDF) {
Log.e(TAG, "Unable to retrieve header", unknownKDF)

View File

@@ -48,10 +48,7 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.settings.preference.DialogListExplanationPreference
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
import com.kunzisoft.keepass.settings.preference.InputNumberPreference
import com.kunzisoft.keepass.settings.preference.InputTextPreference
import com.kunzisoft.keepass.settings.preference.*
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener {
@@ -61,9 +58,9 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
private var mCount = 0
private var mRoundPref: InputNumberPreference? = null
private var mMemoryPref: InputNumberPreference? = null
private var mParallelismPref: InputNumberPreference? = null
private var mRoundPref: InputKdfNumberPreference? = null
private var mMemoryPref: InputKdfNumberPreference? = null
private var mParallelismPref: InputKdfNumberPreference? = null
enum class Screen {
APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, DATABASE, APPEARANCE
@@ -74,7 +71,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
activity?.let { activity ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val autoFillEnablePreference: SwitchPreference? = findPreference<SwitchPreference>(getString(R.string.settings_autofill_enable_key))
val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key))
if (autoFillEnablePreference != null) {
val autofillManager = activity.getSystemService(AutofillManager::class.java)
autoFillEnablePreference.isChecked = autofillManager != null
@@ -139,7 +136,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
setPreferencesFromResource(R.xml.preferences_form_filling, rootKey)
activity?.let { activity ->
val autoFillEnablePreference: SwitchPreference? = findPreference<SwitchPreference>(getString(R.string.settings_autofill_enable_key))
val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val autofillManager = activity.getSystemService(AutofillManager::class.java)
if (autofillManager != null && autofillManager.hasEnabledAutofillServices())
@@ -217,7 +214,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
setPreferencesFromResource(R.xml.preferences_advanced_unlock, rootKey)
activity?.let { activity ->
val biometricUnlockEnablePreference: SwitchPreference? = findPreference<SwitchPreference>(getString(R.string.biometric_unlock_enable_key))
val biometricUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.biometric_unlock_enable_key))
// < M solve verifyError exception
var biometricUnlockSupported = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -240,7 +237,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
}
}
val deleteKeysFingerprints: Preference? = findPreference<Preference>(getString(R.string.biometric_delete_all_key_key))
val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key))
if (!biometricUnlockSupported) {
deleteKeysFingerprints?.isEnabled = false
} else {
@@ -338,10 +335,10 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
if (mDatabase.loaded) {
val dbGeneralPrefCategory: PreferenceCategory? = findPreference<PreferenceCategory>(getString(R.string.database_general_key))
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_general_key))
// Db name
val dbNamePref: InputTextPreference? = findPreference<InputTextPreference>(getString(R.string.database_name_key))
val dbNamePref: InputTextPreference? = findPreference(getString(R.string.database_name_key))
if (mDatabase.containsName()) {
dbNamePref?.summary = mDatabase.name
} else {
@@ -349,7 +346,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
}
// Db description
val dbDescriptionPref: InputTextPreference? = findPreference<InputTextPreference>(getString(R.string.database_description_key))
val dbDescriptionPref: InputTextPreference? = findPreference(getString(R.string.database_description_key))
if (mDatabase.containsDescription()) {
dbDescriptionPref?.summary = mDatabase.description
} else {
@@ -357,7 +354,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
}
// Recycle bin
val recycleBinPref: SwitchPreference? = findPreference<SwitchPreference>(getString(R.string.recycle_bin_key))
val recycleBinPref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_key))
// TODO Recycle
dbGeneralPrefCategory?.removePreference(recycleBinPref) // To delete
if (mDatabase.isRecycleBinAvailable) {
@@ -371,6 +368,17 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
findPreference<Preference>(getString(R.string.database_version_key))
?.summary = mDatabase.getVersion()
findPreference<PreferenceCategory>(getString(R.string.database_history_key))
?.isVisible = mDatabase.manageHistory == true
// Max history items
findPreference<InputNumberPreference>(getString(R.string.max_history_items_key))
?.summary = mDatabase.historyMaxItems.toString()
// Max history size
findPreference<InputNumberPreference>(getString(R.string.max_history_size_key))
?.summary = mDatabase.historyMaxSize.toString()
// Encryption Algorithm
findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))
?.summary = mDatabase.getEncryptionAlgorithmName(resources)
@@ -380,16 +388,16 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
?.summary = mDatabase.getKeyDerivationName(resources)
// Round encryption
mRoundPref = findPreference<InputNumberPreference>(getString(R.string.transform_rounds_key))
mRoundPref?.summary = mDatabase.numberKeyEncryptionRoundsAsString
mRoundPref = findPreference(getString(R.string.transform_rounds_key))
mRoundPref?.summary = mDatabase.numberKeyEncryptionRounds.toString()
// Memory Usage
mMemoryPref = findPreference<InputNumberPreference>(getString(R.string.memory_usage_key))
mMemoryPref?.summary = mDatabase.memoryUsageAsString
mMemoryPref = findPreference(getString(R.string.memory_usage_key))
mMemoryPref?.summary = mDatabase.memoryUsage.toString()
// Parallelism
mParallelismPref = findPreference<InputNumberPreference>(getString(R.string.parallelism_key))
mParallelismPref?.summary = mDatabase.parallelismAsString
mParallelismPref = findPreference(getString(R.string.parallelism_key))
mParallelismPref?.summary = mDatabase.parallelism.toString()
} else {
Log.e(javaClass.name, "Database isn't ready")
@@ -397,7 +405,7 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
}
private fun allowCopyPassword() {
val copyPasswordPreference: SwitchPreference? = findPreference<SwitchPreference>(getString(R.string.allow_copy_password_key))
val copyPasswordPreference: SwitchPreference? = findPreference(getString(R.string.allow_copy_password_key))
copyPasswordPreference?.setOnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean && context != null) {
val message = getString(R.string.allow_copy_password_warning) +
@@ -461,6 +469,12 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen
preference.key == getString(R.string.database_description_key) -> {
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.max_history_items_key) -> {
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.max_history_size_key) -> {
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
}
preference.key == getString(R.string.encryption_algorithm_key) -> {
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key)
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import android.util.AttributeSet
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
: InputTextPreference(context, attrs, defStyleAttr, defStyleRes) {
override fun setSummary(summary: CharSequence) {
if (summary == UNKNOWN_VALUE_STRING) {
isEnabled = false
super.setSummary("")
} else {
isEnabled = true
super.setSummary(summary)
}
}
companion object {
const val UNKNOWN_VALUE_STRING = KdfEngine.UNKNOWN_VALUE.toString()
}
}

View File

@@ -20,66 +20,28 @@
package com.kunzisoft.keepass.settings.preference
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
class InputNumberPreference @JvmOverloads constructor(context: Context,
open class InputNumberPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
: InputTextExplanationPreference(context, attrs, defStyleAttr, defStyleRes) {
// Save to Shared Preferences
var number: Long = 0
set(number) {
field = number
persistLong(number)
}
: InputTextPreference(context, attrs, defStyleAttr, defStyleRes) {
override fun getDialogLayoutResource(): Int {
return R.layout.pref_dialog_numbers
return R.layout.pref_dialog_input_numbers
}
override fun setSummary(summary: CharSequence) {
if (summary == KdfEngine.UNKNOWN_VALUE_STRING) {
isEnabled = false
if (summary == INFINITE_VALUE_STRING) {
super.setSummary("")
} else {
isEnabled = true
super.setSummary(summary)
}
}
override fun onGetDefaultValue(a: TypedArray?, index: Int): Any {
// Default value from attribute. Fallback value is set to 0.
return a?.getInt(index, 0) ?: 0
companion object {
const val INFINITE_VALUE_STRING = "-1"
}
override fun onSetInitialValue(restorePersistedValue: Boolean,
defaultValue: Any?) {
// Read the value. Use the default value if it is not possible.
var numberValue: Long
if (!restorePersistedValue) {
numberValue = 100000
if (defaultValue is String) {
numberValue = java.lang.Long.parseLong(defaultValue)
}
if (defaultValue is Int) {
numberValue = defaultValue.toLong()
}
try {
numberValue = defaultValue as Long
} catch (e: Exception) {
e.printStackTrace()
}
} else {
numberValue = getPersistedLong(this.number)
}
number = numberValue
}
}

View File

@@ -1,32 +0,0 @@
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 InputTextExplanationPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
var explanation: String? = null
init {
val styleAttributes = context.theme.obtainStyledAttributes(
attrs,
R.styleable.explanationDialog,
0, 0)
try {
explanation = styleAttributes.getString(R.styleable.explanationDialog_explanations)
} finally {
styleAttributes.recycle()
}
}
override fun getDialogLayoutResource(): Int {
return R.layout.pref_dialog_input_text_explanation
}
}

View File

@@ -6,10 +6,10 @@ import android.util.AttributeSet
import com.kunzisoft.keepass.R
class InputTextPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
open class InputTextPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.dialogPreferenceStyle,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
override fun getDialogLayoutResource(): Int {

View File

@@ -23,7 +23,7 @@ import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.tasks.ActionRunnable
class DatabaseDescriptionPreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDialogFragmentCompat() {
class DatabaseDescriptionPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
@@ -48,10 +48,13 @@ class DatabaseDescriptionPreferenceDialogFragmentCompat : InputDatabaseSavePrefe
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val descriptionToShow = mNewDescription
if (!result.isSuccess) {
database?.assignDescription(mOldDescription)
}
val descriptionToShow =
if (result.isSuccess) {
mNewDescription
} else {
database?.assignDescription(mOldDescription)
mOldDescription
}
preference.summary = descriptionToShow
}
}

View File

@@ -56,17 +56,21 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
}
override fun onDialogClosed(positiveResult: Boolean) {
if (database != null && positiveResult && database!!.allowEncryptionAlgorithmModification()) {
if (algorithmSelected != null) {
val newAlgorithm = algorithmSelected
val oldAlgorithm = database?.encryptionAlgorithm
newAlgorithm?.let {
database?.assignEncryptionAlgorithm(it)
if (positiveResult) {
database?.let { database ->
if (database.allowEncryptionAlgorithmModification()) {
if (algorithmSelected != null) {
val newAlgorithm = algorithmSelected
val oldAlgorithm = database.encryptionAlgorithm
newAlgorithm?.let {
database.assignEncryptionAlgorithm(it)
}
if (oldAlgorithm != null && newAlgorithm != null)
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newAlgorithm, oldAlgorithm)
}
}
if (oldAlgorithm != null && newAlgorithm != null)
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newAlgorithm, oldAlgorithm)
}
}
@@ -82,11 +86,13 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
: ActionRunnable() {
override fun onFinishRun(result: Result) {
var algorithmToShow = mNewAlgorithm
if (!result.isSuccess) {
database?.assignEncryptionAlgorithm(mOldAlgorithm)
algorithmToShow = mOldAlgorithm
}
val algorithmToShow =
if (result.isSuccess) {
mNewAlgorithm
} else {
database?.assignEncryptionAlgorithm(mOldAlgorithm)
mOldAlgorithm
}
preference.summary = algorithmToShow.getName(settingsResources)
}
}

View File

@@ -52,21 +52,24 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
recyclerView.adapter = kdfAdapter
database?.let { database ->
kdfEngineSelected = database.kdfEngine
if (kdfEngineSelected != null)
kdfAdapter.setItems(database.availableKdfEngines, kdfEngineSelected!!)
kdfEngineSelected = database.kdfEngine?.apply {
kdfAdapter.setItems(database.availableKdfEngines, this)
}
}
}
}
override fun onDialogClosed(positiveResult: Boolean) {
if (database != null && positiveResult && database!!.allowKdfModification()) {
if (kdfEngineSelected != null) {
val newKdfEngine = kdfEngineSelected!!
val oldKdfEngine = database!!.kdfEngine
database?.assignKdfEngine(newKdfEngine)
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newKdfEngine, oldKdfEngine)
if (positiveResult) {
database?.let { database ->
if (database.allowKdfModification()) {
val newKdfEngine = kdfEngineSelected
val oldKdfEngine = database.kdfEngine
if (newKdfEngine != null && oldKdfEngine != null) {
database.assignKdfEngine(newKdfEngine)
actionInUIThreadAfterSaveDatabase = AfterDescriptionSave(newKdfEngine, oldKdfEngine)
}
}
}
}
@@ -94,17 +97,19 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val kdfEngineToShow = mNewKdfEngine
if (!result.isSuccess) {
database?.assignKdfEngine(mOldKdfEngine)
}
val kdfEngineToShow =
if (result.isSuccess) {
mNewKdfEngine
} else {
database?.assignKdfEngine(mOldKdfEngine)
mOldKdfEngine
}
preference.summary = kdfEngineToShow.getName(settingsResources)
roundPreference?.summary = kdfEngineToShow.defaultKeyRounds.toString()
// Disable memory and parallelism if not available
memoryPreference?.summary = kdfEngineToShow.getDefaultMemoryUsage().toString()
parallelismPreference?.summary = kdfEngineToShow.getDefaultParallelism().toString()
memoryPreference?.summary = kdfEngineToShow.defaultMemoryUsage.toString()
parallelismPreference?.summary = kdfEngineToShow.defaultParallelism.toString()
}
}

View File

@@ -23,7 +23,7 @@ import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.tasks.ActionRunnable
class DatabaseNamePreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDialogFragmentCompat() {
class DatabaseNamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
@@ -32,12 +32,14 @@ class DatabaseNamePreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDi
}
override fun onDialogClosed(positiveResult: Boolean) {
if (database != null && positiveResult) {
val newName = inputText
val oldName = database!!.name
database?.assignName(newName)
if (positiveResult) {
database?.let { database ->
val newName = inputText
val oldName = database.name
database.assignName(newName)
actionInUIThreadAfterSaveDatabase = AfterNameSave(newName, oldName)
actionInUIThreadAfterSaveDatabase = AfterNameSave(newName, oldName)
}
}
super.onDialogClosed(positiveResult)
@@ -48,10 +50,13 @@ class DatabaseNamePreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDi
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val nameToShow = mNewName
if (!result.isSuccess) {
database?.assignName(mOldName)
}
val nameToShow =
if (result.isSuccess) {
mNewName
} else {
database?.assignName(mOldName)
mOldName
}
preference.summary = nameToShow
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.view.View
import android.widget.EditText
import com.kunzisoft.keepass.R
open class InputDatabaseSavePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
private var inputTextView: EditText? = null
var inputText: String
get() = this.inputTextView?.text?.toString() ?: ""
set(inputText) {
if (inputTextView != null) {
this.inputTextView?.setText(inputText)
this.inputTextView?.setSelection(this.inputTextView!!.text.length)
}
}
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
inputTextView = view.findViewById(R.id.input_text)
}
}

View File

@@ -19,36 +19,71 @@
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.view.View
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.preference.PreferenceDialogFragmentCompat
import android.view.View
import android.widget.TextView
import com.kunzisoft.keepass.R
abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
private var inputTextView: EditText? = null
private var textExplanationView: TextView? = null
private var switchElementView: CompoundButton? = null
var inputText: String
get() = this.inputTextView?.text?.toString() ?: ""
set(inputText) {
if (inputTextView != null) {
this.inputTextView?.setText(inputText)
this.inputTextView?.setSelection(this.inputTextView!!.text.length)
}
}
var explanationText: String?
get() = textExplanationView?.text?.toString() ?: ""
set(explanationText) {
if (explanationText != null && explanationText.isNotEmpty()) {
textExplanationView?.text = explanationText
textExplanationView?.visibility = View.VISIBLE
} else {
textExplanationView?.text = explanationText
textExplanationView?.visibility = View.VISIBLE
textExplanationView?.apply {
if (explanationText != null && explanationText.isNotEmpty()) {
text = explanationText
visibility = View.VISIBLE
} else {
text = ""
visibility = View.GONE
}
}
}
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
inputTextView = view.findViewById(R.id.input_text)
textExplanationView = view.findViewById(R.id.explanation_text)
textExplanationView?.visibility = View.GONE
switchElementView = view.findViewById(R.id.switch_element)
switchElementView?.visibility = View.GONE
}
fun setInoutText(@StringRes inputTextId: Int) {
inputText = getString(inputTextId)
}
fun showInputText(show: Boolean) {
inputTextView?.visibility = if (show) View.VISIBLE else View.GONE
}
fun setExplanationText(@StringRes explanationTextId: Int) {
explanationText = getString(explanationTextId)
}
fun setSwitchAction(onCheckedChange: ((isChecked: Boolean)-> Unit)?, defaultChecked: Boolean) {
switchElementView?.visibility = if (onCheckedChange == null) View.GONE else View.VISIBLE
switchElementView?.isChecked = defaultChecked
inputTextView?.visibility = if (defaultChecked) View.VISIBLE else View.GONE
switchElementView?.setOnCheckedChangeListener { _, isChecked ->
onCheckedChange?.invoke(isChecked)
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.tasks.ActionRunnable
class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.max_history_items_summary)
database?.historyMaxItems?.let { maxItemsDatabase ->
inputText = maxItemsDatabase.toString()
setSwitchAction({ isChecked ->
inputText = if (!isChecked) {
INFINITE_MAX_HISTORY_ITEMS.toString()
} else
DEFAULT_MAX_HISTORY_ITEMS.toString()
showInputText(isChecked)
}, maxItemsDatabase > INFINITE_MAX_HISTORY_ITEMS)
}
}
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
database?.let { database ->
var maxHistoryItems: Int = try {
inputText.toInt()
} catch (e: NumberFormatException) {
DEFAULT_MAX_HISTORY_ITEMS
}
if (maxHistoryItems < INFINITE_MAX_HISTORY_ITEMS) {
maxHistoryItems = INFINITE_MAX_HISTORY_ITEMS
}
val oldMaxHistoryItems = database.historyMaxItems
database.historyMaxItems = maxHistoryItems
actionInUIThreadAfterSaveDatabase = AfterMaxHistoryItemsSave(maxHistoryItems, oldMaxHistoryItems)
}
}
super.onDialogClosed(positiveResult)
}
private inner class AfterMaxHistoryItemsSave(private val mNewMaxHistoryItems: Int,
private val mOldMaxHistoryItems: Int)
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val maxHistoryItemsToShow =
if (result.isSuccess) {
mNewMaxHistoryItems
} else {
database?.historyMaxItems = mOldMaxHistoryItems
mOldMaxHistoryItems
}
preference.summary = maxHistoryItemsToShow.toString()
}
}
companion object {
const val DEFAULT_MAX_HISTORY_ITEMS = 10
const val INFINITE_MAX_HISTORY_ITEMS = -1
fun newInstance(key: String): MaxHistoryItemsPreferenceDialogFragmentCompat {
val fragment = MaxHistoryItemsPreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX 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.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.tasks.ActionRunnable
class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.max_history_size_summary)
database?.historyMaxSize?.let { maxItemsDatabase ->
inputText = maxItemsDatabase.toString()
setSwitchAction({ isChecked ->
inputText = if (!isChecked) {
INFINITE_MAX_HISTORY_SIZE.toString()
} else
DEFAULT_MAX_HISTORY_SIZE.toString()
showInputText(isChecked)
}, maxItemsDatabase > INFINITE_MAX_HISTORY_SIZE)
}
}
override fun onDialogClosed(positiveResult: Boolean) {
if (positiveResult) {
database?.let { database ->
var maxHistorySize: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
DEFAULT_MAX_HISTORY_SIZE
}
if (maxHistorySize < INFINITE_MAX_HISTORY_SIZE) {
maxHistorySize = INFINITE_MAX_HISTORY_SIZE
}
val oldMaxHistorySize = database.historyMaxSize
database.historyMaxSize = maxHistorySize
actionInUIThreadAfterSaveDatabase = AfterMaxHistorySizeSave(maxHistorySize, oldMaxHistorySize)
}
}
super.onDialogClosed(positiveResult)
}
private inner class AfterMaxHistorySizeSave(private val mNewMaxHistorySize: Long,
private val mOldMaxHistorySize: Long)
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val maxHistorySizeToShow =
if (result.isSuccess) {
mNewMaxHistorySize
} else {
database?.historyMaxSize = mOldMaxHistorySize
mOldMaxHistorySize
}
preference.summary = maxHistorySizeToShow.toString()
}
}
companion object {
const val DEFAULT_MAX_HISTORY_SIZE = 134217728L
const val INFINITE_MAX_HISTORY_SIZE = -1L
fun newInstance(key: String): MaxHistorySizePreferenceDialogFragmentCompat {
val fragment = MaxHistorySizePreferenceDialogFragmentCompat()
val bundle = Bundle(1)
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}

View File

@@ -21,38 +21,36 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.tasks.ActionRunnable
class MemoryUsagePreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDialogFragmentCompat() {
class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.memory_usage_explanation)
inputText = database?.memoryUsageAsString ?: ""
inputText = database?.memoryUsage?.toString()?: MIN_MEMORY_USAGE.toString()
}
override fun onDialogClosed(positiveResult: Boolean) {
if (database != null && positiveResult) {
var memoryUsage: Long
try {
val stringMemory = inputText
memoryUsage = java.lang.Long.parseLong(stringMemory)
} catch (e: NumberFormatException) {
Toast.makeText(context, R.string.error_rounds_not_number, Toast.LENGTH_LONG).show() // TODO change error
return
if (positiveResult) {
database?.let { database ->
var memoryUsage: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
MIN_MEMORY_USAGE
}
if (memoryUsage < MIN_MEMORY_USAGE) {
memoryUsage = MIN_MEMORY_USAGE
}
// TODO Max Memory
val oldMemoryUsage = database.memoryUsage
database.memoryUsage = memoryUsage
actionInUIThreadAfterSaveDatabase = AfterMemorySave(memoryUsage, oldMemoryUsage)
}
if (memoryUsage < 1) {
memoryUsage = 1
}
val oldMemoryUsage = database!!.memoryUsage
database!!.memoryUsage = memoryUsage
actionInUIThreadAfterSaveDatabase = AfterMemorySave(memoryUsage, oldMemoryUsage)
}
super.onDialogClosed(positiveResult)
@@ -63,16 +61,21 @@ class MemoryUsagePreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDia
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val memoryToShow = mNewMemory
if (!result.isSuccess) {
database?.memoryUsage = mOldMemory
}
val memoryToShow =
if (result.isSuccess) {
mNewMemory
} else {
database?.memoryUsage = mOldMemory
mOldMemory
}
preference.summary = memoryToShow.toString()
}
}
companion object {
const val MIN_MEMORY_USAGE = 1L
fun newInstance(key: String): MemoryUsagePreferenceDialogFragmentCompat {
val fragment = MemoryUsagePreferenceDialogFragmentCompat()
val bundle = Bundle(1)

View File

@@ -21,38 +21,36 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.tasks.ActionRunnable
class ParallelismPreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDialogFragmentCompat() {
class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
setExplanationText(R.string.parallelism_explanation)
inputText = database?.parallelismAsString ?: ""
inputText = database?.parallelism?.toString() ?: MIN_PARALLELISM.toString()
}
override fun onDialogClosed(positiveResult: Boolean) {
if (database != null && positiveResult) {
var parallelism: Int
try {
val stringParallelism = inputText
parallelism = Integer.parseInt(stringParallelism)
} catch (e: NumberFormatException) {
Toast.makeText(context, R.string.error_rounds_not_number, Toast.LENGTH_LONG).show() // TODO change error
return
if (positiveResult) {
database?.let { database ->
var parallelism: Int = try {
inputText.toInt()
} catch (e: NumberFormatException) {
MIN_PARALLELISM
}
if (parallelism < MIN_PARALLELISM) {
parallelism = MIN_PARALLELISM
}
// TODO Max Parallelism
val oldParallelism = database.parallelism
database.parallelism = parallelism
actionInUIThreadAfterSaveDatabase = AfterParallelismSave(parallelism, oldParallelism)
}
if (parallelism < 1) {
parallelism = 1
}
val oldParallelism = database!!.parallelism
database?.parallelism = parallelism
actionInUIThreadAfterSaveDatabase = AfterParallelismSave(parallelism, oldParallelism)
}
super.onDialogClosed(positiveResult)
@@ -63,16 +61,21 @@ class ParallelismPreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDia
: ActionRunnable() {
override fun onFinishRun(result: Result) {
val parallelismToShow = mNewParallelism
if (!result.isSuccess) {
database?.parallelism = mOldParallelism
}
val parallelismToShow =
if (result.isSuccess) {
mNewParallelism
} else {
database?.parallelism = mOldParallelism
mOldParallelism
}
preference.summary = parallelismToShow.toString()
}
}
companion object {
const val MIN_PARALLELISM = 1
fun newInstance(key: String): ParallelismPreferenceDialogFragmentCompat {
val fragment = ParallelismPreferenceDialogFragmentCompat()
val bundle = Bundle(1)

View File

@@ -25,39 +25,38 @@ import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.tasks.ActionRunnable
class RoundsPreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDialogFragmentCompat() {
class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
super.onBindDialogView(view)
explanationText = getString(R.string.rounds_explanation)
inputText = database?.numberKeyEncryptionRoundsAsString ?: ""
inputText = database?.numberKeyEncryptionRounds?.toString() ?: MIN_ITERATIONS.toString()
}
override fun onDialogClosed(positiveResult: Boolean) {
if (database != null && positiveResult) {
var rounds: Long
try {
val strRounds = inputText
rounds = java.lang.Long.parseLong(strRounds)
} catch (e: NumberFormatException) {
Toast.makeText(context, R.string.error_rounds_not_number, Toast.LENGTH_LONG).show()
return
}
if (positiveResult) {
database?.let { database ->
var rounds: Long = try {
inputText.toLong()
} catch (e: NumberFormatException) {
MIN_ITERATIONS
}
if (rounds < MIN_ITERATIONS) {
rounds = MIN_ITERATIONS
}
// TODO Max iterations
if (rounds < 1) {
rounds = 1
}
val oldRounds = database.numberKeyEncryptionRounds
try {
database.numberKeyEncryptionRounds = rounds
} catch (e: NumberFormatException) {
Toast.makeText(context, R.string.error_rounds_too_large, Toast.LENGTH_LONG).show()
database.numberKeyEncryptionRounds = Long.MAX_VALUE
}
val oldRounds = database!!.numberKeyEncryptionRounds
try {
database?.numberKeyEncryptionRounds = rounds
} catch (e: NumberFormatException) {
Toast.makeText(context, R.string.error_rounds_too_large, Toast.LENGTH_LONG).show()
database?.numberKeyEncryptionRounds = Integer.MAX_VALUE.toLong()
actionInUIThreadAfterSaveDatabase = AfterRoundSave(rounds, oldRounds)
}
actionInUIThreadAfterSaveDatabase = AfterRoundSave(rounds, oldRounds)
}
super.onDialogClosed(positiveResult)
@@ -67,17 +66,21 @@ class RoundsPreferenceDialogFragmentCompat : InputDatabaseSavePreferenceDialogFr
private val mOldRounds: Long) : ActionRunnable() {
override fun onFinishRun(result: Result) {
val roundsToShow = mNewRounds
if (!result.isSuccess) {
database?.numberKeyEncryptionRounds = mOldRounds
}
val roundsToShow =
if (result.isSuccess) {
mNewRounds
} else {
database?.numberKeyEncryptionRounds = mOldRounds
mOldRounds
}
preference.summary = roundsToShow.toString()
}
}
companion object {
const val MIN_ITERATIONS = 1L
fun newInstance(key: String): RoundsPreferenceDialogFragmentCompat {
val fragment = RoundsPreferenceDialogFragmentCompat()
val bundle = Bundle(1)

View File

@@ -20,7 +20,6 @@ package com.kunzisoft.keepass.view
import android.content.Context
import android.graphics.Color
import androidx.core.content.ContextCompat
import android.text.method.PasswordTransformationMethod
import android.util.AttributeSet
import android.view.LayoutInflater
@@ -29,9 +28,14 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwDate
import com.kunzisoft.keepass.database.element.security.ProtectedString
import java.text.DateFormat
import java.util.*
class EntryContentsView @JvmOverloads constructor(context: Context,
@@ -59,9 +63,6 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
private val extrasContainerView: View
private val extrasView: ViewGroup
private val dateFormat: DateFormat = android.text.format.DateFormat.getDateFormat(context)
private val timeFormat: DateFormat = android.text.format.DateFormat.getTimeFormat(context)
private val creationDateView: TextView
private val modificationDateView: TextView
private val lastAccessDateView: TextView
@@ -69,6 +70,10 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
private val uuidView: TextView
private val historyContainerView: View
private val historyListView: RecyclerView
private val historyAdapter = EntryHistoryAdapter(context)
val isUserNamePresent: Boolean
get() = userNameContainerView.visibility == View.VISIBLE
@@ -103,6 +108,13 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
uuidView = findViewById(R.id.entry_UUID)
historyContainerView = findViewById(R.id.entry_history_container)
historyListView = findViewById(R.id.entry_history_list)
historyListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
adapter = historyAdapter
}
val attrColorAccent = intArrayOf(R.attr.colorAccent)
val taColorAccent = context.theme.obtainStyledAttributes(attrColorAccent)
colorAccent = taColorAccent.getColor(0, Color.BLACK)
@@ -230,24 +242,20 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
extrasContainerView.visibility = View.GONE
}
private fun getDateTime(date: Date): String {
return dateFormat.format(date) + " " + timeFormat.format(date)
fun assignCreationDate(date: PwDate) {
creationDateView.text = date.getDateTimeString(resources)
}
fun assignCreationDate(date: Date) {
creationDateView.text = getDateTime(date)
fun assignModificationDate(date: PwDate) {
modificationDateView.text = date.getDateTimeString(resources)
}
fun assignModificationDate(date: Date) {
modificationDateView.text = getDateTime(date)
fun assignLastAccessDate(date: PwDate) {
lastAccessDateView.text = date.getDateTimeString(resources)
}
fun assignLastAccessDate(date: Date) {
lastAccessDateView.text = getDateTime(date)
}
fun assignExpiresDate(date: Date) {
expiresDateView.text = getDateTime(date)
fun assignExpiresDate(date: PwDate) {
expiresDateView.text = date.getDateTimeString(resources)
}
fun assignExpiresDate(constString: String) {
@@ -258,6 +266,21 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
uuidView.text = uuid.toString()
}
fun showHistory(show: Boolean) {
historyContainerView.visibility = if (show) View.VISIBLE else View.GONE
}
fun assignHistory(history: ArrayList<EntryVersioned>) {
historyAdapter.clear()
historyAdapter.entryHistoryList.addAll(history)
}
fun onHistoryClick(action: (historyItem: EntryVersioned, position: Int)->Unit) {
historyAdapter.onItemClickListener = { item, position ->
action.invoke(item, position)
}
}
override fun generateDefaultLayoutParams(): LayoutParams {
return LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
}

View File

@@ -75,7 +75,6 @@
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/entry_scroll"
android:layout_width="match_parent"
@@ -86,12 +85,30 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/history_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:background="?attr/colorAccent"
android:padding="12dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:textColor="?attr/textColorInverse"
android:text="@string/entry_history"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.kunzisoft.keepass.view.EntryContentsView
android:id="@+id/entry_contents"
android:layout_height="match_parent"
android:layout_width="0dp"
app:layout_constraintWidth_percent="@dimen/content_percent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/history_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<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:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_last_modified"
tools:text = "Last Modified"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_title"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_title"
tools:text = "Title"
app:layout_constraintStart_toEndOf="@+id/entry_history_last_modified"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_username"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_username"
tools:text = "Username"
app:layout_constraintStart_toEndOf="@+id/entry_history_title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_url"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_url"
tools:text = "URL"
app:layout_constraintStart_toEndOf="@+id/entry_history_username"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -17,11 +17,13 @@
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants"
@@ -29,14 +31,29 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/explanation_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:gravity="center"
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.AppCompatCheckBox
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.appcompat.widget.AppCompatEditText
android:id="@+id/input_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:id="@+id/input_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
app:layout_constraintTop_toBottomOf="@+id/switch_element"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:minHeight="48dp"
android:digits="0123456789"
android:inputType="number"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Jeremy Jamet / Kunzisoft.
Copyright 2019 Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
@@ -17,17 +17,41 @@
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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: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:gravity="center"
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.AppCompatCheckBox
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.appcompat.widget.AppCompatEditText
android:id="@+id/input_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
</LinearLayout>
android:id="@+id/input_text"
android:layout_height="wrap_content"
android:layout_width="0dp"
app:layout_constraintTop_toBottomOf="@+id/switch_element"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:minHeight="48dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
KeePass DX 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.
KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
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="wrap_content"
android:gravity="center"
android:layout_marginBottom="8dp"
style="@style/KeepassDXStyle.TextAppearance.SmallTitle"/>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/input_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
</LinearLayout>

View File

@@ -254,7 +254,7 @@
android:layout_margin="@dimen/default_margin"
android:orientation="vertical">
<!-- Expires -->
<!-- UUID -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_UUID_label"
android:layout_width="match_parent"
@@ -275,4 +275,93 @@
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/entry_history_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginEnd="@dimen/default_margin"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginBottom="@dimen/default_margin">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_margin="@dimen/default_margin"
android:orientation="vertical">
<!-- History -->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/entry_history"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_last_modified"
android:text="@string/entry_modified"
style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_title"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_title"
android:text="@string/entry_title"
style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle"
app:layout_constraintStart_toEndOf="@+id/entry_history_last_modified"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_username"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_username"
android:text="@string/entry_user_name"
style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle"
app:layout_constraintStart_toEndOf="@+id/entry_history_title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_url"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_url"
android:text="@string/entry_url"
style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle"
app:layout_constraintStart_toEndOf="@+id/entry_history_username"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/entry_history_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -143,7 +143,6 @@
<string name="error_load_database">تعذر تحميل قاعدة البيانات.</string>
<string name="error_load_database_KDF_memory">غير قادر على تحميل المفتاح، في محاولة لتقليل الذاكرة المستخدمة من قبل KDF.</string>
<string name="error_pass_gen_type">يجب تحديد كلمة مرور واحد على الأقل نوع الجيل</string>
<string name="error_rounds_not_number">يجب أن تكون \"جولات\" عددا.</string>
<string name="error_rounds_too_large">\"جولات\" كبيرة جداً. الإعداد إلى 2147483648.</string>
<string name="error_string_key">يجب أن يكون لكل سلسلة اسم حقل.</string>
<string name="error_wrong_length">أدخل عددًا صحيحًا موجبًا في حقل «الطول».</string>
@@ -216,7 +215,7 @@
\nاستعد كلمة السر.</string>
<string name="biometric_not_recognized">لم يتعرّف على البصمة</string>
<string name="open_biometric_prompt_store_credential">استخدم البصمة لحفظ كلمة السر</string>
<string name="history">تأريخ</string>
<string name="database_history">تأريخ</string>
<string name="clipboard_notifications_summary">مكن اشعارات الحافظة لنسخ الحقول</string>
<string name="fingerprint_advanced_unlock_title">كيف أعد فحص البصمة للفتح السريع \?</string>
<string name="fingerprint_setting_text">"احفظ البصمات في "</string>

View File

@@ -69,7 +69,6 @@
<string name="error_out_of_memory">El telèfon sha quedat sense memòria processant la teva base de dades. Potser és massa gran pel teu telèfon.</string>
<string name="error_pass_gen_type">Has de seleccionar almenys un tipus de generador de contrasenyes</string>
<string name="error_pass_match">Les contrasenyes no coincideixen.</string>
<string name="error_rounds_not_number">Les passades han de ser un número.</string>
<string name="error_rounds_too_large">Massa passades. Establint a 2147483648.</string>
<string name="error_title_required">És necessari un títol.</string>
<string name="error_wrong_length">Insereix un enter positiu al camp longitud</string>

View File

@@ -73,7 +73,6 @@
<string name="error_out_of_memory">Nedostatek paměti k otevření databáze.</string>
<string name="error_pass_gen_type">Je třeba zvolit alespoň jeden způsob vytváření hesla.</string>
<string name="error_pass_match">Zadání hesla se neshodují.</string>
<string name="error_rounds_not_number">„Počet průchodů“ musí být číslo.</string>
<string name="error_rounds_too_large">Příliš vysoký „Počet průchodů“. Nastavuji na 2147483648.</string>
<string name="error_string_key">Je třeba, aby každý řetězec měl název kolonky.</string>
<string name="error_title_required">Přidejte název.</string>
@@ -222,7 +221,7 @@
<string name="biometric_scanning_error">Problém s otiskem prstu: %1$s</string>
<string name="open_biometric_prompt_store_credential">Použít pro uložení tohoto hesla otisk prstu</string>
<string name="no_credentials_stored">Tato databáze zatím není chráněna heslem.</string>
<string name="history">Historie</string>
<string name="database_history">Historie</string>
<string name="menu_appearance_settings">Vzhled</string>
<string name="general">Obecné</string>
<string name="autofill">Automatické vyplnění</string>

View File

@@ -72,7 +72,6 @@
<string name="error_out_of_memory">Ikke nok hukommelse til at indlæse hele databasen.</string>
<string name="error_pass_gen_type">Der skal vælges mindst én kode for kodeordsgenerering.</string>
<string name="error_pass_match">Adgangskoderne er ikke ens.</string>
<string name="error_rounds_not_number">\"Transformationsrunder\" skal være en talværdi.</string>
<string name="error_rounds_too_large">\"Transformation Runder\" er for stor. Sættes til 2147483648.</string>
<string name="error_string_key">Hver streng skal have et feltnavn.</string>
<string name="error_title_required">Tilføj en titel.</string>
@@ -221,7 +220,7 @@
<string name="biometric_scanning_error">Problem med fingeraftryk: %1$s</string>
<string name="open_biometric_prompt_store_credential">Brug fingeraftryk til at gemme adgangskoden</string>
<string name="no_credentials_stored">Databasen har endnu ikke en adgangskode.</string>
<string name="history">Historik</string>
<string name="database_history">Historik</string>
<string name="menu_appearance_settings">Udseende</string>
<string name="general">Generelt</string>
<string name="autofill">Autoudfyld</string>

View File

@@ -75,7 +75,6 @@
<string name="error_out_of_memory">Zu wenig Speicherplatz, um die ganze Datenbank zu laden.</string>
<string name="error_pass_gen_type">Mindestens eine Art der Passwortgenerierung muss ausgewählt werden.</string>
<string name="error_pass_match">Die Passwörter stimmen nicht überein.</string>
<string name="error_rounds_not_number">„Transformationsrunden“ zu einer Zahl machen.</string>
<string name="error_rounds_too_large">„Transformationsrunden“ zu hoch. Wird auf 214748364848 eingestellt.</string>
<string name="error_string_key">Für jede Zeichenfolge ist ein Feldname notwendig.</string>
<string name="error_title_required">Titel hinzufügen.</string>
@@ -207,7 +206,7 @@
<string name="encrypted_value_stored">Verschlüsseltes Passwort wurde gespeichert</string>
<string name="biometric_invalid_key">Fingerabdruckschlüssel nicht lesbar. Bitte das Passwort wiederherstellen.</string>
<string name="biometric_scanning_error">Problem mit dem Fingerabdruck: %1$s</string>
<string name="history">Verlauf</string>
<string name="database_history">Verlauf</string>
<string name="fingerprint_advanced_unlock_title">Wie richte ich den Fingerabdruckscanner für schnelles Entsperren ein?</string>
<string name="fingerprint_setting_text">Eingelesenen Fingerabdruck für das Gerät speichern in</string>
<string name="fingerprint_setting_link_text">„Einstellungen“ → „Sicherheit“ → „Fingerabdruck“</string>

View File

@@ -71,7 +71,6 @@
<string name="error_out_of_memory">Το τηλέφωνο ξέμεινε από μνήμη κατά τη διάρκεια προσπέλασης της βάσης δεδομένων σας. Μπορεί να είναι πολύ μεγάλη για το τηλέφωνο σας.</string>
<string name="error_pass_gen_type">Πρέπει να επιλεγεί τουλάχιστον ένας τύπος δημιουργίας κωδικού πρόσβασης</string>
<string name="error_pass_match">Οι κωδικοί δεν ταιριάζουν.</string>
<string name="error_rounds_not_number">Οι κύκλοι πρέπει να είναι αριθμός.</string>
<string name="error_rounds_too_large">Οι κύκλοι είναι υπερβολικά πολλοί. Ορισμός σε 2147483648.</string>
<string name="error_string_key">Ένα όνομα πεδίου απαιτείται για κάθε σειρά.</string>
<string name="error_title_required">Απαιτείται ένας τίτλος.</string>

View File

@@ -68,7 +68,6 @@
<string name="error_out_of_memory">El dispositivo se quedó sin memory mientras analizada la base de datos. Puede ser demasiado grande para este dispositivo.</string>
<string name="error_pass_gen_type">Debe seleccionar al menos un tipo de generación de contraseñas</string>
<string name="error_pass_match">Las contraseñas no coinciden.</string>
<string name="error_rounds_not_number">Las pasadas deben ser un número.</string>
<string name="error_rounds_too_large">Pasadas demasiado grande. Establecido a 2147483648.</string>
<string name="error_title_required">Se necesita un título.</string>
<string name="error_wrong_length">Introduzca un entero positivo en el campo longitud</string>
@@ -207,7 +206,7 @@
<string name="biometric_scanning_error">Problema de huella digital: %1$s</string>
<string name="open_biometric_prompt_store_credential">Usa la huella digital para almacenar esta contraseña</string>
<string name="no_credentials_stored">Aún sin contraseña almacenada para esta base de datos</string>
<string name="history">Historial</string>
<string name="database_history">Historial</string>
<string name="menu_appearance_settings">Apariencia</string>
<string name="general">General</string>
<string name="autofill">Autocompletar</string>

View File

@@ -72,7 +72,6 @@
<string name="error_out_of_memory">Telefonoa memoriarik gabe gelditu da zure datubasea arakatzean. Handiegia izan daiteke zure telefonorako.</string>
<string name="error_pass_gen_type">Pasahitza sortzeko mota bat gutxienez aukeratu behar da</string>
<string name="error_pass_match">Pasahitzak ez datoz bat.</string>
<string name="error_rounds_not_number">Rondak zenbaki bat izan behar dira.</string>
<string name="error_rounds_too_large">Rondak handiegiak. 2147483648 balorean jarrita.</string>
<string name="error_string_key">Eremu izen bat behar da testu kate bakoitzerako.</string>
<string name="error_title_required">Izenburu bat behar da.</string>

View File

@@ -71,7 +71,6 @@
<string name="error_out_of_memory">Puhelimesta loppui muisti salasanatietokantaa avatessa. Tietokanta voi olla liian suuri tälle puhelinmallille.</string>
<string name="error_pass_gen_type">Vähintään yksi salasanagenerointitapa täytyy olla valittuna.</string>
<string name="error_pass_match">Salasanat eivät täsmää.</string>
<string name="error_rounds_not_number">Kierroksia täytyy olla numero.</string>
<string name="error_rounds_too_large">Kierroksia on liian paljon. Asetetaan se arvoon 2147483648.</string>
<string name="error_string_key">Kentän nimi on pakollinen joka tekstille.</string>
<string name="error_title_required">Otsikko on pakollinen.</string>

View File

@@ -76,7 +76,6 @@
<string name="error_out_of_memory">Mémoire insuffisante pour charger lensemble de votre base de données.</string>
<string name="error_pass_gen_type">Au moins un type de génération de mot de passe doit être sélectionné.</string>
<string name="error_pass_match">Les mots de passe ne correspondent pas.</string>
<string name="error_rounds_not_number">«Tours de transformation» doit être un nombre.</string>
<string name="error_rounds_too_large">«Tours de transformation» trop grand. Défini à 2147483648.</string>
<string name="error_string_key">Chaque chaîne doit avoir un nom de champ.</string>
<string name="error_title_required">Ajoutez un titre.</string>
@@ -187,7 +186,7 @@
<string name="biometric_scanning_error">Problème dempreinte digitale : %1$s</string>
<string name="open_biometric_prompt_store_credential">Utiliser lempreinte digitale pour stocker ce mot de passe</string>
<string name="no_credentials_stored">Cette base de données na pas encore de mot de passe.</string>
<string name="history">Historique</string>
<string name="database_history">Historique</string>
<string name="menu_appearance_settings">Apparence</string>
<string name="general">Général</string>
<string name="autofill">Remplissage automatique</string>

View File

@@ -70,7 +70,6 @@
<string name="error_out_of_memory">Nincs elég memória a teljes adatbázis betöltéséhez.</string>
<string name="error_pass_gen_type">Legalább egy jelszóelőállítási típust kell választania.</string>
<string name="error_pass_match">A jelszavak nem egyeznek meg.</string>
<string name="error_rounds_not_number">A „Transzformációs körök” szám kell legyen.</string>
<string name="error_rounds_too_large">A „Transzformációs körök” száma túl nagy. Beállítás 2147483648-ra.</string>
<string name="error_string_key">Minden karakterlánchoz szükséges egy mezőnév.</string>
<string name="error_title_required">Adjon hozzá egy címet.</string>
@@ -223,7 +222,7 @@
<string name="warning_no_encryption_key">Biztos, hogy nem akar semmilyen titkosítási kulcsot használni\?</string>
<string name="build_label">Összeállítás: %1$s</string>
<string name="biometric_not_recognized">Az ujjlenyomat nem ismerhető fel</string>
<string name="history">Előzmények</string>
<string name="database_history">Előzmények</string>
<string name="menu_appearance_settings">Megjelenés</string>
<string name="general">Általános</string>
<string name="autofill">Automatikus kitöltés</string>

View File

@@ -71,7 +71,6 @@
<string name="error_out_of_memory">Memoria insufficiente per caricare l\'intero database.</string>
<string name="error_pass_gen_type">Deve essere selezionato almeno un tipo di generazione password.</string>
<string name="error_pass_match">Le password non corrispondono.</string>
<string name="error_rounds_not_number">Rendi il \"livello\" un numero.</string>
<string name="error_rounds_too_large">\"Livello\" troppo alto. Impostato a 2147483648.</string>
<string name="error_string_key">Ogni stringa deve avere un nome.</string>
<string name="error_title_required">Aggiungi un titolo.</string>
@@ -215,7 +214,7 @@
<string name="warning_empty_password">Vuoi veramente che non ci sia una password di sblocco\?</string>
<string name="warning_no_encryption_key">Sei sicuro di non volere usare una chiave di cifratura?</string>
<string name="biometric_not_recognized">Impronta non riconosciuta</string>
<string name="history">Cronologia</string>
<string name="database_history">Cronologia</string>
<string name="menu_appearance_settings">Aspetto</string>
<string name="general">Generale</string>
<string name="autofill">Autocompletamento</string>

View File

@@ -68,7 +68,6 @@
<string name="error_out_of_memory">זיכרון המכשיר אזל בזמן ניתוח מסד הנתונים. יתכן והוא גדול מדי למכשירך.</string>
<string name="error_pass_gen_type">לפחות סוג אחד ליצירת סיסמה צריך להיבחר</string>
<string name="error_pass_match">הסיסמאות לא תואמות.</string>
<string name="error_rounds_not_number">סיבובים חייב להיות מספר.</string>
<string name="error_rounds_too_large">מספר סיבובים גדול מדי. מגדיר ל-2147483648.</string>
<string name="error_string_key">שדה שם נדרש לכל מחרוזת.</string>
<string name="error_title_required">כותרת נדרשת.</string>

View File

@@ -65,7 +65,6 @@
<string name="error_out_of_memory">データベース解析中にメモリ不足になりました。</string>
<string name="error_pass_gen_type">少なくとも1つ以上のパスワード生成タイプを選択する必要があります。</string>
<string name="error_pass_match">パスワードが一致しません</string>
<string name="error_rounds_not_number">数値を入力してください。</string>
<string name="error_rounds_too_large">値が大きすぎます。 2147483648にセットしました。</string>
<string name="error_title_required">タイトルは必須入力です。</string>
<string name="error_wrong_length">\"長さ\"欄には正の整数を入力してください。</string>

View File

@@ -77,7 +77,6 @@
<string name="error_load_database_KDF_memory">키를 로드할 수 없습니다. KDF \"메모리 사용량\"을 줄여 보세요.</string>
<string name="error_pass_gen_type">최소 한 가지의 비밀번호 생성 방식이 선택되어야 합니다.</string>
<string name="error_pass_match">비밀번호가 일치하지 않습니다.</string>
<string name="error_rounds_not_number">\"Transformation rounds\"는 숫자로 입력해 주세요.</string>
<string name="error_rounds_too_large">\"Transformation rounds\" 가 너무 높습니다. 2147483648로 설정합니다.</string>
<string name="error_string_key">각 항목은 필드 이름을 가져야 합니다.</string>
<string name="error_title_required">제목을 입력하십시오.</string>

View File

@@ -51,7 +51,6 @@
<string name="error_out_of_memory">Darbam ar datu bāzi, tālrunī nepietiek atmiņas.</string>
<string name="error_pass_gen_type">Ir jāatlasa vismaz viens paroles ģenerēšanas tips</string>
<string name="error_pass_match">Paroles nesakrīt.</string>
<string name="error_rounds_not_number">Ievadiet līmeni no 1 līdz 2147483648</string>
<string name="error_rounds_too_large">Līmenis pārāk liels. Maksimālais 2147483648</string>
<string name="error_string_key">A field name is required for each string.</string>
<string name="error_title_required">Nepieciešams nosaukums.</string>

View File

@@ -77,7 +77,6 @@
<string name="error_load_database_KDF_memory">Kunne ikke laste nøkkelen, prøv å senke minnet brukt av KDF.</string>
<string name="error_pass_gen_type">Minst én passordgenereringstype må velges.</string>
<string name="error_pass_match">Passordene samsvarer ikke.</string>
<string name="error_rounds_not_number">\"Omganger\" må være et tall.</string>
<string name="error_rounds_too_large">\"Omganger\" er for stort. Setter til 2147483648.</string>
<string name="error_string_key">Hver streng må ha et feltnavn.</string>
<string name="error_title_required">En tittel er påkrevd.</string>
@@ -199,7 +198,7 @@
<string name="biometric_scanning_error">Fingeravtrykksproblem: %1$s</string>
<string name="open_biometric_prompt_store_credential">Bruk fingeravtrykk til å lagre dette passordet</string>
<string name="no_credentials_stored">Denne databasen har ikke et passord enda.</string>
<string name="history">Historikk</string>
<string name="database_history">Historikk</string>
<string name="menu_appearance_settings">Utseende</string>
<string name="general">Generelt</string>
<string name="autofill">Autofyll</string>

View File

@@ -68,7 +68,6 @@
<string name="error_out_of_memory">Onvoldoende vrij geheugen om de gehele databank te laden.</string>
<string name="error_pass_gen_type">Je moet minimaal één soort wachtwoordgenerering kiezen.</string>
<string name="error_pass_match">De wachtwoorden komen niet overeen.</string>
<string name="error_rounds_not_number">\"Cycli-waarde\" moet een getal zijn.</string>
<string name="error_rounds_too_large">\"Cycli-waarde\" te groot. Wordt ingesteld op 2147483648.</string>
<string name="error_title_required">Voeg een titel toe.</string>
<string name="error_wrong_length">Voer een positief geheel getal in in het veld \"Lengte\".</string>
@@ -222,7 +221,7 @@
<string name="biometric_scanning_error">Vingerafdrukprobleem: %1$s</string>
<string name="open_biometric_prompt_store_credential">Vingerafdruk gebruiken om dit wachtwoord op te slaan</string>
<string name="no_credentials_stored">Deze databank heeft nog geen wachtwoord.</string>
<string name="history">Geschiedenis</string>
<string name="database_history">Geschiedenis</string>
<string name="menu_appearance_settings">Uiterlijk</string>
<string name="general">Algemeen</string>
<string name="autofill">Auto-aanvullen</string>

View File

@@ -66,7 +66,6 @@
<string name="error_out_of_memory">Telefonen gjekk tom for minne ved lesinga av databasen din. Databasen er kanskje for stor.</string>
<string name="error_pass_gen_type">Du må velja minst éin passordlagingstype</string>
<string name="error_pass_match">Passorda samsvarer ikkje.</string>
<string name="error_rounds_not_number">Omgangar må vera eit tal.</string>
<string name="error_rounds_too_large">For mange omgangar. Bruker 2147483648.</string>
<string name="error_title_required">Treng ein tittel.</string>
<string name="error_wrong_length">Bruk eit positivt heiltal i lengdfeltet</string>

View File

@@ -65,7 +65,6 @@
<string name="error_out_of_memory">W urządzeniu zabrakło pamięci do załadowania całej bazy danych.</string>
<string name="error_pass_gen_type">Należy wybrać co najmniej jeden rodzaj generowania hasła.</string>
<string name="error_pass_match">Hasła nie pasują do siebie.</string>
<string name="error_rounds_not_number">\"Rundy szyfrowania\" muszą być liczbą.</string>
<string name="error_rounds_too_large">\"Rundy szyfrowania\" są zbyt wysokie. Ustaw na 2147483648.</string>
<string name="error_title_required">Dodaj tytuł.</string>
<string name="error_wrong_length">Wprowadź dodatnią liczbę całkowitą w polu \"Długość\".</string>
@@ -220,7 +219,7 @@
<string name="biometric_scanning_error">Problem z odciskiem palca: %1$s</string>
<string name="open_biometric_prompt_store_credential">Użyj odcisku palca, aby zapisać to hasło</string>
<string name="no_credentials_stored">Baza danych nie ma jeszcze hasła.</string>
<string name="history">Historia</string>
<string name="database_history">Historia</string>
<string name="menu_appearance_settings">Wygląd</string>
<string name="general">Ogólne</string>
<string name="autofill">Wypełnij automatycznie</string>

View File

@@ -68,7 +68,6 @@
<string name="error_out_of_memory">Falta de memória para abrir todo o banco.</string>
<string name="error_pass_gen_type">Pelo menos um tipo de geração de senhas deve ser selecionado.</string>
<string name="error_pass_match">As senhas não combinam.</string>
<string name="error_rounds_not_number">\"Número de rodadas\" deve ser um número.</string>
<string name="error_rounds_too_large">\"Número de rodadas\" é muito grande. Modificado para 2147483648.</string>
<string name="error_title_required">Insira um título.</string>
<string name="error_wrong_length">Digite um número inteiro positivo no campo \"Tamanho\".</string>
@@ -216,7 +215,7 @@
<string name="biometric_scanning_error">Problema de Impressão digital: %1$s</string>
<string name="open_biometric_prompt_store_credential">Use Impressão digital para armazenar esta senha</string>
<string name="no_credentials_stored">Ainda não há nenhuma senha armazenada nesse banco de dados.</string>
<string name="history">Histórico</string>
<string name="database_history">Histórico</string>
<string name="menu_appearance_settings">Aparência</string>
<string name="general">Geral</string>
<string name="autofill">Preenchimento automático</string>

View File

@@ -71,7 +71,6 @@
<string name="error_out_of_memory">Sem memória para carregar toda a bases de dados.</string>
<string name="error_pass_gen_type">Pelo menos um tipo de geração de palavra-chave deve ser selecionado.</string>
<string name="error_pass_match">As palavras-passe não coincidem.</string>
<string name="error_rounds_not_number">\"Número de rodadas\" deve ser um número.</string>
<string name="error_rounds_too_large">\"Número de rodadas\" é muito grande. Modificado para 2147483648.</string>
<string name="error_string_key">Um nome do campo é necessário para cada string.</string>
<string name="error_title_required">Adicione um título.</string>
@@ -212,7 +211,7 @@
<string name="biometric_scanning_error">Problema da Impressão digital: %1$s</string>
<string name="open_biometric_prompt_store_credential">Use a impressão digital para armazenar esta palavra-chave</string>
<string name="no_credentials_stored">Ainda não há nenhuma palavra-chave armazenada nesta base de dados.</string>
<string name="history">Histórico</string>
<string name="database_history">Histórico</string>
<string name="menu_appearance_settings">Aparência</string>
<string name="general">Geral</string>
<string name="autofill">Preenchimento automático</string>

View File

@@ -71,7 +71,6 @@
<string name="error_out_of_memory">Недостаточно памяти для работы с базой.</string>
<string name="error_pass_gen_type">Выберите один или несколько типов символов.</string>
<string name="error_pass_match">Пароли не совпадают.</string>
<string name="error_rounds_not_number">Введите число.</string>
<string name="error_rounds_too_large">Предельное значение 2147483648.</string>
<string name="error_string_key">Каждое поле должно иметь название.</string>
<string name="error_title_required">Введите название.</string>
@@ -220,7 +219,7 @@
<string name="warning_empty_password">Вы действительно хотите использовать пустой пароль?</string>
<string name="warning_no_encryption_key">Вы действительно не хотите использовать ключ шифрования?</string>
<string name="biometric_not_recognized">Отпечаток пальца не распознан</string>
<string name="history">История</string>
<string name="database_history">История</string>
<string name="menu_appearance_settings">Внешний вид</string>
<string name="general">Общие</string>
<string name="autofill">Автозаполнение</string>

View File

@@ -65,7 +65,6 @@
<string name="error_out_of_memory">Telefón vyčerpal pamäť pri analýze databázy. Možno je to príliš na Váš telefón.</string>
<string name="error_pass_gen_type">Musí byť vybraý najmenej jeden typ generovania hesla</string>
<string name="error_pass_match">Heslá sa nezhodujú.</string>
<string name="error_rounds_not_number">Opakovanie musí byť číslo.</string>
<string name="error_rounds_too_large">Príliš veľa opakovaní. Nastavujem na 2147483648.</string>
<string name="error_title_required">Vyžaduje sa názov.</string>
<string name="error_wrong_length">Zadajte celé kladné číslo na dĺžku poľa</string>

View File

@@ -71,7 +71,6 @@
<string name="error_out_of_memory">The phone ran out of memory while parsing your database. It may be too large for your phone.</string>
<string name="error_pass_gen_type">At least one password generation type must be selected</string>
<string name="error_pass_match">Lösenorden matchar inte.</string>
<string name="error_rounds_not_number">Antalet rundor måste vara en siffra.</string>
<string name="error_rounds_too_large">Antalet rundor är för stort. Sätter värdet till 2147483648.</string>
<string name="error_string_key">Ett fältnamn krävs för varje sträng.</string>
<string name="error_title_required">En titel krävs.</string>

View File

@@ -77,7 +77,6 @@
<string name="error_load_database_KDF_memory">Anahtar yüklenemedi. KDF \"Bellek Kullanımı\" nı azaltmaya çalışın.</string>
<string name="error_pass_gen_type">En az bir parola oluşturma türü seçilmelidir.</string>
<string name="error_pass_match">Parolalar uyuşmuyor.</string>
<string name="error_rounds_not_number">\"Dönüşüm turları\"nı bir sayı yapın.</string>
<string name="error_rounds_too_large">\"Dönüşüm turları\" çok yüksek. 2147483648\'e ayarlayın.</string>
<string name="error_string_key">Her dizenin bir alan adı olmalıdır.</string>
<string name="error_title_required">Bir başlık ekle.</string>
@@ -202,7 +201,7 @@
<string name="biometric_scanning_error">Parmak izi sorunu: %1$s</string>
<string name="open_biometric_prompt_store_credential">Bu şifreyi saklamak için parmak izini kullanın</string>
<string name="no_credentials_stored">Bu veritabanının henüz bir parolası yok.</string>
<string name="history">Geçmiş</string>
<string name="database_history">Geçmiş</string>
<string name="menu_appearance_settings">Görünüm</string>
<string name="general">Genel</string>
<string name="autofill">Otomatik Doldurma</string>

View File

@@ -66,7 +66,6 @@
<string name="error_out_of_memory">Телефону не вистачило пам’яті при аналізі вашої бази даних. Можливо база надто велика для вашог телефона.</string>
<string name="error_pass_gen_type">Принаймні один тип генерації пароля необхідно вибрати.</string>
<string name="error_pass_match">Паролі не співпадають.</string>
<string name="error_rounds_not_number">Кількість циклів має бути числом.</string>
<string name="error_rounds_too_large">Надто багато циклів. Установлено 2147483648.</string>
<string name="error_title_required">Необхідно вказати заголовок.</string>
<string name="error_wrong_length">Введіть ціле число на усю довжину поля</string>

View File

@@ -66,7 +66,6 @@
<string name="error_out_of_memory">因内存不足无法加载数据库。</string>
<string name="error_pass_gen_type">必须至少选择一种密码生成类型。</string>
<string name="error_pass_match">密码不匹配。</string>
<string name="error_rounds_not_number">“变换次数”必须是数字。</string>
<string name="error_rounds_too_large">“变换次数”过多。已设置为 2147483648。</string>
<string name="error_title_required">请添加标题。</string>
<string name="error_wrong_length">请在“长度”字段输入一个正整数。</string>
@@ -209,7 +208,7 @@
<string name="warning">警告</string>
<string name="version_label">版本 %1$s</string>
<string name="open_biometric_prompt_store_credential">使用指纹保存此密码</string>
<string name="history">历史</string>
<string name="database_history">历史</string>
<string name="menu_appearance_settings">外观</string>
<string name="general">一般设置</string>
<string name="autofill">自动填充</string>

View File

@@ -65,7 +65,6 @@
<string name="error_out_of_memory">這款手機運行記憶體不足而不能解析資料庫。這可能是資料庫太大的緣故。</string>
<string name="error_pass_gen_type">至少必須選擇一個密碼生成類型</string>
<string name="error_pass_match">密碼不正確。</string>
<string name="error_rounds_not_number">次數必須是數字。</string>
<string name="error_rounds_too_large">次數太多。最大設置到2147483648。</string>
<string name="error_title_required">標題為必填。</string>
<string name="error_wrong_length">長度欄位輸入一個正整數</string>

View File

@@ -149,12 +149,17 @@
<!-- Database Settings -->
<string name="settings_database_key" translatable="false">settings_database_key</string>
<string name="settings_database_change_credentials_key" translatable="false">settings_database_change_credentials_key</string>
<string name="database_general_key" translatable="false">database_general_key</string>
<string name="database_general_key" translatable="false">database_general_key</string>
<string name="database_name_key" translatable="false">database_name_key</string>
<string name="database_description_key" translatable="false">database_description_key</string>
<string name="recycle_bin_key" translatable="false">recycle_bin_key</string>
<string name="database_version_key" translatable="false">database_version_key</string>
<string name="database_history_key" translatable="false">database_history_key</string>
<string name="max_history_items_key" translatable="false">max_history_items_key</string>
<string name="max_history_size_key" translatable="false">max_history_size_key</string>
<string name="recycle_bin_key" translatable="false">recycle_bin_key</string>
<string name="encryption_algorithm_key" translatable="false">algorithm</string>
<string name="key_derivation_function_key" translatable="false">key_derivation_function_key</string>
<string name="transform_rounds_key" translatable="false">transform_rounds_key</string>

View File

@@ -81,6 +81,7 @@
<string name="entry_created">Created</string>
<string name="entry_expires">Expires</string>
<string name="entry_UUID">UUID</string>
<string name="entry_history">History</string>
<string name="entry_keyfile">Keyfile</string>
<string name="entry_modified">Modified</string>
<string name="entry_not_found">Could not find entry data.</string>
@@ -101,7 +102,6 @@
<string name="error_load_database_KDF_memory">Could not load the key. Try to lower the KDF \"Memory Usage\".</string>
<string name="error_pass_gen_type">At least one password generation type must be selected.</string>
<string name="error_pass_match">The passwords do not match.</string>
<string name="error_rounds_not_number">Make \"Transformation rounds\" a number.</string>
<string name="error_rounds_too_large">\"Transformation rounds\" too high. Setting to 2147483648.</string>
<string name="error_string_key">Each string must have a field name.</string>
<string name="error_title_required">Add a title.</string>
@@ -238,7 +238,7 @@
<!--This problem could be with scanning, or something else.-->
<string name="biometric_scanning_error">Biometric error: %1$s</string>
<string name="no_credentials_stored">This database does not have stored credential yet.</string>
<string name="history">History</string>
<string name="database_history">History</string>
<string name="menu_appearance_settings">Appearance</string>
<string name="biometric">Biometric</string>
<string name="general">General</string>
@@ -288,6 +288,10 @@
<string name="full_file_path_enable_summary">View the full file path</string>
<string name="recycle_bin_title">Use recycle bin</string>
<string name="recycle_bin_summary">Moves groups and entries to \"Recycle bin\" before deleting</string>
<string name="max_history_items_title">Max. history items</string>
<string name="max_history_items_summary">Limit number of history items per entry</string>
<string name="max_history_size_title">Max. history size</string>
<string name="max_history_size_summary">Limit history size per entry (in binary bytes)</string>
<string name="monospace_font_fields_enable_title">Field font</string>
<string name="monospace_font_fields_enable_summary">Change font used in fields for better character visibility</string>
<string name="auto_open_file_uri_title">Open files by selecting</string>

View File

@@ -312,6 +312,10 @@
<item name="android:textColor">?attr/colorAccent</item>
<item name="android:textSize">12sp</item>
</style>
<style name="KeepassDXStyle.TextAppearance.LabelTableTextStyle" parent="KeepassDXStyle.TextAppearance">
<item name="android:textSize">12sp</item>
<item name="android:textStyle">bold</item>
</style>
<style name="KeepassDXStyle.TextAppearance.TextEntryItem" parent="KeepassDXStyle.TextAppearance">
<item name="android:padding">5sp</item>
<item name="android:textSize">16sp</item>

View File

@@ -108,7 +108,7 @@
</PreferenceCategory>
<PreferenceCategory
android:title="@string/history">
android:title="@string/database_history">
<SwitchPreference
android:key="@string/recentfile_key"

View File

@@ -36,12 +36,6 @@
android:title="@string/database_description_title"
android:positiveButtonText="@string/entry_save"
android:negativeButtonText="@string/entry_cancel"/>
<SwitchPreference
android:key="@string/recycle_bin_key"
android:persistent="false"
android:title="@string/recycle_bin_title"
android:summary="@string/recycle_bin_summary"
android:checked="false"/>
<Preference
android:key="@string/database_version_key"
android:persistent="false"
@@ -49,6 +43,38 @@
</PreferenceCategory>
<PreferenceCategory
android:title="@string/recycle_bin">
<SwitchPreference
android:key="@string/recycle_bin_key"
android:persistent="false"
android:title="@string/recycle_bin_title"
android:summary="@string/recycle_bin_summary"
android:checked="false"/>
</PreferenceCategory>
<PreferenceCategory
android:key="@string/database_history_key"
android:title="@string/entry_history">
<com.kunzisoft.keepass.settings.preference.InputNumberPreference
android:key="@string/max_history_items_key"
android:persistent="false"
android:title="@string/max_history_items_title"
android:positiveButtonText="@string/entry_save"
android:negativeButtonText="@string/entry_cancel"/>
<com.kunzisoft.keepass.settings.preference.InputNumberPreference
android:key="@string/max_history_size_key"
android:persistent="false"
android:title="@string/max_history_size_title"
android:summary="@string/max_history_size_summary"
android:positiveButtonText="@string/entry_save"
android:negativeButtonText="@string/entry_cancel"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/encryption">
@@ -60,21 +86,21 @@
android:key="@string/key_derivation_function_key"
android:persistent="false"
android:title="@string/key_derivation_function"/>
<com.kunzisoft.keepass.settings.preference.InputNumberPreference
<com.kunzisoft.keepass.settings.preference.InputKdfNumberPreference
android:key="@string/transform_rounds_key"
android:persistent="false"
android:title="@string/rounds"
custom:explanations="@string/rounds_explanation"
android:positiveButtonText="@string/entry_save"
android:negativeButtonText="@string/entry_cancel"/>
<com.kunzisoft.keepass.settings.preference.InputNumberPreference
<com.kunzisoft.keepass.settings.preference.InputKdfNumberPreference
android:key="@string/memory_usage_key"
android:persistent="false"
android:title="@string/memory_usage"
custom:explanations="@string/memory_usage_explanation"
android:positiveButtonText="@string/entry_save"
android:negativeButtonText="@string/entry_cancel"/>
<com.kunzisoft.keepass.settings.preference.InputNumberPreference
<com.kunzisoft.keepass.settings.preference.InputKdfNumberPreference
android:key="@string/parallelism_key"
android:persistent="false"
android:title="@string/parallelism"