mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Possibility to remove attachment
This commit is contained in:
@@ -350,7 +350,6 @@ class EntryActivity : LockingActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
entryContentsView?.refreshAttachments()
|
||||
|
||||
// Assign dates
|
||||
entryContentsView?.assignCreationDate(entry.creationTime)
|
||||
|
||||
@@ -300,6 +300,10 @@ class EntryEditActivity : LockingActivity(),
|
||||
for ((key, value) in newEntry.customFields) {
|
||||
putCustomField(Field(key, value))
|
||||
}
|
||||
assignAttachments(newEntry.getAttachments())
|
||||
onAttachmentDeleted { attachment, _ ->
|
||||
newEntry.removeAttachment(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,13 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
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)
|
||||
var entryAttachmentsList: MutableList<EntryAttachment> = ArrayList()
|
||||
var onItemClickListener: ((item: EntryAttachment, position: Int)->Unit)? = null
|
||||
var onDeleteListener: ((item: EntryAttachment, position: Int)->Unit)? = null
|
||||
|
||||
private val mDatabase = Database.getInstance()
|
||||
|
||||
@@ -61,6 +63,18 @@ class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<Entry
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
if (editable) {
|
||||
holder.binaryFileProgressContainer.visibility = View.GONE
|
||||
holder.binaryFileDeleteButton.apply {
|
||||
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
|
||||
@@ -68,16 +82,33 @@ class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<Entry
|
||||
}
|
||||
progress = entryAttachment.downloadProgression
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
onItemClickListener?.invoke(entryAttachment, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
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) {
|
||||
val indexEntryAttachment = entryAttachmentsList.indexOfLast { current -> current.name == entryAttachment.name }
|
||||
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 binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
|
||||
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 binaryFileDeleteButton: View = itemView.findViewById(R.id.item_attachment_delete_button)
|
||||
}
|
||||
}
|
||||
@@ -329,22 +329,31 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
fun getAttachments(): ArrayList<EntryAttachment> {
|
||||
val attachments = ArrayList<EntryAttachment>()
|
||||
|
||||
val binaryDescriptionKDB = entryKDB?.binaryDescription ?: ""
|
||||
val binaryKDB = entryKDB?.binaryData
|
||||
if (binaryKDB != null) {
|
||||
attachments.add(EntryAttachment(binaryDescriptionKDB, binaryKDB))
|
||||
entryKDB?.binaryData?.let { binaryKDB ->
|
||||
attachments.add(EntryAttachment(entryKDB?.binaryDescription ?: "", binaryKDB))
|
||||
}
|
||||
|
||||
val actionEach = object : (Map.Entry<String, BinaryAttachment>)->Unit {
|
||||
override fun invoke(mapEntry: Map.Entry<String, BinaryAttachment>) {
|
||||
attachments.add(EntryAttachment(mapEntry.key, mapEntry.value))
|
||||
entryKDBX?.binaries?.let { binariesKDBX ->
|
||||
for ((key, value) in binariesKDBX) {
|
||||
attachments.add(EntryAttachment(key, value))
|
||||
}
|
||||
}
|
||||
entryKDBX?.binaries?.forEach(actionEach)
|
||||
|
||||
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> {
|
||||
val history = ArrayList<Entry>()
|
||||
val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
|
||||
|
||||
@@ -288,6 +288,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
binaries[key] = value
|
||||
}
|
||||
|
||||
fun removeProtectedBinary(name: String) {
|
||||
binaries.remove(name)
|
||||
}
|
||||
|
||||
fun sizeOfHistory(): Int {
|
||||
return history.size
|
||||
}
|
||||
|
||||
@@ -47,6 +47,22 @@ data class EntryAttachment(var name: String,
|
||||
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> {
|
||||
override fun createFromParcel(parcel: Parcel): EntryAttachment {
|
||||
return EntryAttachment(parcel)
|
||||
|
||||
@@ -86,7 +86,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
|
||||
private val attachmentsContainerView: View
|
||||
private val attachmentsListView: RecyclerView
|
||||
private val attachmentsAdapter = EntryAttachmentsAdapter(context)
|
||||
private val attachmentsAdapter = EntryAttachmentsAdapter(context, false)
|
||||
|
||||
private val historyContainerView: View
|
||||
private val historyListView: RecyclerView
|
||||
@@ -366,13 +366,8 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
fun refreshAttachments() {
|
||||
attachmentsAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun assignAttachments(attachments: ArrayList<EntryAttachment>) {
|
||||
attachmentsAdapter.clear()
|
||||
attachmentsAdapter.entryAttachmentsList.addAll(attachments)
|
||||
attachmentsAdapter.assignAttachments(attachments)
|
||||
}
|
||||
|
||||
fun updateAttachmentDownloadProgress(attachmentToDownload: EntryAttachment) {
|
||||
|
||||
@@ -44,7 +44,7 @@ class EntryCustomField @JvmOverloads constructor(context: Context,
|
||||
init {
|
||||
|
||||
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)
|
||||
valueView = findViewById(R.id.value)
|
||||
|
||||
@@ -22,18 +22,22 @@ package com.kunzisoft.keepass.view
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsAdapter
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||
import com.kunzisoft.keepass.icons.assignDefaultDatabaseIcon
|
||||
import com.kunzisoft.keepass.model.EntryAttachment
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
import org.joda.time.Duration
|
||||
import org.joda.time.Instant
|
||||
@@ -57,6 +61,9 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
||||
private val entryExpiresTextView: TextView
|
||||
private val entryNotesView: EditText
|
||||
private val entryExtraFieldsContainer: ViewGroup
|
||||
private val attachmentsListView: RecyclerView
|
||||
|
||||
private val attachmentsAdapter = EntryAttachmentsAdapter(context, true)
|
||||
|
||||
private var iconColor: Int = 0
|
||||
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)
|
||||
entryNotesView = findViewById(R.id.entry_edit_notes)
|
||||
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 { _, _ ->
|
||||
assignExpiresDateText()
|
||||
@@ -255,6 +268,16 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
|
||||
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
|
||||
*
|
||||
|
||||
@@ -66,7 +66,7 @@ class EntryEditExtraField @JvmOverloads constructor(context: Context,
|
||||
|
||||
init {
|
||||
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)
|
||||
valueView = findViewById(R.id.entry_extra_field_value)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
android:gravity="end"
|
||||
app:layout_constraintTop_toTopOf="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
|
||||
android:id="@+id/item_attachment_size"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -65,13 +65,35 @@
|
||||
android:paddingEnd="8dp"
|
||||
tools:text="GZip" />
|
||||
</LinearLayout>
|
||||
<FrameLayout
|
||||
android:id="@+id/item_attachment_action_container"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp">
|
||||
<ImageView
|
||||
android:id="@+id/item_attachment_delete_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="6dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:src="@drawable/ic_close_white_24dp"
|
||||
app:tint="?attr/colorAccent"
|
||||
android:contentDescription="@string/content_description_remove_field">
|
||||
</ImageView>
|
||||
<FrameLayout
|
||||
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="36dp"
|
||||
android:layout_height="36dp"
|
||||
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" />
|
||||
@@ -81,8 +103,10 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
style="@style/KeepassDXStyle.ProgressBar.Circle"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:max="100"
|
||||
android:progress="60" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -33,11 +33,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
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">
|
||||
app:layout_constraintEnd_toStartOf="@+id/entry_extra_field_delete">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/entry_extra_field_value"
|
||||
@@ -51,9 +47,10 @@
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/entry_extra_field_delete"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:padding="12dp"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:padding="6dp"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:src="@drawable/ic_close_white_24dp"
|
||||
@@ -212,10 +212,26 @@
|
||||
android:id="@+id/entry_edit_extra_fields_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/default_margin"
|
||||
android:layout_marginBottom="@dimen/default_margin"
|
||||
android:layout_margin="@dimen/default_margin"
|
||||
android:orientation="vertical">
|
||||
</LinearLayout>
|
||||
</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>
|
||||
Reference in New Issue
Block a user