Files
KeePassDX/app/src/main/java/com/kunzisoft/keepass/view/TextFieldView.kt

329 lines
10 KiB
Kotlin

/*
* Copyright 2021 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.text.InputFilter
import android.text.util.Linkify
import android.util.AttributeSet
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.View
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatImageButton
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.ContextCompat
import androidx.core.text.util.LinkifyCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.AppOriginEntryField.APPLICATION_ID_FIELD_NAME
import com.kunzisoft.keepass.utils.AppUtil.openExternalApp
open class TextFieldView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: ProtectedTextFieldView(context, attrs, defStyle) {
protected var labelViewId = ViewCompat.generateViewId()
private var valueViewId = ViewCompat.generateViewId()
private var showButtonId = ViewCompat.generateViewId()
private var copyButtonId = ViewCompat.generateViewId()
protected val labelView = AppCompatTextView(context).apply {
setTextAppearance(context,
R.style.KeepassDXStyle_TextAppearance_LabelTextStyle)
layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
).also {
it.leftMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
4f,
resources.displayMetrics
).toInt()
it.marginStart = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
4f,
resources.displayMetrics
).toInt()
}
}
protected val valueView = AppCompatTextView(context).apply {
setTextAppearance(context,
R.style.KeepassDXStyle_TextAppearance_TextNode)
layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT).also {
it.topMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
4f,
resources.displayMetrics
).toInt()
it.leftMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
8f,
resources.displayMetrics
).toInt()
it.marginStart = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
8f,
resources.displayMetrics
).toInt()
}
setTextIsSelectable(true)
}
private var showButton = AppCompatImageButton(
ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0).apply {
layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_visibility_state))
contentDescription = context.getString(R.string.menu_showpass)
}
private var copyButton = AppCompatImageButton(
ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0).apply {
layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_content_copy_white_24dp))
contentDescription = context.getString(R.string.menu_copy)
}
init {
buildViews()
addView(copyButton)
addView(showButton)
addView(labelView)
addView(valueView)
}
private fun buildViews() {
copyButton.apply {
id = copyButtonId
layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(ALIGN_PARENT_RIGHT)
it.addRule(ALIGN_PARENT_END)
}
}
showButton.apply {
id = showButtonId
layoutParams = (layoutParams as LayoutParams?)?.also {
if (copyButton.isVisible) {
it.addRule(LEFT_OF, copyButtonId)
it.addRule(START_OF, copyButtonId)
} else {
it.addRule(ALIGN_PARENT_RIGHT)
it.addRule(ALIGN_PARENT_END)
}
}
}
labelView.apply {
id = labelViewId
layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(LEFT_OF, showButtonId)
it.addRule(START_OF, showButtonId)
}
}
valueView.apply {
id = valueViewId
layoutParams = (layoutParams as LayoutParams?)?.also {
it.addRule(LEFT_OF, showButtonId)
it.addRule(START_OF, showButtonId)
it.addRule(BELOW, labelViewId)
}
}
}
override fun applyFontVisibility(fontInVisibility: Boolean) {
if (fontInVisibility)
valueView.applyFontVisibility()
}
override var label: String
get() {
return labelView.text.toString()
}
set(value) {
labelView.text = value
}
open fun setLabel(@StringRes labelId: Int) {
labelView.setText(labelId)
}
override var value: String
get() {
return valueView.text.toString()
}
set(value) {
valueView.text = value
changeProtectedValueParameters()
}
open fun setValue(@StringRes valueId: Int) {
value = resources.getString(valueId)
changeProtectedValueParameters()
}
override var default: String = ""
fun setMaxChars(numberChars: Int) {
when {
numberChars <= 0 -> {
valueView.filters += InputFilter.LengthFilter(MAX_CHARS_LIMIT)
}
else -> {
val chars = if (numberChars > MAX_CHARS_LIMIT) MAX_CHARS_LIMIT else numberChars
valueView.filters += InputFilter.LengthFilter(chars)
}
}
}
override fun setProtection(
protection: Boolean,
isCurrentlyProtected: Boolean,
onUnprotectClickListener: OnClickListener?
) {
super.setProtection(protection, isCurrentlyProtected, onUnprotectClickListener)
showButton.isVisible = isProtected
if (isProtected) {
showButton.setOnClickListener {
onUnprotectClickListener?.onClick(this@TextFieldView)
}
}
}
override fun changeProtectedValueParameters() {
valueView.apply {
if (showButton.isVisible) {
applyHiddenStyle(isCurrentlyProtected())
} else {
linkify()
}
}
invalidate()
}
private fun linkify() {
when {
labelView.text.contains(APPLICATION_ID_FIELD_NAME) -> {
val packageName = valueView.text.toString()
// TODO #996 if (UriUtil.isExternalAppInstalled(context, packageName)) {
valueView.customLink {
context.openExternalApp(packageName)
}
//}
}
else -> {
LinkifyCompat.addLinks(valueView, Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES)
}
}
}
fun getCopyButtonView(): View? {
if (copyButton.isVisible) {
return copyButton
}
return null
}
fun setCopyButtonState(buttonState: ButtonState) {
when (buttonState) {
ButtonState.ACTIVATE -> {
copyButton.apply {
visibility = VISIBLE
isActivated = false
}
valueView.apply {
isFocusable = true
setTextIsSelectable(true)
}
}
ButtonState.DEACTIVATE -> {
copyButton.apply {
visibility = VISIBLE
// Reverse because isActivated show custom color and allow click
isActivated = true
}
valueView.apply {
isFocusable = false
setTextIsSelectable(false)
}
}
ButtonState.GONE -> {
copyButton.apply {
visibility = GONE
setOnClickListener(null)
}
valueView.apply {
isFocusable = false
setTextIsSelectable(false)
}
}
}
invalidate()
}
fun setCopyButtonClickListener(onActionClickListener: ((label: String, value: String) -> Unit)?) {
val clickListener = if (onActionClickListener != null)
OnClickListener { onActionClickListener.invoke(label, value) }
else
null
setOnActionClickListener(clickListener, null)
}
override fun setOnActionClickListener(
onActionClickListener: OnClickListener?,
actionImageId: Int?
) {
copyButton.setOnClickListener(onActionClickListener)
copyButton.isVisible = onActionClickListener != null
invalidate()
}
override var isFieldVisible: Boolean
get() {
return isVisible
}
set(value) {
isVisible = value
}
override fun invalidate() {
super.invalidate()
buildViews()
}
enum class ButtonState {
ACTIVATE, DEACTIVATE, GONE
}
companion object {
const val MAX_CHARS_LIMIT = Integer.MAX_VALUE
}
}