Files
KeePassDX/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt
2021-04-19 18:26:40 +02:00

369 lines
14 KiB
Kotlin

/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
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.security.ProtectedString
import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
class EntryContentsView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: LinearLayout(context, attrs, defStyle) {
private var fontInVisibility: Boolean = false
private val userNameFieldView: EntryField
private val passwordFieldView: EntryField
private val otpFieldView: EntryField
private val urlFieldView: EntryField
private val notesFieldView: EntryField
private var otpRunnable: Runnable? = null
private val extraFieldsContainerView: View
private val extraFieldsListView: ViewGroup
private val creationDateView: TextView
private val modificationDateView: TextView
private val expiresImageView: ImageView
private val expiresDateView: TextView
private val attachmentsContainerView: View
private val attachmentsListView: RecyclerView
private val attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
private val historyContainerView: View
private val historyListView: RecyclerView
private val historyAdapter = EntryHistoryAdapter(context)
private val uuidContainerView: View
private val uuidView: TextView
private val uuidReferenceView: TextView
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
inflater?.inflate(R.layout.view_entry_contents, this)
userNameFieldView = findViewById(R.id.entry_user_name_field)
userNameFieldView.setLabel(R.string.entry_user_name)
passwordFieldView = findViewById(R.id.entry_password_field)
passwordFieldView.setLabel(R.string.entry_password)
otpFieldView = findViewById(R.id.entry_otp_field)
otpFieldView.setLabel(R.string.entry_otp)
urlFieldView = findViewById(R.id.entry_url_field)
urlFieldView.setLabel(R.string.entry_url)
urlFieldView.setLinkAll()
notesFieldView = findViewById(R.id.entry_notes_field)
notesFieldView.setLabel(R.string.entry_notes)
notesFieldView.setAutoLink()
extraFieldsContainerView = findViewById(R.id.extra_fields_container)
extraFieldsListView = findViewById(R.id.extra_fields_list)
attachmentsContainerView = findViewById(R.id.entry_attachments_container)
attachmentsListView = findViewById(R.id.entry_attachments_list)
attachmentsListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
creationDateView = findViewById(R.id.entry_created)
modificationDateView = findViewById(R.id.entry_modified)
expiresImageView = findViewById(R.id.entry_expires_image)
expiresDateView = findViewById(R.id.entry_expires_date)
historyContainerView = findViewById(R.id.entry_history_container)
historyListView = findViewById(R.id.entry_history_list)
historyListView?.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
adapter = historyAdapter
}
uuidContainerView = findViewById(R.id.entry_UUID_container)
uuidContainerView?.apply {
visibility = if (PreferencesUtil.showUUID(context)) View.VISIBLE else View.GONE
}
uuidView = findViewById(R.id.entry_UUID)
uuidReferenceView = findViewById(R.id.entry_UUID_reference)
}
fun applyFontVisibilityToFields(fontInVisibility: Boolean) {
this.fontInVisibility = fontInVisibility
}
fun firstEntryFieldCopyView(): View? {
return when {
userNameFieldView.isVisible && userNameFieldView.copyButtonView.isVisible -> userNameFieldView.copyButtonView
passwordFieldView.isVisible && passwordFieldView.copyButtonView.isVisible -> passwordFieldView.copyButtonView
otpFieldView.isVisible && otpFieldView.copyButtonView.isVisible -> otpFieldView.copyButtonView
urlFieldView.isVisible && urlFieldView.copyButtonView.isVisible -> urlFieldView.copyButtonView
notesFieldView.isVisible && notesFieldView.copyButtonView.isVisible -> notesFieldView.copyButtonView
else -> null
}
}
fun assignUserName(userName: String?,
onClickListener: OnClickListener?) {
userNameFieldView.apply {
if (userName != null && userName.isNotEmpty()) {
visibility = View.VISIBLE
setValue(userName)
applyFontVisibility(fontInVisibility)
} else {
visibility = View.GONE
}
assignCopyButtonClickListener(onClickListener)
}
}
fun assignPassword(password: String?,
allowCopyPassword: Boolean,
onClickListener: OnClickListener?) {
passwordFieldView.apply {
if (password != null && password.isNotEmpty()) {
visibility = View.VISIBLE
setValue(password, true)
applyFontVisibility(fontInVisibility)
activateCopyButton(allowCopyPassword)
}else {
visibility = View.GONE
}
assignCopyButtonClickListener(onClickListener)
}
}
fun assignOtp(otpElement: OtpElement?,
otpProgressView: ProgressBar?,
onClickListener: OnClickListener) {
otpFieldView.removeCallbacks(otpRunnable)
if (otpElement != null) {
otpFieldView.visibility = View.VISIBLE
if (otpElement.token.isEmpty()) {
otpFieldView.setValue(R.string.error_invalid_OTP)
otpFieldView.activateCopyButton(false)
otpFieldView.assignCopyButtonClickListener(null)
} else {
otpFieldView.setLabel(otpElement.type.name)
otpFieldView.setValue(otpElement.token)
otpFieldView.assignCopyButtonClickListener(onClickListener)
when (otpElement.type) {
// Only add token if HOTP
OtpType.HOTP -> {
otpProgressView?.visibility = View.GONE
}
// Refresh view if TOTP
OtpType.TOTP -> {
otpProgressView?.apply {
max = otpElement.period
progress = otpElement.secondsRemaining
visibility = View.VISIBLE
}
otpRunnable = Runnable {
if (otpElement.shouldRefreshToken()) {
otpFieldView.setValue(otpElement.token)
}
otpProgressView?.progress = otpElement.secondsRemaining
otpFieldView.postDelayed(otpRunnable, 1000)
}
otpFieldView.post(otpRunnable)
}
}
}
} else {
otpFieldView.visibility = View.GONE
otpProgressView?.visibility = View.GONE
}
}
fun assignURL(url: String?) {
urlFieldView.apply {
if (url != null && url.isNotEmpty()) {
visibility = View.VISIBLE
setValue(url)
} else {
visibility = View.GONE
}
}
}
fun assignNotes(notes: String?) {
notesFieldView.apply {
if (notes != null && notes.isNotEmpty()) {
visibility = View.VISIBLE
setValue(notes)
applyFontVisibility(fontInVisibility)
} else {
visibility = View.GONE
}
}
}
fun assignCreationDate(date: DateInstant) {
creationDateView.text = date.getDateTimeString(resources)
}
fun assignModificationDate(date: DateInstant) {
modificationDateView.text = date.getDateTimeString(resources)
}
fun setExpires(isExpires: Boolean, expiryTime: DateInstant) {
expiresImageView.visibility = if (isExpires) View.VISIBLE else View.GONE
expiresDateView.text = if (isExpires) {
expiryTime.getDateTimeString(resources)
} else {
resources.getString(R.string.never)
}
}
fun assignUUID(uuid: UUID) {
uuidView.text = uuid.toString()
uuidReferenceView.text = UuidUtil.toHexString(uuid)
}
fun setHiddenProtectedValue(hiddenProtectedValue: Boolean) {
passwordFieldView.hiddenProtectedValue = hiddenProtectedValue
// Hidden style for custom fields
extraFieldsListView.let {
for (i in 0 until it.childCount) {
val childCustomView = it.getChildAt(i)
if (childCustomView is EntryField)
childCustomView.hiddenProtectedValue = hiddenProtectedValue
}
}
}
/* -------------
* Extra Fields
* -------------
*/
private fun showOrHideExtraFieldsContainer(hide: Boolean) {
extraFieldsContainerView.visibility = if (hide) View.GONE else View.VISIBLE
}
fun addExtraField(title: String,
value: ProtectedString,
allowCopy: Boolean,
onCopyButtonClickListener: OnClickListener?) {
val entryCustomField: EntryField? = EntryField(context)
entryCustomField?.apply {
setLabel(title)
setValue(value.toString(), value.isProtected)
setAutoLink()
activateCopyButton(allowCopy)
assignCopyButtonClickListener(onCopyButtonClickListener)
applyFontVisibility(fontInVisibility)
}
entryCustomField?.let {
extraFieldsListView.addView(it)
}
showOrHideExtraFieldsContainer(false)
}
fun clearExtraFields() {
extraFieldsListView.removeAllViews()
showOrHideExtraFieldsContainer(true)
}
/* -------------
* Attachments
* -------------
*/
fun setAttachmentCipherKey(database: Database?) {
attachmentsAdapter.database = database
}
private fun showAttachments(show: Boolean) {
attachmentsContainerView.visibility = if (show) View.VISIBLE else View.GONE
}
fun assignAttachments(attachments: Set<Attachment>,
streamDirection: StreamDirection,
onAttachmentClicked: (attachment: Attachment)->Unit) {
showAttachments(attachments.isNotEmpty())
attachmentsAdapter.assignItems(attachments.map { EntryAttachmentState(it, streamDirection) })
attachmentsAdapter.onItemClickListener = { item ->
onAttachmentClicked.invoke(item.attachment)
}
}
fun putAttachment(attachmentToDownload: EntryAttachmentState) {
attachmentsAdapter.putItem(attachmentToDownload)
}
/* -------------
* History
* -------------
*/
fun assignHistory(history: ArrayList<Entry>, action: (historyItem: Entry, position: Int)->Unit) {
historyAdapter.clear()
historyAdapter.entryHistoryList.addAll(history)
historyAdapter.onItemClickListener = { item, position ->
action.invoke(item, position)
}
historyContainerView.visibility = if (historyAdapter.entryHistoryList.isEmpty())
View.GONE
else
View.VISIBLE
historyAdapter.notifyDataSetChanged()
}
override fun generateDefaultLayoutParams(): LayoutParams {
return LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
}
}