Merge branch 'develop' into feature/Custom_Icons

This commit is contained in:
J-Jamet
2021-02-24 20:51:08 +01:00
37 changed files with 281 additions and 307 deletions

View File

@@ -1,5 +1,7 @@
KeePassDX(2.9.14) KeePassDX(2.9.14)
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation) * Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation) #532 #714
* Fix binary deduplication #715
* Fix IconId #901
KeePassDX(2.9.13) KeePassDX(2.9.13)
* Binary image viewer #473 #749 * Binary image viewer #473 #749

View File

@@ -11,8 +11,6 @@ import org.junit.Test
import java.io.DataInputStream import java.io.DataInputStream
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.lang.Exception
import java.security.MessageDigest
class BinaryAttachmentTest { class BinaryAttachmentTest {
@@ -132,20 +130,6 @@ class BinaryAttachmentTest {
} }
} }
private fun BinaryAttachment.md5(): String {
val md = MessageDigest.getInstance("MD5")
return this.getInputDataStream(loadedKey).use { fis ->
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
generateSequence {
when (val bytesRead = fis.read(buffer)) {
-1 -> null
else -> bytesRead
}
}.forEach { bytesRead -> md.update(buffer, 0, bytesRead) }
md.digest().joinToString("") { "%02x".format(it) }
}
}
companion object { companion object {
private const val TEST_FILE_CACHE_A = "testA" private const val TEST_FILE_CACHE_A = "testA"
private const val TEST_FILE_CACHE_B = "testB" private const val TEST_FILE_CACHE_B = "testB"

View File

@@ -172,10 +172,14 @@ class EntryEditActivity : LockingActivity(),
val parentIcon = mParent?.icon val parentIcon = mParent?.icon
tempEntryInfo = mDatabase?.createEntry()?.getEntryInfo(mDatabase, true) tempEntryInfo = mDatabase?.createEntry()?.getEntryInfo(mDatabase, true)
// Set default icon // Set default icon
if (parentIcon != null if (parentIcon != null) {
&& parentIcon.iconId != IconImage.UNKNOWN_ID if (parentIcon.custom.isUnknown
&& parentIcon.iconId != IconImageStandard.FOLDER) { && parentIcon.standard.id != IconImageStandard.FOLDER_ID) {
tempEntryInfo?.icon = parentIcon tempEntryInfo?.icon = IconImage(parentIcon.standard)
}
if (!parentIcon.custom.isUnknown) {
tempEntryInfo?.icon = IconImage(parentIcon.custom)
}
} }
// Set default username // Set default username
tempEntryInfo?.username = mDatabase?.defaultUsername ?: "" tempEntryInfo?.username = mDatabase?.defaultUsername ?: ""
@@ -711,7 +715,7 @@ class EntryEditActivity : LockingActivity(),
} }
} }
override fun iconPicked(icon: IconImageStandard) { override fun iconPicked(icon: IconImage) {
entryEditFragment?.icon = icon entryEditFragment?.icon = icon
} }

View File

@@ -54,7 +54,7 @@ import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillComponent import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
@@ -1122,7 +1122,7 @@ class GroupActivity : LockingActivity(),
} }
// For icon in create tree dialog // For icon in create tree dialog
override fun iconPicked(icon: IconImageStandard) { override fun iconPicked(icon: IconImage) {
(supportFragmentManager (supportFragmentManager
.findFragmentByTag(GroupEditDialogFragment.TAG_CREATE_GROUP) as GroupEditDialogFragment) .findFragmentByTag(GroupEditDialogFragment.TAG_CREATE_GROUP) as GroupEditDialogFragment)
.iconPicked(icon) .iconPicked(icon)

View File

@@ -35,7 +35,7 @@ import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGrou
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.GroupInfo import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.view.ExpirationView import com.kunzisoft.keepass.view.ExpirationView
@@ -210,7 +210,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
} }
} }
override fun iconPicked(icon: IconImageStandard) { override fun iconPicked(icon: IconImage) {
mGroupInfo.icon = icon mGroupInfo.icon = icon
assignIconView() assignIconView()
} }

View File

@@ -28,10 +28,11 @@ import androidx.fragment.app.*
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
class IconPickerDialogFragment : DialogFragment(), IconStandardFragment.IconStandardPickerListener { class IconPickerDialogFragment : DialogFragment() {
private lateinit var iconPickerPagerAdapter: IconPickerPagerAdapter private lateinit var iconPickerPagerAdapter: IconPickerPagerAdapter
private lateinit var viewPager: ViewPager private lateinit var viewPager: ViewPager
@@ -78,40 +79,46 @@ class IconPickerDialogFragment : DialogFragment(), IconStandardFragment.IconStan
val root = layoutInflater.inflate(R.layout.fragment_icon_picker, container) val root = layoutInflater.inflate(R.layout.fragment_icon_picker, container)
viewPager = root.findViewById(R.id.icon_picker_pager) viewPager = root.findViewById(R.id.icon_picker_pager)
tabLayout = root.findViewById(R.id.icon_picker_tabs) tabLayout = root.findViewById(R.id.icon_picker_tabs)
iconPickerPagerAdapter = IconPickerPagerAdapter(requireContext(), childFragmentManager) iconPickerPagerAdapter = IconPickerPagerAdapter(childFragmentManager) { icon ->
iconPickerListener?.iconPicked(IconImage(icon))
dismiss()
}
viewPager.adapter = iconPickerPagerAdapter viewPager.adapter = iconPickerPagerAdapter
tabLayout.setupWithViewPager(viewPager) tabLayout.setupWithViewPager(viewPager)
return root return root
} }
class IconPickerPagerAdapter(private val context: Context, fragmentManager: FragmentManager) class IconPickerPagerAdapter(fragmentManager: FragmentManager,
iconStandardSelected: (icon: IconImageStandard) -> Unit)
: FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_SET_USER_VISIBLE_HINT) { : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_SET_USER_VISIBLE_HINT) {
override fun getCount(): Int = 3 private val iconStandardFragment = IconStandardFragment()
private val iconCustomFragment = IconStandardFragment()
init {
iconStandardFragment.iconStandardPickerListener = iconStandardSelected
}
override fun getCount(): Int = 2
override fun getItem(i: Int): Fragment { override fun getItem(i: Int): Fragment {
val fragment = IconStandardFragment() return when (i) {
return fragment 1 -> iconCustomFragment
else -> iconStandardFragment
}
} }
override fun getPageTitle(position: Int): CharSequence { override fun getPageTitle(position: Int): CharSequence {
return when (position) { return when (position) {
0 -> "Standard" //context.getString(R.string.iconStandard) 1 -> "Custom" //context.getString(R.string.iconStandard)
1 -> "Service" //context.getString(R.string.iconStandard) else -> "Standard" //context.getString(R.string.iconStandard)
else -> "Custom" //context.getString(R.string.iconStandard)
} }
} }
} }
override fun iconStandardPicked(icon: IconImageStandard) {
iconPickerListener?.iconPicked(icon)
dismiss()
}
interface IconPickerListener { interface IconPickerListener {
fun iconPicked(icon: IconImageStandard) fun iconPicked(icon: IconImage)
} }
companion object { companion object {

View File

@@ -25,18 +25,7 @@ class IconStandardFragment : Fragment() {
private lateinit var currIconGridView: GridView private lateinit var currIconGridView: GridView
private var iconPack: IconPack? = null private var iconPack: IconPack? = null
private var iconStandardPickerListener: IconPickerDialogFragment.IconPickerListener? = null var iconStandardPickerListener: ((icon: IconImageStandard) -> Unit)? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
iconStandardPickerListener = context as IconPickerDialogFragment.IconPickerListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + IconStandardFragment::class.java.name)
}
}
override fun onDetach() { override fun onDetach() {
iconStandardPickerListener = null iconStandardPickerListener = null
@@ -57,7 +46,7 @@ class IconStandardFragment : Fragment() {
iconPack = IconPackChooser.getSelectedIconPack(requireContext()) iconPack = IconPackChooser.getSelectedIconPack(requireContext())
currIconGridView.adapter = IconStandardAdapter(requireActivity()) currIconGridView.adapter = IconStandardAdapter(requireActivity())
currIconGridView.setOnItemClickListener { _, _, position, _ -> currIconGridView.setOnItemClickListener { _, _, position, _ ->
iconStandardPickerListener?.iconPicked(IconImageStandard(position)) iconStandardPickerListener?.invoke(IconImageStandard(position))
} }
} }

View File

@@ -110,10 +110,10 @@ class SearchEntryCursorAdapter(private val context: Context,
return database.createEntry()?.apply { return database.createEntry()?.apply {
database.startManageEntry(this) database.startManageEntry(this)
entryKDB?.let { entryKDB -> entryKDB?.let { entryKDB ->
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconFactory) (cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconPool)
} }
entryKDBX?.let { entryKDBX -> entryKDBX?.let { entryKDBX ->
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconFactory) (cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconPool)
} }
database.stopManageEntry(this) database.stopManageEntry(this)
} }

View File

@@ -23,7 +23,8 @@ import android.database.MatrixCursor
import android.provider.BaseColumns import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.entry.EntryVersioned import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconPool
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import java.util.* import java.util.*
@@ -49,12 +50,14 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
abstract fun getPwNodeId(): NodeId<EntryId> abstract fun getPwNodeId(): NodeId<EntryId>
open fun populateEntry(pwEntry: PwEntryV, iconFactory: IconImageFactory) { open fun populateEntry(pwEntry: PwEntryV, iconPool: IconPool) {
pwEntry.nodeId = getPwNodeId() pwEntry.nodeId = getPwNodeId()
pwEntry.title = getString(getColumnIndex(COLUMN_INDEX_TITLE)) pwEntry.title = getString(getColumnIndex(COLUMN_INDEX_TITLE))
val iconStandard = iconFactory.getIcon(getInt(getColumnIndex(COLUMN_INDEX_ICON_STANDARD))) val iconStandard = iconPool.getIcon(getInt(getColumnIndex(COLUMN_INDEX_ICON_STANDARD)))
pwEntry.icon = iconStandard val iconCustom = iconPool.getIcon(UUID(getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
pwEntry.icon = IconImage(iconStandard, iconCustom)
pwEntry.username = getString(getColumnIndex(COLUMN_INDEX_USERNAME)) pwEntry.username = getString(getColumnIndex(COLUMN_INDEX_USERNAME))
pwEntry.password = getString(getColumnIndex(COLUMN_INDEX_PASSWORD)) pwEntry.password = getString(getColumnIndex(COLUMN_INDEX_PASSWORD))

View File

@@ -19,7 +19,6 @@
*/ */
package com.kunzisoft.keepass.database.cursor package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
class EntryCursorKDB : EntryCursorUUID<EntryKDB>() { class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
@@ -30,9 +29,9 @@ class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
entry.id.mostSignificantBits, entry.id.mostSignificantBits,
entry.id.leastSignificantBits, entry.id.leastSignificantBits,
entry.title, entry.title,
entry.icon.iconId, entry.icon.standard.id,
DatabaseVersioned.UUID_ZERO.mostSignificantBits, entry.icon.custom.uuid.mostSignificantBits,
DatabaseVersioned.UUID_ZERO.leastSignificantBits, entry.icon.custom.uuid.leastSignificantBits,
entry.username, entry.username,
entry.password, entry.password,
entry.url, entry.url,

View File

@@ -20,9 +20,7 @@
package com.kunzisoft.keepass.database.cursor package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageFactory import com.kunzisoft.keepass.database.element.icon.IconPool
import java.util.UUID
class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() { class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
@@ -34,9 +32,9 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
entry.id.mostSignificantBits, entry.id.mostSignificantBits,
entry.id.leastSignificantBits, entry.id.leastSignificantBits,
entry.title, entry.title,
entry.icon.iconId, entry.icon.standard.id,
entry.iconCustom.uuid.mostSignificantBits, entry.icon.custom.uuid.mostSignificantBits,
entry.iconCustom.uuid.leastSignificantBits, entry.icon.custom.uuid.leastSignificantBits,
entry.username, entry.username,
entry.password, entry.password,
entry.url, entry.url,
@@ -52,14 +50,8 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
entryId++ entryId++
} }
override fun populateEntry(pwEntry: EntryKDBX, iconFactory: IconImageFactory) { override fun populateEntry(pwEntry: EntryKDBX, iconPool: IconPool) {
super.populateEntry(pwEntry, iconFactory) super.populateEntry(pwEntry, iconPool)
// Retrieve custom icon
val iconCustom = iconFactory.getIcon(
UUID(getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
pwEntry.iconCustom = iconCustom
// Retrieve extra fields // Retrieve extra fields
if (extraFieldCursor.moveToFirst()) { if (extraFieldCursor.moveToFirst()) {

View File

@@ -26,7 +26,7 @@ import android.util.Log
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.database.* import com.kunzisoft.keepass.database.element.database.*
import com.kunzisoft.keepass.database.element.icon.IconImageFactory import com.kunzisoft.keepass.database.element.icon.IconPool
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
@@ -51,7 +51,6 @@ import java.security.Key
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
import javax.crypto.KeyGenerator import javax.crypto.KeyGenerator
import javax.crypto.spec.IvParameterSpec
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -92,9 +91,9 @@ class Database {
return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey
} }
val iconFactory: IconImageFactory val iconPool: IconPool
get() { get() {
return mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory() return mDatabaseKDB?.iconPool ?: mDatabaseKDBX?.iconPool ?: IconPool()
} }
val allowName: Boolean val allowName: Boolean
@@ -625,6 +624,7 @@ class Database {
} }
fun clear(filesDirectory: File? = null) { fun clear(filesDirectory: File? = null) {
iconPool.clearCache()
drawFactory.clearCache() drawFactory.clearCache()
// Delete the cache of the database if present // Delete the cache of the database if present
mDatabaseKDB?.clearCache() mDatabaseKDB?.clearCache()

View File

@@ -109,7 +109,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
override var icon: IconImage override var icon: IconImage
get() { get() {
return entryKDB?.icon ?: entryKDBX?.icon ?: IconImageStandard() return entryKDB?.icon ?: entryKDBX?.icon ?: IconImage()
} }
set(value) { set(value) {
entryKDB?.icon = value entryKDB?.icon = value
@@ -257,31 +257,12 @@ class Entry : Node, EntryVersionedInterface<Group> {
} }
} }
/*
------------
KDB Methods
------------
*/
/**
* If it's a node with only meta information like Meta-info SYSTEM Database Color
* @return false by default, true if it's a meta stream
*/
val isMetaStream: Boolean
get() = entryKDB?.isMetaStream ?: false
/* /*
------------ ------------
KDBX Methods KDBX Methods
------------ ------------
*/ */
var iconCustom: IconImageCustom
get() = entryKDBX?.iconCustom ?: IconImageCustom.UNKNOWN_ICON
set(value) {
entryKDBX?.iconCustom = value
}
/** /**
* Retrieve extra fields to show, key is the label, value is the value of field (protected or not) * Retrieve extra fields to show, key is the label, value is the value of field (protected or not)
* @return Map of label/value * @return Map of label/value

View File

@@ -26,7 +26,6 @@ import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.* import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.GroupInfo import com.kunzisoft.keepass.model.GroupInfo
@@ -124,7 +123,7 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
} }
override var icon: IconImage override var icon: IconImage
get() = groupKDB?.icon ?: groupKDBX?.icon ?: IconImageStandard() get() = groupKDB?.icon ?: groupKDBX?.icon ?: IconImage()
set(value) { set(value) {
groupKDB?.icon = value groupKDB?.icon = value
groupKDBX?.icon = value groupKDBX?.icon = value

View File

@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.stream.readAllBytes import com.kunzisoft.keepass.stream.readAllBytes
import org.apache.commons.io.output.CountingOutputStream import org.apache.commons.io.output.CountingOutputStream
import java.io.* import java.io.*
import java.security.MessageDigest
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
@@ -176,6 +177,25 @@ class BinaryAttachment : Parcelable {
throw IOException("Unable to delete temp file " + dataFile!!.absolutePath) throw IOException("Unable to delete temp file " + dataFile!!.absolutePath)
} }
/**
* MD5 of the raw encrypted file in temp folder, only to compare binary data
*/
fun md5(): String {
val md = MessageDigest.getInstance("MD5")
if (dataFile == null)
return ""
return FileInputStream(dataFile).use { fis ->
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
generateSequence {
when (val bytesRead = fis.read(buffer)) {
-1 -> null
else -> bytesRead
}
}.forEach { bytesRead -> md.update(buffer, 0, bytesRead) }
md.digest().joinToString("") { "%02x".format(it) }
}
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) if (this === other)
return true return true

View File

@@ -99,7 +99,13 @@ class BinaryPool {
private fun orderedBinaries(): List<KeyBinary> { private fun orderedBinaries(): List<KeyBinary> {
val keyBinaryList = ArrayList<KeyBinary>() val keyBinaryList = ArrayList<KeyBinary>()
for ((key, binary) in pool) { for ((key, binary) in pool) {
keyBinaryList.add(KeyBinary(key, binary)) // Don't deduplicate
val existentBinary = keyBinaryList.find { it.binary.md5() == binary.md5() }
if (existentBinary == null) {
keyBinaryList.add(KeyBinary(binary, key))
} else {
existentBinary.addKey(key)
}
} }
return keyBinaryList return keyBinaryList
} }
@@ -108,7 +114,7 @@ class BinaryPool {
* To register a binary with a ref corresponding to an ordered index * To register a binary with a ref corresponding to an ordered index
*/ */
fun getBinaryIndexFromKey(key: Int): Int? { fun getBinaryIndexFromKey(key: Int): Int? {
val index = orderedBinaries().indexOfFirst { it.key == key } val index = orderedBinaries().indexOfFirst { it.keys.contains(key) }
return if (index < 0) return if (index < 0)
null null
else else
@@ -118,8 +124,10 @@ class BinaryPool {
/** /**
* Different from doForEach, provide an ordered index to each binary * Different from doForEach, provide an ordered index to each binary
*/ */
fun doForEachOrderedBinary(action: (index: Int, keyBinary: KeyBinary) -> Unit) { fun doForEachOrderedBinary(action: (index: Int, binary: BinaryAttachment) -> Unit) {
orderedBinaries().forEachIndexed(action) orderedBinaries().forEachIndexed { index, keyBinary ->
action.invoke(index, keyBinary.binary)
}
} }
/** /**
@@ -149,7 +157,16 @@ class BinaryPool {
} }
/** /**
* Utility data class to order binaries * Utility class to order binaries
*/ */
data class KeyBinary(val key: Int, val binary: BinaryAttachment) private class KeyBinary(val binary: BinaryAttachment, key: Int) {
val keys = HashSet<Int>()
init {
addKey(key)
}
fun addKey(key: Int) {
keys.add(key)
}
}
} }

View File

@@ -24,6 +24,7 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned import com.kunzisoft.keepass.database.element.node.NodeVersioned
@@ -175,6 +176,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return false return false
} }
override fun getStandardIcon(iconId: Int): IconImageStandard {
return this.iconPool.getIcon(iconId)
}
override fun containsCustomData(): Boolean { override fun containsCustomData(): Boolean {
return false return false
} }
@@ -223,7 +228,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
// Create recycle bin // Create recycle bin
val recycleBinGroup = createGroup().apply { val recycleBinGroup = createGroup().apply {
title = BACKUP_FOLDER_TITLE title = BACKUP_FOLDER_TITLE
icon = iconFactory.trashIcon icon.standard = getStandardIcon(IconImageStandard.TRASH_ID)
} }
addGroupTo(recycleBinGroup, rootGroup) addGroupTo(recycleBinGroup, rootGroup)
backupGroupId = recycleBinGroup.id backupGroupId = recycleBinGroup.id

View File

@@ -36,6 +36,7 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BAC
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
@@ -105,7 +106,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var lastTopVisibleGroupUUID = UUID_ZERO var lastTopVisibleGroupUUID = UUID_ZERO
var memoryProtection = MemoryProtectionConfig() var memoryProtection = MemoryProtectionConfig()
val deletedObjects = ArrayList<DeletedObject>() val deletedObjects = ArrayList<DeletedObject>()
val customIcons = ArrayList<IconImageCustom>()
val customData = HashMap<String, String>() val customData = HashMap<String, String>()
var binaryPool = BinaryPool() var binaryPool = BinaryPool()
@@ -129,7 +129,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
kdbxVersion = FILE_VERSION_32_3 kdbxVersion = FILE_VERSION_32_3
val group = createGroup().apply { val group = createGroup().apply {
title = rootName title = rootName
icon = iconFactory.folderIcon icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID)
} }
rootGroup = group rootGroup = group
addGroupIndex(group) addGroupIndex(group)
@@ -307,16 +307,20 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
this.dataEngine = dataEngine this.dataEngine = dataEngine
} }
fun getCustomIcons(): List<IconImageCustom> { override fun getStandardIcon(iconId: Int): IconImageStandard {
return customIcons return this.iconPool.getIcon(iconId)
} }
fun addCustomIcon(customIcon: IconImageCustom) { fun getCustomIcon(iconUuid: UUID): IconImageCustom {
this.customIcons.add(customIcon) return this.iconPool.getIcon(iconUuid)
} }
fun getCustomData(): Map<String, String> { fun putCustomIcon(customIcon: IconImageCustom) {
return customData this.iconPool.putIcon(customIcon)
}
fun containsCustomIcons(): Boolean {
return this.iconPool.containsCustomIcons()
} }
fun putCustomData(label: String, value: String) { fun putCustomData(label: String, value: String) {
@@ -324,7 +328,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
override fun containsCustomData(): Boolean { override fun containsCustomData(): Boolean {
return getCustomData().isNotEmpty() return customData.isNotEmpty()
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -550,7 +554,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// Create recycle bin // Create recycle bin
val recycleBinGroup = createGroup().apply { val recycleBinGroup = createGroup().apply {
title = resources.getString(R.string.recycle_bin) title = resources.getString(R.string.recycle_bin)
icon = iconFactory.trashIcon icon.standard = getStandardIcon(IconImageStandard.TRASH_ID)
enableAutoType = false enableAutoType = false
enableSearching = false enableSearching = false
isExpanded = false isExpanded = false

View File

@@ -23,7 +23,8 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.entry.EntryVersioned import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.group.GroupVersioned import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconPool
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
@@ -55,8 +56,7 @@ abstract class DatabaseVersioned<
var finalKey: ByteArray? = null var finalKey: ByteArray? = null
protected set protected set
var iconFactory = IconImageFactory() val iconPool = IconPool()
protected set
var changeDuplicateId = false var changeDuplicateId = false
@@ -329,6 +329,8 @@ abstract class DatabaseVersioned<
abstract fun rootCanContainsEntry(): Boolean abstract fun rootCanContainsEntry(): Boolean
abstract fun getStandardIcon(iconId: Int): IconImageStandard
abstract fun containsCustomData(): Boolean abstract fun containsCustomData(): Boolean
fun addGroupTo(newGroup: Group, parent: Group?) { fun addGroupTo(newGroup: Group, parent: Group?) {

View File

@@ -21,15 +21,15 @@ package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.database.BinaryAttachment
import com.kunzisoft.keepass.database.element.Attachment
import java.util.* import java.util.*
import kotlin.collections.ArrayList
/** /**
* Structure containing information about one entry. * Structure containing information about one entry.
@@ -68,7 +68,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
if (username.isEmpty()) return false if (username.isEmpty()) return false
if (username != PMS_ID_USER) return false if (username != PMS_ID_USER) return false
if (url.isEmpty()) return false if (url.isEmpty()) return false
return if (url != PMS_ID_URL) false else icon.isMetaStreamIcon if (url != PMS_ID_URL) return false
return icon.standard.id == KEY_ID
} }
override fun initNodeId(): NodeId<UUID> { override fun initNodeId(): NodeId<UUID> {

View File

@@ -26,9 +26,6 @@ import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.database.BinaryPool import com.kunzisoft.keepass.database.element.database.BinaryPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
@@ -48,19 +45,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
@Transient @Transient
private var mDecodeRef = false private var mDecodeRef = false
override var icon: IconImage
get() {
return when {
iconCustom.isUnknown -> super.icon
else -> iconCustom
}
}
set(value) {
if (value is IconImageStandard)
iconCustom = IconImageCustom.UNKNOWN_ICON
super.icon = value
}
var iconCustom = IconImageCustom.UNKNOWN_ICON
var customData = LinkedHashMap<String, String>() var customData = LinkedHashMap<String, String>()
var fields = LinkedHashMap<String, ProtectedString>() var fields = LinkedHashMap<String, ProtectedString>()
var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId> var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId>
@@ -103,7 +87,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor() : super() constructor() : super()
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
usageCount = UnsignedLong(parcel.readLong()) usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel) customData = ParcelableUtil.readStringParcelableMap(parcel)
@@ -121,7 +104,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount.toKotlinLong()) dest.writeLong(usageCount.toKotlinLong())
dest.writeParcelable(locationChanged, flags) dest.writeParcelable(locationChanged, flags)
ParcelableUtil.writeStringParcelableMap(dest, customData) ParcelableUtil.writeStringParcelableMap(dest, customData)
@@ -143,7 +125,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
*/ */
fun updateWith(source: EntryKDBX, copyHistory: Boolean = true) { fun updateWith(source: EntryKDBX, copyHistory: Boolean = true) {
super.updateWith(source) super.updateWith(source)
iconCustom = IconImageCustom(source.iconCustom)
usageCount = source.usageCount usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged) locationChanged = DateInstant(source.locationChanged)
// Add all custom elements in map // Add all custom elements in map

View File

@@ -21,37 +21,18 @@ package com.kunzisoft.keepass.database.element.group
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.utils.UnsignedLong import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
import java.util.HashMap
import java.util.UUID
class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface { class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
// TODO Encapsulate
override var icon: IconImage
get() {
return if (iconCustom.isUnknown)
super.icon
else
iconCustom
}
set(value) {
if (value is IconImageStandard)
iconCustom = IconImageCustom.UNKNOWN_ICON
super.icon = value
}
var iconCustom = IconImageCustom.UNKNOWN_ICON
private val customData = HashMap<String, String>() private val customData = HashMap<String, String>()
var notes = "" var notes = ""
@@ -77,7 +58,6 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor() : super() constructor() : super()
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
usageCount = UnsignedLong(parcel.readLong()) usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
// TODO customData = ParcelableUtil.readStringParcelableMap(parcel); // TODO customData = ParcelableUtil.readStringParcelableMap(parcel);
@@ -101,7 +81,6 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeParcelable(iconCustom, flags)
dest.writeLong(usageCount.toKotlinLong()) dest.writeLong(usageCount.toKotlinLong())
dest.writeParcelable(locationChanged, flags) dest.writeParcelable(locationChanged, flags)
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData); // TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
@@ -115,7 +94,6 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
fun updateWith(source: GroupKDBX) { fun updateWith(source: GroupKDBX) {
super.updateWith(source) super.updateWith(source)
iconCustom = IconImageCustom(source.iconCustom)
usageCount = source.usageCount usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged) locationChanged = DateInstant(source.locationChanged)
// Add all custom elements in map // Add all custom elements in map

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2021 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -19,19 +19,49 @@
*/ */
package com.kunzisoft.keepass.database.element.icon package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
abstract class IconImage protected constructor() : Parcelable { class IconImage() : Parcelable {
abstract val iconId: Int var standard: IconImageStandard = IconImageStandard()
abstract val isUnknown: Boolean var custom: IconImageCustom = IconImageCustom()
abstract val isMetaStreamIcon: Boolean
constructor(iconImageStandard: IconImageStandard) : this() {
this.standard = iconImageStandard
}
constructor(iconImageCustom: IconImageCustom) : this() {
this.custom = iconImageCustom
}
constructor(iconImageStandard: IconImageStandard,
iconImageCustom: IconImageCustom) : this() {
this.standard = iconImageStandard
this.custom = iconImageCustom
}
constructor(parcel: Parcel) : this() {
standard = parcel.readParcelable(IconImageStandard::class.java.classLoader) ?: standard
custom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: custom
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(standard, flags)
parcel.writeParcelable(custom, flags)
}
override fun describeContents(): Int { override fun describeContents(): Int {
return 0 return 0
} }
companion object { companion object CREATOR : Parcelable.Creator<IconImage> {
const val UNKNOWN_ID = -1 override fun createFromParcel(parcel: Parcel): IconImage {
return IconImage(parcel)
}
override fun newArray(size: Int): Array<IconImage?> {
return arrayOfNulls(size)
}
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2021 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -22,36 +22,34 @@ package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.*
import java.util.UUID class IconImageCustom() : Parcelable {
class IconImageCustom : IconImage { var uuid: UUID = DatabaseVersioned.UUID_ZERO
val uuid: UUID
@Transient @Transient
var imageData: ByteArray = ByteArray(0) var imageData: ByteArray = ByteArray(0)
constructor(uuid: UUID, data: ByteArray) : super() { constructor(uuid: UUID, data: ByteArray) : this() {
this.uuid = uuid this.uuid = uuid
this.imageData = data this.imageData = data
} }
constructor(uuid: UUID) : super() { constructor(uuid: UUID) : this() {
this.uuid = uuid this.uuid = uuid
this.imageData = ByteArray(0) this.imageData = ByteArray(0)
} }
constructor(icon: IconImageCustom) : super() { constructor(parcel: Parcel) : this() {
uuid = icon.uuid
imageData = icon.imageData
}
constructor(parcel: Parcel) {
uuid = parcel.readSerializable() as UUID uuid = parcel.readSerializable() as UUID
// TODO Take too much memories // TODO Take too much memories
// parcel.readByteArray(imageData); // parcel.readByteArray(imageData);
} }
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(uuid) dest.writeSerializable(uuid)
// Too big for a parcelable dest.writeByteArray(imageData); // Too big for a parcelable dest.writeByteArray(imageData);
@@ -74,17 +72,10 @@ class IconImageCustom : IconImage {
return uuid == other.uuid return uuid == other.uuid
} }
override val iconId: Int val isUnknown: Boolean
get() = UNKNOWN_ID get() = uuid == DatabaseVersioned.UUID_ZERO
override val isUnknown: Boolean
get() = this == UNKNOWN_ICON
override val isMetaStreamIcon: Boolean
get() = false
companion object { companion object {
val UNKNOWN_ICON = IconImageCustom(DatabaseVersioned.UUID_ZERO, ByteArray(0))
@JvmField @JvmField
val CREATOR: Parcelable.Creator<IconImageCustom> = object : Parcelable.Creator<IconImageCustom> { val CREATOR: Parcelable.Creator<IconImageCustom> = object : Parcelable.Creator<IconImageCustom> {

View File

@@ -22,32 +22,37 @@ package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
class IconImageStandard : IconImage { class IconImageStandard : Parcelable {
val id: Int
constructor() { constructor() {
this.iconId = KEY this.id = KEY_ID
} }
constructor(iconId: Int) { constructor(iconId: Int) {
this.iconId = iconId if (iconId < MIN_ID || iconId > MAX_ID)
} this.id = KEY_ID
else
constructor(icon: IconImageStandard) { this.id = iconId
this.iconId = icon.iconId
} }
constructor(parcel: Parcel) { constructor(parcel: Parcel) {
iconId = parcel.readInt() id = parcel.readInt()
}
override fun describeContents(): Int {
return 0
} }
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(iconId) dest.writeInt(id)
} }
override fun hashCode(): Int { override fun hashCode(): Int {
val prime = 31 val prime = 31
var result = 1 var result = 1
result = prime * result + iconId result = prime * result + id
return result return result
} }
@@ -59,22 +64,16 @@ class IconImageStandard : IconImage {
if (other !is IconImageStandard) { if (other !is IconImageStandard) {
return false return false
} }
return iconId == other.iconId return id == other.id
} }
override val iconId: Int
override val isUnknown: Boolean
get() = iconId == UNKNOWN_ID
override val isMetaStreamIcon: Boolean
get() = iconId == 0
companion object { companion object {
const val KEY = 0 const val KEY_ID = 0
const val TRASH = 43 const val TRASH_ID = 43
const val FOLDER = 48 const val FOLDER_ID = 48
const val MIN_ID = 0
const val MAX_ID = 48
@JvmField @JvmField
val CREATOR: Parcelable.Creator<IconImageStandard> = object : Parcelable.Creator<IconImageStandard> { val CREATOR: Parcelable.Creator<IconImageStandard> = object : Parcelable.Creator<IconImageStandard> {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2021 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -19,49 +19,26 @@
*/ */
package com.kunzisoft.keepass.database.element.icon package com.kunzisoft.keepass.database.element.icon
import org.apache.commons.collections.map.AbstractReferenceMap
import org.apache.commons.collections.map.ReferenceMap
import java.util.UUID import java.util.UUID
class IconImageFactory { class IconPool {
/** customIconMap
* Cache for icon drawable.
* Keys: Integer, Values: IconImageStandard
*/
private val cache = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)
/** standardIconMap private val standardCache = HashMap<Int, IconImageStandard?>()
* Cache for icon drawable. private val customCache = HashMap<UUID, IconImageCustom?>()
* Keys: UUID, Values: IconImageCustom
*/
private val customCache = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK)
val unknownIcon: IconImageStandard
get() = getIcon(IconImage.UNKNOWN_ID)
val keyIcon: IconImageStandard
get() = getIcon(IconImageStandard.KEY)
val trashIcon: IconImageStandard
get() = getIcon(IconImageStandard.TRASH)
val folderIcon: IconImageStandard
get() = getIcon(IconImageStandard.FOLDER)
fun getIcon(iconId: Int): IconImageStandard { fun getIcon(iconId: Int): IconImageStandard {
var icon: IconImageStandard? = cache[iconId] as IconImageStandard? var icon: IconImageStandard? = standardCache[iconId]
if (icon == null) { if (icon == null) {
icon = IconImageStandard(iconId) icon = IconImageStandard(iconId)
cache[iconId] = icon standardCache[iconId] = icon
} }
return icon return icon
} }
fun getIcon(iconUuid: UUID): IconImageCustom { fun getIcon(iconUuid: UUID): IconImageCustom {
var icon: IconImageCustom? = customCache[iconUuid] as IconImageCustom? var icon: IconImageCustom? = customCache[iconUuid]
if (icon == null) { if (icon == null) {
icon = IconImageCustom(iconUuid) icon = IconImageCustom(iconUuid)
@@ -71,7 +48,25 @@ class IconImageFactory {
return icon return icon
} }
fun put(icon: IconImageCustom) { fun putIcon(icon: IconImageCustom) {
customCache[icon.uuid] = icon customCache[icon.uuid] = icon
} }
fun containsCustomIcons(): Boolean {
return customCache.isNotEmpty()
}
fun doForEachCustomIcon(action: (customIcon: IconImageCustom) -> Unit) {
for ((_, customIcon) in customCache) {
action.invoke(customIcon!!)
}
}
/**
* Clear the cache of icons
*/
fun clearCache() {
standardCache.clear()
customCache.clear()
}
} }

View File

@@ -22,11 +22,10 @@ package com.kunzisoft.keepass.database.element.node
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import org.joda.time.LocalDateTime import org.joda.time.LocalDateTime
/** /**
@@ -88,7 +87,7 @@ abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, En
final override var parent: Parent? = null final override var parent: Parent? = null
override var icon: IconImage = IconImageStandard() final override var icon: IconImage = IconImage()
final override var creationTime: DateInstant = DateInstant() final override var creationTime: DateInstant = DateInstant()

View File

@@ -231,7 +231,7 @@ class DatabaseInputKDB(cacheDirectory: File)
if (iconId == -1) { if (iconId == -1) {
iconId = 0 iconId = 0
} }
entry.icon = mDatabase.iconFactory.getIcon(iconId) entry.icon.standard = mDatabase.getStandardIcon(iconId)
} }
} }
0x0004 -> { 0x0004 -> {
@@ -260,7 +260,7 @@ class DatabaseInputKDB(cacheDirectory: File)
} }
0x0007 -> { 0x0007 -> {
newGroup?.let { group -> newGroup?.let { group ->
group.icon = mDatabase.iconFactory.getIcon(cipherInputStream.readBytes4ToUInt().toKotlinInt()) group.icon.standard = mDatabase.getStandardIcon(cipherInputStream.readBytes4ToUInt().toKotlinInt())
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
entry.password = cipherInputStream.readBytesToString(fieldSize,false) entry.password = cipherInputStream.readBytesToString(fieldSize,false)

View File

@@ -507,9 +507,9 @@ class DatabaseInputKDBX(cacheDirectory: File)
} else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) {
ctxGroup?.notes = readString(xpp) ctxGroup?.notes = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt()) ctxGroup?.icon?.standard = mDatabase.getStandardIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt())
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
ctxGroup?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp)) ctxGroup?.icon?.custom = mDatabase.getCustomIcon(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
return switchContext(ctx, KdbContext.GroupTimes, xpp) return switchContext(ctx, KdbContext.GroupTimes, xpp)
} else if (name.equals(DatabaseKDBXXML.ElemIsExpanded, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemIsExpanded, ignoreCase = true)) {
@@ -561,9 +561,9 @@ class DatabaseInputKDBX(cacheDirectory: File)
KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) { KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) {
ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp)) ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt()) ctxEntry?.icon?.standard = mDatabase.getStandardIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt())
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
ctxEntry?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp)) ctxEntry?.icon?.custom = mDatabase.getCustomIcon(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) {
ctxEntry?.foregroundColor = readString(xpp) ctxEntry?.foregroundColor = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemBgColor, ignoreCase = true)) { } else if (name.equals(DatabaseKDBXXML.ElemBgColor, ignoreCase = true)) {
@@ -706,8 +706,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
} else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) { } else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) {
if (customIconID != DatabaseVersioned.UUID_ZERO && customIconData != null) { if (customIconID != DatabaseVersioned.UUID_ZERO && customIconData != null) {
val icon = IconImageCustom(customIconID, customIconData!!) val icon = IconImageCustom(customIconID, customIconData!!)
mDatabase.addCustomIcon(icon) mDatabase.putCustomIcon(icon)
mDatabase.iconFactory.put(icon)
} }
customIconID = DatabaseVersioned.UUID_ZERO customIconID = DatabaseVersioned.UUID_ZERO

View File

@@ -35,7 +35,6 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BA
import com.kunzisoft.keepass.database.element.entry.AutoType import com.kunzisoft.keepass.database.element.entry.AutoType
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
@@ -137,24 +136,23 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
dataOutputStream.writeInt(streamKeySize) dataOutputStream.writeInt(streamKeySize)
dataOutputStream.write(header.innerRandomStreamKey) dataOutputStream.write(header.innerRandomStreamKey)
database.binaryPool.doForEachOrderedBinary { _, keyBinary -> val binaryCipherKey = database.loadedCipherKey
val protectedBinary = keyBinary.binary ?: throw IOException("Unable to retrieve cipher key to write binaries")
val binaryCipherKey = database.loadedCipherKey database.binaryPool.doForEachOrderedBinary { _, binary ->
?: throw IOException("Unable to retrieve cipher key to write binaries")
// Force decompression to add binary in header // Force decompression to add binary in header
protectedBinary.decompress(binaryCipherKey) binary.decompress(binaryCipherKey)
// Write type binary // Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size // Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length + 1)) dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.length + 1))
// Write protected flag // Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (protectedBinary.isProtected) { if (binary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
} }
dataOutputStream.writeByte(flag) dataOutputStream.writeByte(flag)
protectedBinary.getInputDataStream(binaryCipherKey).use { inputStream -> binary.getInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
dataOutputStream.write(buffer) dataOutputStream.write(buffer)
} }
@@ -363,10 +361,10 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeUuid(DatabaseKDBXXML.ElemUuid, group.id) writeUuid(DatabaseKDBXXML.ElemUuid, group.id)
writeObject(DatabaseKDBXXML.ElemName, group.title) writeObject(DatabaseKDBXXML.ElemName, group.title)
writeObject(DatabaseKDBXXML.ElemNotes, group.notes) writeObject(DatabaseKDBXXML.ElemNotes, group.notes)
writeObject(DatabaseKDBXXML.ElemIcon, group.icon.iconId.toLong()) writeObject(DatabaseKDBXXML.ElemIcon, group.icon.standard.id.toLong())
if (group.iconCustom != IconImageCustom.UNKNOWN_ICON) { if (!group.icon.custom.isUnknown) {
writeUuid(DatabaseKDBXXML.ElemCustomIconID, group.iconCustom.uuid) writeUuid(DatabaseKDBXXML.ElemCustomIconID, group.icon.custom.uuid)
} }
writeTimes(group) writeTimes(group)
@@ -388,10 +386,10 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemEntry) xml.startTag(null, DatabaseKDBXXML.ElemEntry)
writeUuid(DatabaseKDBXXML.ElemUuid, entry.id) writeUuid(DatabaseKDBXXML.ElemUuid, entry.id)
writeObject(DatabaseKDBXXML.ElemIcon, entry.icon.iconId.toLong()) writeObject(DatabaseKDBXXML.ElemIcon, entry.icon.standard.id.toLong())
if (entry.iconCustom != IconImageCustom.UNKNOWN_ICON) { if (!entry.icon.custom.isUnknown) {
writeUuid(DatabaseKDBXXML.ElemCustomIconID, entry.iconCustom.uuid) writeUuid(DatabaseKDBXXML.ElemCustomIconID, entry.icon.custom.uuid)
} }
writeObject(DatabaseKDBXXML.ElemFgColor, entry.foregroundColor) writeObject(DatabaseKDBXXML.ElemFgColor, entry.foregroundColor)
@@ -499,10 +497,9 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemBinaries) xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
// Use indexes because necessarily (binary header ref is the order) // Use indexes because necessarily (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary -> mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary) xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString()) xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
val binary = keyBinary.binary
if (binary.length > 0) { if (binary.length > 0) {
if (binary.isCompressed) { if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue) xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
@@ -700,16 +697,15 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeCustomIconList() { private fun writeCustomIconList() {
val customIcons = mDatabaseKDBX.customIcons if (!mDatabaseKDBX.containsCustomIcons()) return
if (customIcons.size == 0) return
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons) xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
for (icon in customIcons) { mDatabaseKDBX.iconPool.doForEachCustomIcon { customIcon ->
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem) xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, icon.uuid) writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, customIcon.uuid)
writeObject(DatabaseKDBXXML.ElemCustomIconItemData, String(Base64.encode(icon.imageData, BASE_64_FLAG))) writeObject(DatabaseKDBXXML.ElemCustomIconItemData, String(Base64.encode(customIcon.imageData, BASE_64_FLAG)))
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem) xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
} }

View File

@@ -53,7 +53,7 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
// Image ID // Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE) mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(IMAGEID_FIELD_SIZE) mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.icon.iconId))) mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.icon.standard.id)))
// Title // Title
//byte[] title = mEntry.title.getBytes("UTF-8"); //byte[] title = mEntry.title.getBytes("UTF-8");

View File

@@ -72,7 +72,7 @@ class GroupOutputKDB(private val mGroup: GroupKDB,
// Image ID // Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE) mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(IMAGEID_FIELD_SIZE) mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.icon.iconId))) mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.icon.standard.id)))
// Level // Level
mOutputStream.write(LEVEL_FIELD_TYPE) mOutputStream.write(LEVEL_FIELD_TYPE)

View File

@@ -38,7 +38,6 @@ import androidx.core.widget.ImageViewCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import org.apache.commons.collections.map.AbstractReferenceMap import org.apache.commons.collections.map.AbstractReferenceMap
import org.apache.commons.collections.map.ReferenceMap import org.apache.commons.collections.map.ReferenceMap
@@ -110,17 +109,12 @@ class IconDrawableFactory {
* Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed * Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed
*/ */
fun getIconSuperDrawable(context: Context, icon: IconImage, width: Int, tint: Boolean = false, tintColor: Int = Color.WHITE): SuperDrawable { fun getIconSuperDrawable(context: Context, icon: IconImage, width: Int, tint: Boolean = false, tintColor: Int = Color.WHITE): SuperDrawable {
return when (icon) { return if (!icon.custom.isUnknown) {
is IconImageStandard -> { SuperDrawable(getIconDrawable(context.resources, icon.custom))
val resId = IconPackChooser.getSelectedIconPack(context)?.iconToResId(icon.iconId) ?: R.drawable.ic_blank_32dp } else IconPackChooser.getSelectedIconPack(context)?.iconToResId(icon.standard.id)?.let { resId ->
getIconSuperDrawable(context, resId, width, tint, tintColor) getIconSuperDrawable(context, resId, width, tint, tintColor)
} } ?: run {
is IconImageCustom -> { SuperDrawable(PatternIcon(context.resources).blankDrawable)
SuperDrawable(getIconDrawable(context.resources, icon))
}
else -> {
SuperDrawable(PatternIcon(context.resources).blankDrawable)
}
} }
} }

View File

@@ -3,14 +3,14 @@ package com.kunzisoft.keepass.model
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.FOLDER import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.FOLDER_ID
class GroupInfo : NodeInfo { class GroupInfo : NodeInfo {
var notes: String? = null var notes: String? = null
init { init {
icon = IconImageStandard(FOLDER) icon.standard = IconImageStandard(FOLDER_ID)
} }
constructor(): super() constructor(): super()

View File

@@ -4,12 +4,11 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
open class NodeInfo() : Parcelable { open class NodeInfo() : Parcelable {
var title: String = "" var title: String = ""
var icon: IconImage = IconImageStandard() var icon: IconImage = IconImage()
var creationTime: DateInstant = DateInstant() var creationTime: DateInstant = DateInstant()
var lastModificationTime: DateInstant = DateInstant() var lastModificationTime: DateInstant = DateInstant()
var expires: Boolean = false var expires: Boolean = false

View File

@@ -1 +1,3 @@
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation) * Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation) #532 #714
* Fix binary deduplication #715
* Fix IconId #901

View File

@@ -1 +1,3 @@
* Thèmes sombres (ATTENTION: Vous devez reselectionner votre theme si mise à niveau d'une ancienne installation) * Thèmes sombres (ATTENTION: Vous devez reselectionner votre theme si mise à niveau d'une ancienne installation) #532 #714
* Correction de la duplication des binaires #715
* Correction IconId #901