Possibility to remove attachment

This commit is contained in:
J-Jamet
2020-08-15 12:01:08 +02:00
parent c5ef11febc
commit 0e5b7fbfa2
14 changed files with 176 additions and 56 deletions

View File

@@ -350,7 +350,6 @@ class EntryActivity : LockingActivity() {
} }
} }
} }
entryContentsView?.refreshAttachments()
// Assign dates // Assign dates
entryContentsView?.assignCreationDate(entry.creationTime) entryContentsView?.assignCreationDate(entry.creationTime)

View File

@@ -300,6 +300,10 @@ class EntryEditActivity : LockingActivity(),
for ((key, value) in newEntry.customFields) { for ((key, value) in newEntry.customFields) {
putCustomField(Field(key, value)) putCustomField(Field(key, value))
} }
assignAttachments(newEntry.getAttachments())
onAttachmentDeleted { attachment, _ ->
newEntry.removeAttachment(attachment)
}
} }
} }

View File

@@ -33,11 +33,13 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment import com.kunzisoft.keepass.model.EntryAttachment
class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<EntryAttachmentsAdapter.EntryBinariesViewHolder>() { class EntryAttachmentsAdapter(val context: Context, private val editable: Boolean)
: RecyclerView.Adapter<EntryAttachmentsAdapter.EntryBinariesViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryAttachmentsList: MutableList<EntryAttachment> = ArrayList() var entryAttachmentsList: MutableList<EntryAttachment> = ArrayList()
var onItemClickListener: ((item: EntryAttachment, position: Int)->Unit)? = null var onItemClickListener: ((item: EntryAttachment, position: Int)->Unit)? = null
var onDeleteListener: ((item: EntryAttachment, position: Int)->Unit)? = null
private val mDatabase = Database.getInstance() private val mDatabase = Database.getInstance()
@@ -61,16 +63,28 @@ class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<Entry
visibility = View.GONE visibility = View.GONE
} }
} }
holder.binaryFileProgress.apply { if (editable) {
visibility = when (entryAttachment.downloadState) { holder.binaryFileProgressContainer.visibility = View.GONE
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE holder.binaryFileDeleteButton.apply {
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE visibility = View.VISIBLE
setOnClickListener {
onDeleteListener?.invoke(entryAttachment, position)
deleteAttachment(entryAttachment)
}
}
} else {
holder.binaryFileProgressContainer.visibility = View.VISIBLE
holder.binaryFileDeleteButton.visibility = View.GONE
holder.binaryFileProgress.apply {
visibility = when (entryAttachment.downloadState) {
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
}
progress = entryAttachment.downloadProgression
}
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryAttachment, position)
} }
progress = entryAttachment.downloadProgression
}
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryAttachment, position)
} }
} }
@@ -78,6 +92,23 @@ class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<Entry
return entryAttachmentsList.size return entryAttachmentsList.size
} }
fun assignAttachments(attachments: List<EntryAttachment>) {
entryAttachmentsList.apply {
clear()
addAll(attachments)
}
notifyDataSetChanged()
}
fun deleteAttachment(attachment: EntryAttachment) {
val position = entryAttachmentsList.indexOf(attachment)
if (position >= 0) {
entryAttachmentsList.removeAt(position)
notifyItemRemoved(position)
}
// TODO Animation
}
fun updateProgress(entryAttachment: EntryAttachment) { fun updateProgress(entryAttachment: EntryAttachment) {
val indexEntryAttachment = entryAttachmentsList.indexOfLast { current -> current.name == entryAttachment.name } val indexEntryAttachment = entryAttachmentsList.indexOfLast { current -> current.name == entryAttachment.name }
if (indexEntryAttachment != -1) { if (indexEntryAttachment != -1) {
@@ -95,6 +126,8 @@ class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<Entry
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title) var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size) var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression) var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
var binaryFileProgressContainer: View = itemView.findViewById(R.id.item_attachment_progress_container)
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress) var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress)
var binaryFileDeleteButton: View = itemView.findViewById(R.id.item_attachment_delete_button)
} }
} }

View File

@@ -329,22 +329,31 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun getAttachments(): ArrayList<EntryAttachment> { fun getAttachments(): ArrayList<EntryAttachment> {
val attachments = ArrayList<EntryAttachment>() val attachments = ArrayList<EntryAttachment>()
val binaryDescriptionKDB = entryKDB?.binaryDescription ?: "" entryKDB?.binaryData?.let { binaryKDB ->
val binaryKDB = entryKDB?.binaryData attachments.add(EntryAttachment(entryKDB?.binaryDescription ?: "", binaryKDB))
if (binaryKDB != null) {
attachments.add(EntryAttachment(binaryDescriptionKDB, binaryKDB))
} }
val actionEach = object : (Map.Entry<String, BinaryAttachment>)->Unit { entryKDBX?.binaries?.let { binariesKDBX ->
override fun invoke(mapEntry: Map.Entry<String, BinaryAttachment>) { for ((key, value) in binariesKDBX) {
attachments.add(EntryAttachment(mapEntry.key, mapEntry.value)) attachments.add(EntryAttachment(key, value))
} }
} }
entryKDBX?.binaries?.forEach(actionEach)
return attachments return attachments
} }
fun removeAttachment(attachment: EntryAttachment) {
entryKDB?.apply {
if (binaryDescription == attachment.name
&& binaryData == attachment.binaryAttachment) {
binaryDescription = ""
binaryData = null
}
}
entryKDBX?.removeProtectedBinary(attachment.name)
}
fun getHistory(): ArrayList<Entry> { fun getHistory(): ArrayList<Entry> {
val history = ArrayList<Entry>() val history = ArrayList<Entry>()
val entryKDBXHistory = entryKDBX?.history ?: ArrayList() val entryKDBXHistory = entryKDBX?.history ?: ArrayList()

View File

@@ -288,6 +288,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
binaries[key] = value binaries[key] = value
} }
fun removeProtectedBinary(name: String) {
binaries.remove(name)
}
fun sizeOfHistory(): Int { fun sizeOfHistory(): Int {
return history.size return history.size
} }

View File

@@ -47,6 +47,22 @@ data class EntryAttachment(var name: String,
return 0 return 0
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is EntryAttachment) return false
if (name != other.name) return false
if (binaryAttachment != other.binaryAttachment) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + binaryAttachment.hashCode()
return result
}
companion object CREATOR : Parcelable.Creator<EntryAttachment> { companion object CREATOR : Parcelable.Creator<EntryAttachment> {
override fun createFromParcel(parcel: Parcel): EntryAttachment { override fun createFromParcel(parcel: Parcel): EntryAttachment {
return EntryAttachment(parcel) return EntryAttachment(parcel)

View File

@@ -86,7 +86,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
private val attachmentsContainerView: View private val attachmentsContainerView: View
private val attachmentsListView: RecyclerView private val attachmentsListView: RecyclerView
private val attachmentsAdapter = EntryAttachmentsAdapter(context) private val attachmentsAdapter = EntryAttachmentsAdapter(context, false)
private val historyContainerView: View private val historyContainerView: View
private val historyListView: RecyclerView private val historyListView: RecyclerView
@@ -366,13 +366,8 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE
} }
fun refreshAttachments() {
attachmentsAdapter.notifyDataSetChanged()
}
fun assignAttachments(attachments: ArrayList<EntryAttachment>) { fun assignAttachments(attachments: ArrayList<EntryAttachment>) {
attachmentsAdapter.clear() attachmentsAdapter.assignAttachments(attachments)
attachmentsAdapter.entryAttachmentsList.addAll(attachments)
} }
fun updateAttachmentDownloadProgress(attachmentToDownload: EntryAttachment) { fun updateAttachmentDownloadProgress(attachmentToDownload: EntryAttachment) {

View File

@@ -44,7 +44,7 @@ class EntryCustomField @JvmOverloads constructor(context: Context,
init { init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater? val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.item_entry_new_field, this) inflater?.inflate(R.layout.item_entry_extra_field, this)
labelView = findViewById(R.id.title) labelView = findViewById(R.id.title)
valueView = findViewById(R.id.value) valueView = findViewById(R.id.value)

View File

@@ -22,18 +22,22 @@ package com.kunzisoft.keepass.view
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsAdapter
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.icons.IconDrawableFactory import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.icons.assignDefaultDatabaseIcon import com.kunzisoft.keepass.icons.assignDefaultDatabaseIcon
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.Field
import org.joda.time.Duration import org.joda.time.Duration
import org.joda.time.Instant import org.joda.time.Instant
@@ -57,6 +61,9 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
private val entryExpiresTextView: TextView private val entryExpiresTextView: TextView
private val entryNotesView: EditText private val entryNotesView: EditText
private val entryExtraFieldsContainer: ViewGroup private val entryExtraFieldsContainer: ViewGroup
private val attachmentsListView: RecyclerView
private val attachmentsAdapter = EntryAttachmentsAdapter(context, true)
private var iconColor: Int = 0 private var iconColor: Int = 0
private var expiresInstant: DateInstant = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate()) private var expiresInstant: DateInstant = DateInstant(Instant.now().plus(Duration.standardDays(30)).toDate())
@@ -86,6 +93,12 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
entryExpiresTextView = findViewById(R.id.entry_edit_expires_text) entryExpiresTextView = findViewById(R.id.entry_edit_expires_text)
entryNotesView = findViewById(R.id.entry_edit_notes) entryNotesView = findViewById(R.id.entry_edit_notes)
entryExtraFieldsContainer = findViewById(R.id.entry_edit_extra_fields_container) entryExtraFieldsContainer = findViewById(R.id.entry_edit_extra_fields_container)
attachmentsListView = findViewById(R.id.entry_attachments_list)
attachmentsListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
entryExpiresCheckBox.setOnCheckedChangeListener { _, _ -> entryExpiresCheckBox.setOnCheckedChangeListener { _, _ ->
assignExpiresDateText() assignExpiresDateText()
@@ -255,6 +268,16 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
return extraFieldView return extraFieldView
} }
fun assignAttachments(attachments: java.util.ArrayList<EntryAttachment>) {
attachmentsAdapter.assignAttachments(attachments)
}
fun onAttachmentDeleted(action: (attachment: EntryAttachment, position: Int)->Unit) {
attachmentsAdapter.onDeleteListener = { item, position ->
action.invoke(item, position)
}
}
/** /**
* Validate or not the entry form * Validate or not the entry form
* *

View File

@@ -66,7 +66,7 @@ class EntryEditExtraField @JvmOverloads constructor(context: Context,
init { init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater? val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.view_entry_edit_extra_field, this) inflater?.inflate(R.layout.item_entry_edit_extra_field, this)
valueLayoutView = findViewById(R.id.entry_extra_field_value_container) valueLayoutView = findViewById(R.id.entry_extra_field_value_container)
valueView = findViewById(R.id.entry_extra_field_value) valueView = findViewById(R.id.entry_extra_field_value)

View File

@@ -44,7 +44,7 @@
android:gravity="end" android:gravity="end"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/item_attachment_icon" > app:layout_constraintEnd_toStartOf="@+id/item_attachment_action_container" >
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/item_attachment_size" android:id="@+id/item_attachment_size"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -65,24 +65,48 @@
android:paddingEnd="8dp" android:paddingEnd="8dp"
tools:text="GZip" /> tools:text="GZip" />
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatImageView <FrameLayout
android:id="@+id/item_attachment_icon" android:id="@+id/item_attachment_action_container"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="36dp" android:layout_height="36dp">
android:padding="6dp" <ImageView
android:src="@drawable/ic_file_download_white_24dp" android:id="@+id/item_attachment_delete_button"
android:tint="?attr/colorAccent" /> android:layout_width="match_parent"
<ProgressBar android:layout_height="match_parent"
android:id="@+id/item_attachment_progress" android:padding="6dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" android:src="@drawable/ic_close_white_24dp"
style="@style/KeepassDXStyle.ProgressBar.Circle" app:tint="?attr/colorAccent"
android:layout_width="36dp" android:contentDescription="@string/content_description_remove_field">
android:layout_height="36dp" </ImageView>
android:max="100" <FrameLayout
android:progress="60" /> android:id="@+id/item_attachment_progress_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/item_attachment_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:src="@drawable/ic_file_download_white_24dp"
android:tint="?attr/colorAccent" />
<ProgressBar
android:id="@+id/item_attachment_progress"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/KeepassDXStyle.ProgressBar.Circle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:max="100"
android:progress="60" />
</FrameLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -33,11 +33,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_delete" app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_delete">
android:paddingLeft="@dimen/default_margin"
android:paddingStart="@dimen/default_margin"
android:paddingRight="0dp"
android:paddingEnd="0dp">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/entry_extra_field_value" android:id="@+id/entry_extra_field_value"
@@ -51,9 +47,10 @@
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/entry_extra_field_delete" android:id="@+id/entry_extra_field_delete"
android:layout_width="48dp" android:layout_width="36dp"
android:layout_height="48dp" android:layout_height="36dp"
android:padding="12dp" android:padding="6dp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_close_white_24dp" android:src="@drawable/ic_close_white_24dp"

View File

@@ -212,10 +212,26 @@
android:id="@+id/entry_edit_extra_fields_container" android:id="@+id/entry_edit_extra_fields_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/default_margin" android:layout_margin="@dimen/default_margin"
android:layout_marginBottom="@dimen/default_margin"
android:orientation="vertical"> android:orientation="vertical">
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/entry_edit_attachments_container_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/card_view_margin_bottom"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
app:layout_constraintTop_toBottomOf="@+id/entry_edit_extra_fields_container_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/entry_attachments_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/default_margin" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>