Refactor fragment edit entry, using EntryInfo

This commit is contained in:
J-Jamet
2020-09-15 19:04:20 +02:00
parent 1d57309db9
commit 7247de6908
8 changed files with 208 additions and 176 deletions

View File

@@ -311,7 +311,9 @@ class EntryActivity : LockingActivity() {
// Assign custom fields
if (mDatabase?.allowEntryCustomFields() == true) {
entryContentsView?.clearExtraFields()
for ((label, value) in entry.customFields) {
entry.getExtraFields().forEach { field ->
val label = field.name
val value = field.protectedValue
val allowCopyProtectedField = !value.isProtected || allowCopyPasswordAndProtectedFields
if (allowCopyProtectedField) {
entryContentsView?.addExtraField(label, value, allowCopyProtectedField) {

View File

@@ -132,7 +132,7 @@ class EntryEditActivity : LockingActivity(),
// Likely the app has been killed exit the activity
mDatabase = Database.getInstance()
var tempEntry: Entry? = null
var tempEntryInfo: EntryInfo? = null
// Entry is retrieve, it's an entry to update
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
@@ -149,14 +149,7 @@ class EntryEditActivity : LockingActivity(),
entry.parent = mParent
}
}
mEntry?.let { entry ->
// Create a copy to modify
tempEntry = Entry(entry).also { newEntry ->
// WARNING Remove the parent to keep memory with parcelable
newEntry.removeParent()
}
}
tempEntryInfo = mEntry?.getEntryInfo(mDatabase, true)
}
// Parent is retrieve, it's a new entry to create
@@ -165,12 +158,15 @@ class EntryEditActivity : LockingActivity(),
mParent = mDatabase?.getGroupById(it)
// Add the default icon from parent if not a folder
val parentIcon = mParent?.icon
tempEntryInfo = mDatabase?.createEntry()?.getEntryInfo(mDatabase, true)
// Set default icon
if (parentIcon != null
&& parentIcon.iconId != IconImage.UNKNOWN_ID
&& parentIcon.iconId != IconImageStandard.FOLDER) {
tempEntry?.icon = parentIcon
tempEntryInfo?.icon = parentIcon
}
tempEntry = mDatabase?.createEntry()
// Set default username
tempEntryInfo?.username = mDatabase?.defaultUsername ?: ""
}
// Build fragment to manage entry modification
@@ -181,16 +177,14 @@ class EntryEditActivity : LockingActivity(),
supportFragmentManager.beginTransaction()
.replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG)
.commit()
mDatabase?.let { database ->
entryEditFragment?.setDatabase(database)
}
tempEntry?.let {
entryEditFragment?.setEntry(it, mIsNew)
entryEditFragment?.drawFactory = mDatabase?.drawFactory
tempEntryInfo?.let {
entryEditFragment?.setEntryInfo(it)
}
entryEditFragment?.apply {
applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this@EntryEditActivity))
setOnDateClickListener = View.OnClickListener {
expiresDate.date.let { expiresDate ->
expiryTime.date.let { expiresDate ->
val dateTime = DateTime(expiresDate)
val defaultYear = dateTime.year
val defaultMonth = dateTime.monthOfYear-1
@@ -208,6 +202,7 @@ class EntryEditActivity : LockingActivity(),
}
setOnRemoveAttachment = { attachment ->
mAttachmentFileBinderManager?.removeBinaryAttachment(attachment)
removeAttachment(EntryAttachmentState(attachment, StreamDirection.DOWNLOAD))
}
setOnEditCustomField = { field ->
editCustomField(field)
@@ -475,10 +470,17 @@ class EntryEditActivity : LockingActivity(),
*/
private fun saveEntry() {
// Get the temp entry
entryEditFragment?.getEntry()?.let { newEntry ->
entryEditFragment?.getEntryInfo()?.let { newEntryInfo ->
// WARNING Add the parent previously deleted
newEntry.parent = mEntry?.parent
if (mIsNew) {
// Create new one
mDatabase?.createEntry()
} else {
// Create a clone
Entry(mEntry!!)
}?.let { newEntry ->
newEntry.setEntryInfo(mDatabase, newEntryInfo)
// Build info
newEntry.lastAccessTime = DateInstant()
newEntry.lastModificationTime = DateInstant()
@@ -512,6 +514,7 @@ class EntryEditActivity : LockingActivity(),
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
@@ -603,7 +606,7 @@ class EntryEditActivity : LockingActivity(),
// Update the otp field with otpauth:// url
val otpField = OtpEntryFields.buildOtpField(otpElement,
mEntry?.title, mEntry?.username)
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
mEntry?.putExtraField(Field(otpField.name, otpField.protectedValue))
entryEditFragment?.apply {
putExtraField(otpField)
getExtraFieldViewPosition(otpField) { position ->
@@ -622,9 +625,9 @@ class EntryEditActivity : LockingActivity(),
// To fix android 4.4 issue
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
if (datePicker?.isShown == true) {
entryEditFragment?.expiresDate?.date?.let { expiresDate ->
entryEditFragment?.expiryTime?.date?.let { expiresDate ->
// Save the date
entryEditFragment?.expiresDate =
entryEditFragment?.expiryTime =
DateInstant(DateTime(expiresDate)
.withYear(year)
.withMonthOfYear(month + 1)
@@ -641,9 +644,9 @@ class EntryEditActivity : LockingActivity(),
}
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
entryEditFragment?.expiresDate?.date?.let { expiresDate ->
entryEditFragment?.expiryTime?.date?.let { expiresDate ->
// Save the date
entryEditFragment?.expiresDate =
entryEditFragment?.expiryTime =
DateInstant(DateTime(expiresDate)
.withHourOfDay(hours)
.withMinuteOfHour(minutes)

View File

@@ -39,13 +39,12 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.view.applyFontVisibility
@@ -86,9 +85,7 @@ class EntryEditFragment: StylishFragment() {
var setOnRemoveAttachment: ((Attachment) -> Unit)? = null
// Elements to modify the current entry
private var mDatabase: Database? = null
private var mEntry: Entry? = null
private var mIsNewEntry = true
private var mEntryInfo = EntryInfo()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
@@ -149,94 +146,58 @@ class EntryEditFragment: StylishFragment() {
taIconColor?.recycle()
// Retrieve the new entry after an orientation change
if (savedInstanceState?.containsKey(KEY_TEMP_ENTRY) == true) {
mEntry = savedInstanceState.getParcelable(KEY_TEMP_ENTRY)
if (savedInstanceState?.containsKey(KEY_TEMP_ENTRY_INFO) == true) {
mEntryInfo = savedInstanceState.getParcelable(KEY_TEMP_ENTRY_INFO) ?: mEntryInfo
}
mDatabase?.let { database ->
mEntry?.let { entry ->
populateViewsWithEntry(database, entry, mIsNewEntry)
}
}
populateViewsWithEntry()
return rootView
}
fun setDatabase(database: Database) {
mDatabase = database
fun getEntryInfo(): EntryInfo? {
populateEntryWithViews()
return mEntryInfo
}
fun getEntry(): Entry? {
mDatabase?.let { database ->
mEntry?.let { entry ->
populateEntryWithViews(database, entry)
}
}
return mEntry
fun setEntryInfo(entryInfo: EntryInfo) {
populateViewsWithEntry()
mEntryInfo = entryInfo
}
fun setEntry(entry: Entry, isNewEntry: Boolean) {
mEntry = entry
mIsNewEntry = isNewEntry
}
private fun populateViewsWithEntry(database: Database, entry: Entry, isNewEntry: Boolean) {
// Don't start the field reference manager, we want to see the raw ref
database.stopManageEntry(entry)
private fun populateViewsWithEntry() {
try {
// Set info in view
icon = entry.icon
title = entry.title
username = if (isNewEntry && entry.username.isEmpty())
database.defaultUsername
else
entry.username
url = entry.url
password = entry.password
expires = entry.expires
if (expires)
expiresDate = entry.expiryTime
notes = entry.notes
assignExtraFields(entry.customFields.mapTo(ArrayList()) {
Field(it.key, it.value)
}) {
setOnEditCustomField?.invoke(it)
icon = mEntryInfo.icon
title = mEntryInfo.title
username = mEntryInfo.username
url = mEntryInfo.url
password = mEntryInfo.password
expires = mEntryInfo.expires
expiryTime = mEntryInfo.expiryTime
notes = mEntryInfo.notes
assignExtraFields(mEntryInfo.customFields) { fields ->
setOnEditCustomField?.invoke(fields)
}
assignAttachments(entry.getAttachments(database.binaryPool).toSet(), StreamDirection.UPLOAD) { attachment ->
// Remove entry by clicking trash button
entry.removeAttachment(attachment)
assignAttachments(mEntryInfo.attachments, StreamDirection.UPLOAD) { attachment ->
setOnRemoveAttachment?.invoke(attachment)
}
} catch (e: Exception) {}
}
private fun populateEntryWithViews(database: Database, newEntry: Entry) {
database.startManageEntry(newEntry)
newEntry.apply {
// Build info from view
this@EntryEditFragment.let { entryView ->
removeAllFields()
title = entryView.title
username = entryView.username
url = entryView.url
password = entryView.password
expires = entryView.expires
if (entryView.expires) {
expiryTime = entryView.expiresDate
}
notes = entryView.notes
entryView.getExtraFields().forEach { customField ->
putExtraField(customField.name, customField.protectedValue)
}
database.binaryPool.let { binaryPool ->
entryView.getAttachments().forEach {
putAttachment(it, binaryPool)
}
}
}
}
database.stopManageEntry(newEntry)
private fun populateEntryWithViews() {
try {
// Icon already populate
mEntryInfo.title = title
mEntryInfo.username = username
mEntryInfo.url = url
mEntryInfo.password = password
mEntryInfo.expires = expires
mEntryInfo.expiryTime = expiryTime
mEntryInfo.notes = notes
mEntryInfo.customFields = getExtraFields()
mEntryInfo.attachments = getAttachments()
} catch (e: Exception) {}
}
fun applyFontVisibilityToFields(fontInVisibility: Boolean) {
@@ -253,13 +214,15 @@ class EntryEditFragment: StylishFragment() {
entryTitleView.applyFontVisibility()
}
var drawFactory: IconDrawableFactory? = null
var icon: IconImage
get() {
return mEntry?.icon ?: IconImageStandard()
return mEntryInfo.icon
}
set(value) {
mEntry?.icon = value
mDatabase?.drawFactory?.let { drawFactory ->
mEntryInfo.icon = value
drawFactory?.let { drawFactory ->
entryIconView.assignDatabaseIcon(drawFactory, value, iconColor)
}
}
@@ -316,7 +279,7 @@ class EntryEditFragment: StylishFragment() {
assignExpiresDateText()
}
var expiresDate: DateInstant
var expiryTime: DateInstant
get() {
return expiresInstant
}
@@ -471,7 +434,7 @@ class EntryEditFragment: StylishFragment() {
return attachmentsAdapter.itemsList.map { it.attachment }
}
fun assignAttachments(attachments: Set<Attachment>,
fun assignAttachments(attachments: List<Attachment>,
streamDirection: StreamDirection,
onDeleteItem: (attachment: Attachment)->Unit) {
attachmentsContainerView.visibility = if (attachments.isEmpty()) View.GONE else View.VISIBLE
@@ -513,18 +476,14 @@ class EntryEditFragment: StylishFragment() {
}
override fun onSaveInstanceState(outState: Bundle) {
mEntry?.let { entry ->
mDatabase?.let { database ->
populateEntryWithViews(database, entry)
}
outState.putParcelable(KEY_TEMP_ENTRY, entry)
}
populateEntryWithViews()
outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo)
super.onSaveInstanceState(outState)
}
companion object {
const val KEY_TEMP_ENTRY = "KEY_TEMP_ENTRY"
const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO"
}
}

View File

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

View File

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

View File

@@ -309,6 +309,12 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
binaries.remove(attachment.name)
}
fun removeAttachments() {
binaries.keys.forEach {
binaries.remove(it)
}
}
private fun getAttachmentsSize(binaryPool: BinaryPool): Long {
var size = 0L
for ((label, poolId) in binaries) {

View File

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

View File

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