diff --git a/app/src/main/java/com/kunzisoft/keepass/view/PasswordTextEditFieldView.kt b/app/src/main/java/com/kunzisoft/keepass/view/PasswordTextEditFieldView.kt index faafa6709..0b6b26ae3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/PasswordTextEditFieldView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/PasswordTextEditFieldView.kt @@ -1,3 +1,22 @@ +/* + * Copyright 2024 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 . + * + */ package com.kunzisoft.keepass.view import android.content.Context diff --git a/app/src/main/java/com/kunzisoft/keepass/view/PasswordTextFieldView.kt b/app/src/main/java/com/kunzisoft/keepass/view/PasswordTextFieldView.kt new file mode 100644 index 000000000..c56454d10 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/view/PasswordTextFieldView.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2024 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 . + * + */ +package com.kunzisoft.keepass.view + +import android.content.Context +import android.graphics.Color +import android.text.SpannableString +import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE +import android.text.style.ImageSpan +import android.util.AttributeSet +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.password.PasswordEntropy +import com.kunzisoft.keepass.password.PasswordGenerator +import com.kunzisoft.keepass.settings.PreferencesUtil + + +class PasswordTextFieldView @JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0) + : TextFieldView(context, attrs, defStyle) { + + private var mPasswordEntropyCalculator: PasswordEntropy = PasswordEntropy { + valueView.text?.toString()?.let { firstPassword -> + getEntropyStrength(firstPassword) + } + } + + private var indicatorDrawable = ContextCompat.getDrawable( + context, + R.drawable.ic_shield_white_24dp + )?.apply { + val lineHeight = labelView.lineHeight + setBounds(0,0,lineHeight, lineHeight) + DrawableCompat.setTint(this, Color.TRANSPARENT) + } + + override var label: String + get() { + return labelView.text.toString().removeSuffix(ICON_STRING_SPACES) + } + set(value) { + indicatorDrawable?.let { drawable -> + val spannableString = SpannableString("$value$ICON_STRING_SPACES") + val startPosition = spannableString.split(ICON_STRING)[0].length + val endPosition = startPosition + ICON_STRING.length + spannableString + .setSpan( + ImageSpan(drawable), + startPosition, + endPosition, + SPAN_EXCLUSIVE_EXCLUSIVE + ) + labelView.text = spannableString + } ?: kotlin.run { + labelView.text = value + } + } + + override fun setLabel(@StringRes labelId: Int) { + label = resources.getString(labelId) + } + + override var value: String + get() { + return valueView.text.toString() + } + set(value) { + val spannableString = + if (PreferencesUtil.colorizePassword(context)) + PasswordGenerator.getColorizedPassword(value) + else + SpannableString(value) + valueView.text = spannableString + changeProtectedValueParameters() + } + + override fun setValue(@StringRes valueId: Int) { + value = resources.getString(valueId) + } + + private fun getEntropyStrength(passwordText: String) { + mPasswordEntropyCalculator.getEntropyStrength(passwordText) { entropyStrength -> + labelView.apply { + post { + val strengthColor = entropyStrength.strength.color + indicatorDrawable?.let { drawable -> + DrawableCompat.setTint(drawable, strengthColor) + } + invalidate() + } + } + } + } + + companion object { + private const val ICON_STRING = "[icon]" + private const val ICON_STRING_SPACES = " $ICON_STRING" + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt index 34ef53dc3..d21f850b0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt @@ -10,6 +10,7 @@ import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.template.TemplateAttribute import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.database.helper.getLocalizedName +import com.kunzisoft.keepass.database.helper.isStandardPasswordName import com.kunzisoft.keepass.model.OtpModel import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD @@ -48,7 +49,9 @@ class TemplateView @JvmOverloads constructor(context: Context, field: Field): TextFieldView? { // Add an action icon if needed return context?.let { - TextFieldView(it).apply { + (if (TemplateField.isStandardPasswordName(context, templateAttribute.label)) + PasswordTextFieldView(it) + else TextFieldView(it)).apply { applyFontVisibility(mFontInVisibility) setProtection(field.protectedValue.isProtected, mHideProtectedValue) label = templateAttribute.alias diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TextFieldView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TextFieldView.kt index fa22cb6f9..6077aa7e5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TextFieldView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TextFieldView.kt @@ -22,7 +22,6 @@ package com.kunzisoft.keepass.view import android.content.Context import android.os.Build import android.text.InputFilter -import android.text.SpannableString import android.text.util.Linkify import android.util.AttributeSet import android.util.TypedValue @@ -38,15 +37,11 @@ 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.database.element.template.TemplateField -import com.kunzisoft.keepass.database.helper.isStandardPasswordName import com.kunzisoft.keepass.model.EntryInfo.Companion.APPLICATION_ID_FIELD_NAME -import com.kunzisoft.keepass.password.PasswordGenerator -import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.utils.UriUtil.openExternalApp -class TextFieldView @JvmOverloads constructor(context: Context, +open class TextFieldView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : RelativeLayout(context, attrs, defStyle), GenericTextFieldView { @@ -56,7 +51,7 @@ class TextFieldView @JvmOverloads constructor(context: Context, private var showButtonId = ViewCompat.generateViewId() private var copyButtonId = ViewCompat.generateViewId() - private val labelView = AppCompatTextView(context).apply { + protected val labelView = AppCompatTextView(context).apply { setTextAppearance(context, R.style.KeepassDXStyle_TextAppearance_LabelTextStyle) layoutParams = LayoutParams( @@ -77,7 +72,7 @@ class TextFieldView @JvmOverloads constructor(context: Context, } } } - private val valueView = AppCompatTextView(context).apply { + protected val valueView = AppCompatTextView(context).apply { setTextAppearance(context, R.style.KeepassDXStyle_TextAppearance_TextNode) layoutParams = LayoutParams( @@ -131,46 +126,46 @@ class TextFieldView @JvmOverloads constructor(context: Context, private fun buildViews() { copyButton.apply { id = copyButtonId - layoutParams = (layoutParams as LayoutParams?).also { - it?.addRule(ALIGN_PARENT_RIGHT) + layoutParams = (layoutParams as LayoutParams?)?.also { + it.addRule(ALIGN_PARENT_RIGHT) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - it?.addRule(ALIGN_PARENT_END) + it.addRule(ALIGN_PARENT_END) } } } showButton.apply { id = showButtonId - layoutParams = (layoutParams as LayoutParams?).also { + layoutParams = (layoutParams as LayoutParams?)?.also { if (copyButton.isVisible) { - it?.addRule(LEFT_OF, copyButtonId) + it.addRule(LEFT_OF, copyButtonId) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - it?.addRule(START_OF, copyButtonId) + it.addRule(START_OF, copyButtonId) } } else { - it?.addRule(ALIGN_PARENT_RIGHT) + it.addRule(ALIGN_PARENT_RIGHT) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - it?.addRule(ALIGN_PARENT_END) + it.addRule(ALIGN_PARENT_END) } } } } labelView.apply { id = labelViewId - layoutParams = (layoutParams as LayoutParams?).also { - it?.addRule(LEFT_OF, showButtonId) + layoutParams = (layoutParams as LayoutParams?)?.also { + it.addRule(LEFT_OF, showButtonId) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - it?.addRule(START_OF, showButtonId) + it.addRule(START_OF, showButtonId) } } } valueView.apply { id = valueViewId - layoutParams = (layoutParams as LayoutParams?).also { - it?.addRule(LEFT_OF, showButtonId) + layoutParams = (layoutParams as LayoutParams?)?.also { + it.addRule(LEFT_OF, showButtonId) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - it?.addRule(START_OF, showButtonId) + it.addRule(START_OF, showButtonId) } - it?.addRule(BELOW, labelViewId) + it.addRule(BELOW, labelViewId) } } } @@ -188,7 +183,7 @@ class TextFieldView @JvmOverloads constructor(context: Context, labelView.text = value } - fun setLabel(@StringRes labelId: Int) { + open fun setLabel(@StringRes labelId: Int) { labelView.setText(labelId) } @@ -197,17 +192,11 @@ class TextFieldView @JvmOverloads constructor(context: Context, return valueView.text.toString() } set(value) { - val spannableString = - if (PreferencesUtil.colorizePassword(context) - && TemplateField.isStandardPasswordName(context, label)) - PasswordGenerator.getColorizedPassword(value) - else - SpannableString(value) - valueView.text = spannableString + valueView.text = value changeProtectedValueParameters() } - fun setValue(@StringRes valueId: Int) { + open fun setValue(@StringRes valueId: Int) { value = resources.getString(valueId) changeProtectedValueParameters() } @@ -237,7 +226,7 @@ class TextFieldView @JvmOverloads constructor(context: Context, invalidate() } - private fun changeProtectedValueParameters() { + protected fun changeProtectedValueParameters() { valueView.apply { if (showButton.isVisible) { applyHiddenStyle(showButton.isSelected) diff --git a/app/src/main/res/drawable/ic_shield_white_24dp.xml b/app/src/main/res/drawable/ic_shield_white_24dp.xml new file mode 100644 index 000000000..97970286a --- /dev/null +++ b/app/src/main/res/drawable/ic_shield_white_24dp.xml @@ -0,0 +1,9 @@ + + +