mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
feat: Add shield icon as password strength indicator #1355
This commit is contained in:
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
9
app/src/main/res/drawable/ic_shield_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_shield_white_24dp.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12V5l-9,-4z"/>
|
||||
</vector>
|
||||
Reference in New Issue
Block a user