Files
KeePassDX/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt
2025-09-01 19:12:15 +02:00

277 lines
11 KiB
Kotlin

package com.kunzisoft.keepass.view
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.annotation.IdRes
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.view.isVisible
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.element.template.TemplateAttribute
import com.kunzisoft.keepass.database.element.template.TemplateAttributeAction
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.DataDate
import com.kunzisoft.keepass.model.DataTime
import com.kunzisoft.keepass.model.AppOriginEntryField
import com.kunzisoft.keepass.model.PasskeyEntryFields
import com.kunzisoft.keepass.otp.OtpEntryFields
class TemplateEditView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: TemplateAbstractView<TextEditFieldView, TextSelectFieldView, DateTimeEditFieldView>
(context, attrs, defStyle) {
// Current date time selection
@IdRes
private var mTempDateTimeViewId: Int? = null
private var mOnCustomEditionActionClickListener: ((Field) -> Unit)? = null
fun setOnCustomEditionActionClickListener(listener: ((Field) -> Unit)?) {
this.mOnCustomEditionActionClickListener = listener
}
private var mOnPasswordGenerationActionClickListener: ((Field) -> Unit)? = null
fun setOnPasswordGenerationActionClickListener(listener: ((Field) -> Unit)?) {
this.mOnPasswordGenerationActionClickListener = listener
}
private var mOnDateInstantClickListener: ((DateInstant) -> Unit)? = null
fun setOnDateInstantClickListener(listener: ((DateInstant) -> Unit)?) {
this.mOnDateInstantClickListener = listener
}
fun setOnIconClickListener(onClickListener: OnClickListener) {
entryIconView.setOnClickListener(onClickListener)
}
fun getIcon(): IconImage {
return mEntryInfo?.icon ?: IconImage()
}
fun setIcon(iconImage: IconImage) {
mEntryInfo?.icon = iconImage
refreshIcon()
}
fun setOnBackgroundColorClickListener(onClickListener: OnClickListener) {
backgroundColorButton.setOnClickListener(onClickListener)
}
fun getBackgroundColor(): Int? {
return mEntryInfo?.backgroundColor
}
fun setBackgroundColor(color: Int?) {
applyBackgroundColor(color)
mEntryInfo?.backgroundColor = color
}
private fun applyBackgroundColor(color: Int?) {
if (color != null) {
backgroundColorView.background.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)
backgroundColorView.visibility = View.VISIBLE
} else {
backgroundColorView.visibility = View.GONE
}
}
fun setOnForegroundColorClickListener(onClickListener: OnClickListener) {
foregroundColorButton.setOnClickListener(onClickListener)
}
fun getForegroundColor(): Int? {
return mEntryInfo?.foregroundColor
}
fun setForegroundColor(color: Int?) {
applyForegroundColor(color)
mEntryInfo?.foregroundColor = color
}
private fun applyForegroundColor(color: Int?) {
if (color != null) {
foregroundColorView.background.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)
foregroundColorView.visibility = View.VISIBLE
} else {
foregroundColorView.visibility = View.GONE
}
}
override fun preProcessTemplate() {
headerContainerView.isVisible = true
}
override fun buildLinearTextView(templateAttribute: TemplateAttribute,
field: Field): TextEditFieldView? {
return context?.let {
(if (TemplateField.isStandardPasswordName(context, templateAttribute.label))
PasswordTextEditFieldView(it)
else TextEditFieldView(it)).apply {
// hiddenProtectedValue (mHideProtectedValue) don't work with TextInputLayout
setProtection(field.protectedValue.isProtected)
default = templateAttribute.default
setMaxChars(templateAttribute.options.getNumberChars())
setMaxLines(templateAttribute.options.getNumberLines())
setActionClick(templateAttribute, field, this)
if (field.protectedValue.isProtected) {
textDirection = TEXT_DIRECTION_LTR
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO
}
}
}
}
override fun buildListItemsView(templateAttribute: TemplateAttribute,
field: Field): TextSelectFieldView? {
return context?.let {
TextSelectFieldView(it).apply {
setItems(templateAttribute.options.getListItems())
default = templateAttribute.default
setActionClick(templateAttribute, field, this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO
}
}
}
}
private fun setActionClick(templateAttribute: TemplateAttribute,
field: Field,
view: GenericTextFieldView) {
view.apply {
applyFontVisibility(mFontInVisibility)
label = templateAttribute.alias
?: TemplateField.getLocalizedName(context, field.name)
val fieldValue = field.protectedValue.stringValue
value = if (fieldValue.isEmpty()) templateAttribute.default else fieldValue
// TODO edition and password generator at same time
when (templateAttribute.action) {
TemplateAttributeAction.NONE -> {
setOnActionClickListener(null)
}
TemplateAttributeAction.CUSTOM_EDITION -> {
setOnActionClickListener({
mOnCustomEditionActionClickListener?.invoke(field)
}, R.drawable.ic_more_white_24dp)
}
}
if (templateAttribute.options.isAssociatedWithPasswordGenerator()) {
setOnActionClickListener({
mOnPasswordGenerationActionClickListener?.invoke(field)
}, R.drawable.ic_random_white_24dp)
}
}
}
override fun buildDataTimeView(templateAttribute: TemplateAttribute,
field: Field): DateTimeEditFieldView? {
return context?.let {
DateTimeEditFieldView(it).apply {
label = TemplateField.getLocalizedName(context, field.name)
val dateInstantType = templateAttribute.options.getDateFormat()
try {
val value = field.protectedValue.toString().trim()
type = dateInstantType
activation = value.isNotEmpty()
} catch (e: Exception) {
type = dateInstantType
activation = false
}
setOnDateClickListener = { dateInstant ->
mTempDateTimeViewId = id
mOnDateInstantClickListener?.invoke(dateInstant)
}
}
}
}
override fun getActionImageView(): View? {
return findViewWithTag<TextEditFieldView?>(FIELD_PASSWORD_TAG)?.getActionImageView()
}
fun setPasswordField(passwordField: Field) {
val passwordView = getFieldViewById(passwordField.name.hashCode())
if (passwordView is TextEditFieldView?) {
passwordView?.value = passwordField.protectedValue.stringValue
}
}
fun getPasswordField(): Field {
val passwordView: TextEditFieldView? = templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
return Field(TemplateField.LABEL_PASSWORD, ProtectedString(true, passwordView?.value ?: ""))
}
private fun setCurrentDateTimeSelection(action: (dateInstant: DateInstant) -> DateInstant) {
mTempDateTimeViewId?.let { viewId ->
val dateTimeView = getFieldViewById(viewId)
if (dateTimeView is DateTimeEditFieldView) {
dateTimeView.dateTime = DateInstant(
action.invoke(dateTimeView.dateTime).instant,
dateTimeView.dateTime.type
)
}
}
}
fun setCurrentDateTimeValue(date: DataDate) {
// Save the date
setCurrentDateTimeSelection { dateInstant ->
dateInstant.setDate(date.year, date.month, date.day)
if (dateInstant.type == DateInstant.Type.DATE_TIME) {
// Trick to recall selection with time
mOnDateInstantClickListener?.invoke(
DateInstant(dateInstant.instant, DateInstant.Type.TIME)
)
}
dateInstant
}
}
fun setCurrentTimeValue(time: DataTime) {
setCurrentDateTimeSelection { dateInstant ->
dateInstant.setTime(time.hour, time.minute)
dateInstant
}
}
override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<ViewField> {
refreshIcon()
applyBackgroundColor(mEntryInfo?.backgroundColor)
applyForegroundColor(mEntryInfo?.foregroundColor)
return super.populateViewsWithEntryInfo(showEmptyFields)
}
override fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean,
retrieveDefaultValues: Boolean) {
super.populateEntryInfoWithViews(templateFieldNotEmpty, retrieveDefaultValues)
val getField: (id: String) -> String? = { key ->
getCustomFieldOrNull(key)?.protectedValue?.stringValue
}
mEntryInfo?.otpModel = OtpEntryFields.parseFields(getField)?.otpModel
mEntryInfo?.passkey = PasskeyEntryFields.parseFields(getField)
mEntryInfo?.appOrigin = AppOriginEntryField.parseFields(getField)
}
override fun onRestoreEntryInstanceState(state: SavedState) {
mTempDateTimeViewId = state.tempDateTimeViewId
}
override fun onSaveEntryInstanceState(savedState: SavedState) {
savedState.tempDateTimeViewId = this.mTempDateTimeViewId
}
}