mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Add ProtectedFieldView callback #2283
This commit is contained in:
@@ -63,6 +63,8 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildSpecia
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
|
||||
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
@@ -101,6 +103,7 @@ import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.view.updateLockPaddingStart
|
||||
import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.EnumSet
|
||||
import java.util.UUID
|
||||
@@ -129,6 +132,7 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
private var mTemplatesSelectorAdapter: TemplatesSelectorAdapter? = null
|
||||
|
||||
private val mColorPickerViewModel: ColorPickerViewModel by viewModels()
|
||||
private val mUserVerificationViewModel: UserVerificationViewModel by viewModels()
|
||||
|
||||
private var mAllowCustomFields = false
|
||||
private var mAllowOTP = false
|
||||
@@ -383,23 +387,48 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
mEntryEditViewModel.uiState.collect { uiState ->
|
||||
mEntryEditViewModel.entryEditState.collect { uiState ->
|
||||
when (uiState) {
|
||||
EntryEditViewModel.UIState.Loading -> {}
|
||||
EntryEditViewModel.UIState.ShowOverwriteMessage -> {
|
||||
if (mEntryEditViewModel.warningOverwriteDataAlreadyApproved.not()) {
|
||||
AlertDialog.Builder(this@EntryEditActivity)
|
||||
.setTitle(R.string.warning_overwrite_data_title)
|
||||
.setMessage(R.string.warning_overwrite_data_description)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
mEntryEditViewModel.backPressedAlreadyApproved = true
|
||||
onCancelSpecialMode()
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
mEntryEditViewModel.warningOverwriteDataAlreadyApproved = true
|
||||
}
|
||||
.create().show()
|
||||
is EntryEditViewModel.EntryEditState.Loading -> {}
|
||||
is EntryEditViewModel.EntryEditState.ShowOverwriteMessage -> {
|
||||
AlertDialog.Builder(this@EntryEditActivity)
|
||||
.setTitle(R.string.warning_overwrite_data_title)
|
||||
.setMessage(R.string.warning_overwrite_data_description)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
mEntryEditViewModel.backPressedAlreadyApproved = true
|
||||
onCancelSpecialMode()
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
.create().show()
|
||||
mEntryEditViewModel.actionPerformed()
|
||||
}
|
||||
is EntryEditViewModel.EntryEditState.RequestUnprotectField -> {
|
||||
val fieldView = uiState.protectedFieldView
|
||||
if (fieldView.isCurrentlyProtected()) {
|
||||
checkUserVerification(
|
||||
userVerificationViewModel = mUserVerificationViewModel,
|
||||
dataToVerify = UserVerificationData(protectedFieldView = fieldView)
|
||||
)
|
||||
} else {
|
||||
fieldView.protect()
|
||||
}
|
||||
mEntryEditViewModel.actionPerformed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
mUserVerificationViewModel.userVerificationState.collect { uVState ->
|
||||
when (uVState) {
|
||||
is UserVerificationViewModel.UIState.Loading -> {}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> {
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> {
|
||||
uVState.dataToVerify.protectedFieldView?.unprotect()
|
||||
mUserVerificationViewModel.onUserVerificationReceived()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,9 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
setOnForegroundColorClickListener {
|
||||
mEntryEditViewModel.requestForegroundColorSelection(templateView.getForegroundColor())
|
||||
}
|
||||
setOnUnprotectClickListener { _, textEditFieldView ->
|
||||
mEntryEditViewModel.requestUnprotectField(textEditFieldView)
|
||||
}
|
||||
setOnCustomEditionActionClickListener { field ->
|
||||
mEntryEditViewModel.requestCustomFieldEdition(field)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package com.kunzisoft.keepass.credentialprovider
|
||||
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.view.ProtectedFieldView
|
||||
|
||||
data class UserVerificationData(
|
||||
val database: ContextualDatabase? = null,
|
||||
val entryId: NodeId<*>? = null,
|
||||
val protectedFieldView: ProtectedFieldView? = null,
|
||||
val preferenceKey: String? = null
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.view.View.OnClickListener
|
||||
|
||||
interface ProtectedFieldView {
|
||||
fun setOnUnprotectClickListener(onUnprotectClickListener: OnClickListener?)
|
||||
fun protect()
|
||||
fun unprotect()
|
||||
fun isCurrentlyProtected(): Boolean
|
||||
}
|
||||
@@ -18,9 +18,9 @@ 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.AppOriginEntryField
|
||||
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
|
||||
|
||||
@@ -35,6 +35,11 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
@IdRes
|
||||
private var mTempDateTimeViewId: Int? = null
|
||||
|
||||
private var mOnUnprotectClickListener: ((Field, ProtectedFieldView) -> Unit)? = null
|
||||
fun setOnUnprotectClickListener(listener: ((Field, ProtectedFieldView) -> Unit)?) {
|
||||
this.mOnUnprotectClickListener = listener
|
||||
}
|
||||
|
||||
private var mOnCustomEditionActionClickListener: ((Field) -> Unit)? = null
|
||||
fun setOnCustomEditionActionClickListener(listener: ((Field) -> Unit)?) {
|
||||
this.mOnCustomEditionActionClickListener = listener
|
||||
@@ -80,9 +85,9 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
if (color != null) {
|
||||
backgroundColorView.background.colorFilter = BlendModeColorFilterCompat
|
||||
.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)
|
||||
backgroundColorView.visibility = View.VISIBLE
|
||||
backgroundColorView.visibility = VISIBLE
|
||||
} else {
|
||||
backgroundColorView.visibility = View.GONE
|
||||
backgroundColorView.visibility = GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,9 +108,9 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
if (color != null) {
|
||||
foregroundColorView.background.colorFilter = BlendModeColorFilterCompat
|
||||
.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)
|
||||
foregroundColorView.visibility = View.VISIBLE
|
||||
foregroundColorView.visibility = VISIBLE
|
||||
} else {
|
||||
foregroundColorView.visibility = View.GONE
|
||||
foregroundColorView.visibility = GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,14 +118,20 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
headerContainerView.isVisible = true
|
||||
}
|
||||
|
||||
override fun buildLinearTextView(templateAttribute: TemplateAttribute,
|
||||
field: Field): TextEditFieldView? {
|
||||
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)
|
||||
if (field.protectedValue.isProtected) {
|
||||
setOnUnprotectClickListener {
|
||||
mOnUnprotectClickListener?.invoke(field, this)
|
||||
}
|
||||
}
|
||||
default = templateAttribute.default
|
||||
setMaxChars(templateAttribute.options.getNumberChars())
|
||||
setMaxLines(templateAttribute.options.getNumberLines())
|
||||
@@ -129,7 +140,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
textDirection = TEXT_DIRECTION_LTR
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO
|
||||
importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +154,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
default = templateAttribute.default
|
||||
setActionClick(templateAttribute, field, this)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO
|
||||
importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +168,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
label = templateAttribute.alias
|
||||
?: TemplateField.getLocalizedName(context, field.name)
|
||||
val fieldValue = field.protectedValue.stringValue
|
||||
value = if (fieldValue.isEmpty()) templateAttribute.default else fieldValue
|
||||
value = fieldValue.ifEmpty { templateAttribute.default }
|
||||
// TODO edition and password generator at same time
|
||||
when (templateAttribute.action) {
|
||||
TemplateAttributeAction.NONE -> {
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.text.InputFilter
|
||||
import android.text.InputType
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.method.PasswordTransformationMethod
|
||||
import android.text.method.SingleLineTransformationMethod
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
@@ -25,7 +27,8 @@ import com.kunzisoft.keepass.R
|
||||
open class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: RelativeLayout(context, attrs, defStyle), GenericTextFieldView {
|
||||
: RelativeLayout(context, attrs, defStyle),
|
||||
GenericTextFieldView, ProtectedFieldView {
|
||||
|
||||
private var labelViewId = ViewCompat.generateViewId()
|
||||
private var valueViewId = ViewCompat.generateViewId()
|
||||
@@ -168,11 +171,28 @@ open class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
|
||||
fun setProtection(protection: Boolean) {
|
||||
if (protection) {
|
||||
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
}
|
||||
override fun setOnUnprotectClickListener(onUnprotectClickListener: OnClickListener?) {
|
||||
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
/*
|
||||
// FIXME Called by itself during orientation change
|
||||
labelView.setEndIconOnClickListener {
|
||||
onUnprotectClickListener?.onClick(this@TextEditFieldView)
|
||||
}*/
|
||||
}
|
||||
|
||||
override fun isCurrentlyProtected(): Boolean {
|
||||
return valueView.transformationMethod == PasswordTransformationMethod.getInstance()
|
||||
}
|
||||
|
||||
override fun protect() {
|
||||
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
valueView.transformationMethod = PasswordTransformationMethod.getInstance()
|
||||
}
|
||||
|
||||
override fun unprotect() {
|
||||
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
valueView.transformationMethod = SingleLineTransformationMethod.getInstance()
|
||||
}
|
||||
|
||||
override fun setOnActionClickListener(onActionClickListener: OnClickListener?,
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.utils.IOActionTask
|
||||
import com.kunzisoft.keepass.view.ProtectedFieldView
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.util.UUID
|
||||
@@ -37,7 +38,6 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
|
||||
// To show dialog only one time
|
||||
var backPressedAlreadyApproved = false
|
||||
var warningOverwriteDataAlreadyApproved = false
|
||||
|
||||
// Useful to not relaunch a current action
|
||||
private var actionLocked: Boolean = false
|
||||
@@ -81,8 +81,8 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
|
||||
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
|
||||
|
||||
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||
val uiState: StateFlow<UIState> = mUiState
|
||||
private val mEntryEditState = MutableStateFlow<EntryEditState>(EntryEditState.Loading)
|
||||
val entryEditState: StateFlow<EntryEditState> = mEntryEditState
|
||||
|
||||
fun loadTemplateEntry(database: ContextualDatabase?) {
|
||||
loadTemplateEntry(database, mEntryId, mParentId, mRegisterInfo)
|
||||
@@ -125,7 +125,7 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
mEntryId = null
|
||||
_templatesEntry.value = templatesEntry
|
||||
if (templatesEntry?.overwrittenData == true) {
|
||||
mUiState.value = UIState.ShowOverwriteMessage
|
||||
mEntryEditState.value = EntryEditState.ShowOverwriteMessage
|
||||
}
|
||||
}
|
||||
).execute()
|
||||
@@ -293,6 +293,10 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
_onPasswordSelected.value = passwordField
|
||||
}
|
||||
|
||||
fun requestUnprotectField(fieldView: ProtectedFieldView) {
|
||||
mEntryEditState.value = EntryEditState.RequestUnprotectField(fieldView)
|
||||
}
|
||||
|
||||
fun requestCustomFieldEdition(customField: Field) {
|
||||
_requestCustomFieldEdition.value = customField
|
||||
}
|
||||
@@ -348,6 +352,10 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
_onBinaryPreviewLoaded.value = AttachmentPosition(entryAttachmentState, viewPosition)
|
||||
}
|
||||
|
||||
fun actionPerformed() {
|
||||
mEntryEditState.value = EntryEditState.Loading
|
||||
}
|
||||
|
||||
data class TemplatesEntry(
|
||||
val isTemplate: Boolean,
|
||||
val templates: List<Template>,
|
||||
@@ -362,9 +370,12 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
data class AttachmentUpload(val attachmentToUploadUri: Uri, val attachment: Attachment)
|
||||
data class AttachmentPosition(val entryAttachmentState: EntryAttachmentState, val viewPosition: Float)
|
||||
|
||||
sealed class UIState {
|
||||
object Loading: UIState()
|
||||
object ShowOverwriteMessage: UIState()
|
||||
sealed class EntryEditState {
|
||||
object Loading: EntryEditState()
|
||||
object ShowOverwriteMessage: EntryEditState()
|
||||
data class RequestUnprotectField(
|
||||
val protectedFieldView: ProtectedFieldView
|
||||
): EntryEditState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user