diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt index 77936f411..e7a00baf5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt @@ -56,10 +56,11 @@ import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity import com.kunzisoft.keepass.adapters.TagsAdapter import com.kunzisoft.keepass.credentialprovider.SpecialMode +import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType import com.kunzisoft.keepass.credentialprovider.UserVerificationData import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded -import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestUnprotectField +import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestShowUnprotectField import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.element.Attachment @@ -330,11 +331,26 @@ class EntryActivity : DatabaseLockActivity() { when (entryState) { is EntryViewModel.EntryState.Loading -> {} is EntryViewModel.EntryState.RequestUnprotectField -> { - requestUnprotectField( - userVerificationViewModel = mUserVerificationViewModel, - database = mDatabase, - protectedFieldView = entryState.protectedFieldView - ) + mDatabase?.let { database -> + requestShowUnprotectField( + userVerificationViewModel = mUserVerificationViewModel, + database = database, + protectedFieldView = entryState.protectedFieldView + ) + } + mEntryViewModel.actionPerformed() + } + is EntryViewModel.EntryState.RequestCopyProtectedField -> { + mDatabase?.let { database -> + checkUserVerification( + userVerificationViewModel = mUserVerificationViewModel, + dataToVerify = UserVerificationData( + actionType = UserVerificationActionType.COPY_PROTECTED_FIELD, + database = database, + field = entryState.field, + ) + ) + } mEntryViewModel.actionPerformed() } } @@ -351,10 +367,24 @@ class EntryActivity : DatabaseLockActivity() { mUserVerificationViewModel.onUserVerificationReceived() } is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { - // Edit Entry if corresponding data - editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId) - // Unprotect field if corresponding data - uVState.dataToVerify.protectedFieldView?.unprotect() + val data = uVState.dataToVerify + when (data.actionType) { + UserVerificationActionType.SHOW_PROTECTED_FIELD -> { + // Unprotect field by its view + data.protectedFieldView?.unprotect() + } + UserVerificationActionType.COPY_PROTECTED_FIELD -> { + // Copy field value + data.field?.let { + mEntryViewModel.copyToClipboard(it) + } + } + UserVerificationActionType.EDIT_ENTRY -> { + // Edit Entry + editEntry(data.database, data.entryId) + } + else -> {} + } mUserVerificationViewModel.onUserVerificationReceived() } } @@ -531,10 +561,16 @@ class EntryActivity : DatabaseLockActivity() { when (item.itemId) { R.id.menu_edit -> { if (mEntryViewModel.entryInfo?.isUserVerificationNeeded() == true) { - checkUserVerification( - userVerificationViewModel = mUserVerificationViewModel, - dataToVerify = UserVerificationData(mDatabase, mEntryViewModel.mainEntryId) - ) + mDatabase?.let { database -> + checkUserVerification( + userVerificationViewModel = mUserVerificationViewModel, + dataToVerify = UserVerificationData( + actionType = UserVerificationActionType.EDIT_ENTRY, + database = database, + entryId = mEntryViewModel.mainEntryId + ) + ) + } } else { editEntry(mDatabase, mEntryViewModel.mainEntryId) } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index da8476373..d5fba9e34 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -63,7 +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.UserVerificationHelper.Companion.requestUnprotectField +import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType +import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestShowUnprotectField import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.element.Attachment @@ -403,11 +404,13 @@ class EntryEditActivity : DatabaseLockActivity(), mEntryEditViewModel.actionPerformed() } is EntryEditViewModel.EntryEditState.RequestUnprotectField -> { - requestUnprotectField( - userVerificationViewModel = mUserVerificationViewModel, - database = mDatabase, - protectedFieldView = uiState.protectedFieldView - ) + mDatabase?.let { database -> + requestShowUnprotectField( + userVerificationViewModel = mUserVerificationViewModel, + database = database, + protectedFieldView = uiState.protectedFieldView + ) + } mEntryEditViewModel.actionPerformed() } } @@ -424,7 +427,12 @@ class EntryEditActivity : DatabaseLockActivity(), mUserVerificationViewModel.onUserVerificationReceived() } is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { - uVState.dataToVerify.protectedFieldView?.unprotect() + when (uVState.dataToVerify.actionType) { + UserVerificationActionType.SHOW_PROTECTED_FIELD -> { + uVState.dataToVerify.protectedFieldView?.unprotect() + } + else -> {} + } mUserVerificationViewModel.onUserVerificationReceived() } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 9aa4d6190..2f73bb379 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -75,6 +75,7 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeModes import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.TypeMode +import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType import com.kunzisoft.keepass.credentialprovider.UserVerificationData import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded @@ -586,7 +587,15 @@ class GroupActivity : DatabaseLockActivity(), mUserVerificationViewModel.onUserVerificationReceived() } is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { - editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId) + val data = uVState.dataToVerify + when (data.actionType) { + UserVerificationActionType.EDIT_ENTRY -> { + editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId) + } + UserVerificationActionType.MERGE_FROM_DATABASE -> {} + UserVerificationActionType.SAVE_TO_DATABASE -> {} + else -> {} + } mUserVerificationViewModel.onUserVerificationReceived() } } @@ -1101,7 +1110,11 @@ class GroupActivity : DatabaseLockActivity(), if ((node as Entry).getEntryInfo(database).isUserVerificationNeeded()) { checkUserVerification( userVerificationViewModel = mUserVerificationViewModel, - dataToVerify = UserVerificationData(database, node.nodeId) + dataToVerify = UserVerificationData( + actionType = UserVerificationActionType.EDIT_ENTRY, + database = database, + entryId = node.nodeId + ) ) } else { editEntry(database, node.nodeId) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt index 47dd60682..e2df2d0f0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt @@ -16,13 +16,10 @@ import com.kunzisoft.keepass.R import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.element.Attachment -import com.kunzisoft.keepass.database.element.template.TemplateField -import com.kunzisoft.keepass.database.helper.getLocalizedName import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.settings.PreferencesUtil -import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString import com.kunzisoft.keepass.utils.UUIDUtils.asHexString import com.kunzisoft.keepass.view.TemplateView @@ -50,8 +47,6 @@ class EntryFragment: DatabaseFragment() { private lateinit var uuidContainerView: View private lateinit var uuidReferenceView: TextView - private var mClipboardHelper: ClipboardHelper? = null - private val mEntryViewModel: EntryViewModel by activityViewModels() override fun onCreateView(inflater: LayoutInflater, @@ -66,10 +61,6 @@ class EntryFragment: DatabaseFragment() { savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - context?.let { context -> - mClipboardHelper = ClipboardHelper(context) - } - rootView = view // Hide only the first time if (savedInstanceState == null) { @@ -152,18 +143,14 @@ class EntryFragment: DatabaseFragment() { private fun assignEntryInfo(entryInfo: EntryInfo?) { // Set copy buttons templateView.apply { - setOnUnprotectClickListener { _, textEditFieldView -> - mEntryViewModel.requestUnprotectField(textEditFieldView) + setOnUnprotectClickListener { protectedFieldView -> + mEntryViewModel.requestUnprotectField(protectedFieldView) } setOnAskCopySafeClickListener { showClipboardDialog() } - setOnCopyActionClickListener { field -> - mClipboardHelper?.timeoutCopyToClipboard( - TemplateField.getLocalizedName(context, field.name), - field.protectedValue.stringValue, - field.protectedValue.isProtected - ) + setOnCopyActionClickListener { field, protectedFieldView -> + mEntryViewModel.requestCopyField(field, protectedFieldView) } } @@ -251,7 +238,7 @@ class EntryFragment: DatabaseFragment() { fun launchEntryCopyEducationAction() { val appNameString = getString(R.string.app_name) - mClipboardHelper?.timeoutCopyToClipboard(appNameString, appNameString) + mEntryViewModel.copyToClipboard(appNameString) } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationData.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationData.kt index 4d1bf8a8b..60d7e0bd1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationData.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationData.kt @@ -1,12 +1,25 @@ package com.kunzisoft.keepass.credentialprovider import com.kunzisoft.keepass.database.ContextualDatabase +import com.kunzisoft.keepass.database.element.Field import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.view.ProtectedFieldView data class UserVerificationData( + val actionType: UserVerificationActionType, val database: ContextualDatabase? = null, val entryId: NodeId<*>? = null, + val field: Field? = null, val protectedFieldView: ProtectedFieldView? = null, val preferenceKey: String? = null -) \ No newline at end of file +) + +enum class UserVerificationActionType { + LAUNCH_PASSKEY_CEREMONY, + SHOW_PROTECTED_FIELD, + COPY_PROTECTED_FIELD, + EDIT_ENTRY, + EDIT_DATABASE_SETTING, + MERGE_FROM_DATABASE, + SAVE_TO_DATABASE +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationHelper.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationHelper.kt index c5cad4f4f..36e70dab8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/UserVerificationHelper.kt @@ -104,15 +104,16 @@ class UserVerificationHelper { activity?.checkUserVerification(userVerificationViewModel, dataToVerify) } - fun FragmentActivity.requestUnprotectField( + fun FragmentActivity.requestShowUnprotectField( userVerificationViewModel: UserVerificationViewModel, - database: ContextualDatabase?, + database: ContextualDatabase, protectedFieldView: ProtectedFieldView ) { if (protectedFieldView.isCurrentlyProtected()) { checkUserVerification( userVerificationViewModel = userVerificationViewModel, dataToVerify = UserVerificationData( + actionType = UserVerificationActionType.SHOW_PROTECTED_FIELD, database = database, protectedFieldView = protectedFieldView ) diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt index f27e86091..9e7771060 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/PasskeyLauncherActivity.kt @@ -45,6 +45,7 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addTypeMode import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.TypeMode +import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType import com.kunzisoft.keepass.credentialprovider.UserVerificationData import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.addUserVerification import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification @@ -179,12 +180,18 @@ class PasskeyLauncherActivity : DatabaseLockActivity() { when (uiState) { is UserVerificationViewModel.UIState.Loading -> {} is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { - passkeyLauncherViewModel.launchActionIfNeeded( - userVerified = true, - intent = intent, - specialMode = mSpecialMode, - database = uiState.dataToVerify.database - ) + val data = uiState.dataToVerify + when (data.actionType) { + UserVerificationActionType.LAUNCH_PASSKEY_CEREMONY -> { + passkeyLauncherViewModel.launchActionIfNeeded( + userVerified = true, + intent = intent, + specialMode = mSpecialMode, + database = uiState.dataToVerify.database + ) + } + else -> {} + } userVerificationViewModel.onUserVerificationReceived() } is UserVerificationViewModel.UIState.OnUserVerificationCanceled -> { @@ -207,7 +214,10 @@ class PasskeyLauncherActivity : DatabaseLockActivity() { if (userVerificationNeeded) { checkUserVerification( userVerificationViewModel = userVerificationViewModel, - dataToVerify = UserVerificationData(database) + dataToVerify = UserVerificationData( + actionType = UserVerificationActionType.LAUNCH_PASSKEY_CEREMONY, + database = database + ) ) } else { passkeyLauncherViewModel.launchActionIfNeeded( diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt index 62c336701..268f863cf 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt @@ -42,6 +42,7 @@ import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused +import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType import com.kunzisoft.keepass.credentialprovider.UserVerificationData import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification import com.kunzisoft.keepass.database.ContextualDatabase @@ -188,28 +189,34 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev mUserVerificationViewModel.onUserVerificationReceived() } is UserVerificationViewModel.UIState.OnUserVerificationSucceeded -> { - state.dataToVerify.database?.let { database -> - state.dataToVerify.preferenceKey?.let { preferenceKey -> + val data = state.dataToVerify + when (data.actionType) { + UserVerificationActionType.EDIT_DATABASE_SETTING -> { + val database = data.database + val preferenceKey = data.preferenceKey + if (database != null && preferenceKey != null) { // Main Preferences - when (preferenceKey) { - // Master Key - getString(R.string.settings_database_change_credentials_key) -> { - SetMainCredentialDialogFragment - .getInstance(database.allowNoMasterKey) - .show(parentFragmentManager, "passwordDialog") + when (preferenceKey) { + // Master Key + getString(R.string.settings_database_change_credentials_key) -> { + SetMainCredentialDialogFragment + .getInstance(database.allowNoMasterKey) + .show(parentFragmentManager, "passwordDialog") + } + else -> {} } - else -> {} + // TODO Settings in compose + @Suppress("DEPRECATION") + mSettingsViewModel.dialogFragment?.let { dialogFragment -> + dialogFragment.setTargetFragment( + this@NestedDatabaseSettingsFragment, 0 + ) + dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT) + } + mSettingsViewModel.dialogFragment = null } - // TODO Settings in compose - @Suppress("DEPRECATION") - mSettingsViewModel.dialogFragment?.let { dialogFragment -> - dialogFragment.setTargetFragment( - this@NestedDatabaseSettingsFragment, 0 - ) - dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT) - } - mSettingsViewModel.dialogFragment = null } + else -> {} } mUserVerificationViewModel.onUserVerificationReceived() } @@ -483,6 +490,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev checkUserVerification( mUserVerificationViewModel, UserVerificationData( + actionType = UserVerificationActionType.EDIT_DATABASE_SETTING, database = database, preferenceKey = changeCredentialKey ) @@ -513,7 +521,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev // To reassign color listener after orientation change val chromaDialog = parentFragmentManager.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat? chromaDialog?.onColorSelectedListener = colorSelectedListener - } catch (e: Exception) {} + } catch (_: Exception) {} return view } @@ -785,6 +793,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev checkUserVerification( mUserVerificationViewModel, UserVerificationData( + actionType = UserVerificationActionType.EDIT_DATABASE_SETTING, database = mDatabase, preferenceKey = preference.key ) 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 9062d6921..4aea2690e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt @@ -25,8 +25,8 @@ class TemplateView @JvmOverloads constructor(context: Context, : TemplateAbstractView (context, attrs, defStyle) { - private var mOnUnprotectClickListener: ((Field, ProtectedFieldView) -> Unit)? = null - fun setOnUnprotectClickListener(listener: ((Field, ProtectedFieldView) -> Unit)?) { + private var mOnUnprotectClickListener: ((ProtectedFieldView) -> Unit)? = null + fun setOnUnprotectClickListener(listener: ((ProtectedFieldView) -> Unit)?) { this.mOnUnprotectClickListener = listener } @@ -34,8 +34,8 @@ class TemplateView @JvmOverloads constructor(context: Context, fun setOnAskCopySafeClickListener(listener: (() -> Unit)? = null) { this.mOnAskCopySafeClickListener = listener } - private var mOnCopyActionClickListener: ((Field) -> Unit)? = null - fun setOnCopyActionClickListener(listener: ((Field) -> Unit)? = null) { + private var mOnCopyActionClickListener: ((Field, ProtectedFieldView) -> Unit)? = null + fun setOnCopyActionClickListener(listener: ((Field, ProtectedFieldView) -> Unit)? = null) { this.mOnCopyActionClickListener = listener } @@ -64,7 +64,7 @@ class TemplateView @JvmOverloads constructor(context: Context, else TextFieldView(it)).apply { applyFontVisibility(mFontInVisibility) setProtection(field.protectedValue.isProtected) { - mOnUnprotectClickListener?.invoke(field, this) + mOnUnprotectClickListener?.invoke(this) } label = templateAttribute.alias ?: TemplateField.getLocalizedName(context, field.name) @@ -85,7 +85,15 @@ class TemplateView @JvmOverloads constructor(context: Context, setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonClickListener { label, value -> mOnCopyActionClickListener - ?.invoke(Field(label, ProtectedString(true, value))) + ?.invoke( + Field( + name = label, + value = ProtectedString( + enableProtection = true, + string = value + ) + ), this + ) } } else { setCopyButtonState(TextFieldView.ButtonState.GONE) @@ -96,7 +104,15 @@ class TemplateView @JvmOverloads constructor(context: Context, setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonClickListener { label, value -> mOnCopyActionClickListener - ?.invoke(Field(label, ProtectedString(false, value))) + ?.invoke( + Field( + name = label, + value = ProtectedString( + enableProtection = false, + string = value + ) + ), this + ) } } } @@ -184,9 +200,15 @@ class TemplateView @JvmOverloads constructor(context: Context, value = otpElement.tokenString setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonClickListener { _, _ -> - mOnCopyActionClickListener?.invoke(Field( - otpElement.type.name, - ProtectedString(false, otpElement.token))) + mOnCopyActionClickListener?.invoke( + Field( + name = otpElement.type.name, + value = ProtectedString( + enableProtection = false, + string = otpElement.token + ) + ), this + ) } textDirection = TEXT_DIRECTION_LTR mLastOtpTokenView = this diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/EntryViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/EntryViewModel.kt index 1f6768d2c..2eb2c0854 100644 --- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/EntryViewModel.kt +++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/EntryViewModel.kt @@ -19,17 +19,22 @@ */ package com.kunzisoft.keepass.viewmodels +import android.app.Application +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.element.Attachment +import com.kunzisoft.keepass.database.element.Field import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.template.Template +import com.kunzisoft.keepass.database.element.template.TemplateField +import com.kunzisoft.keepass.database.helper.getLocalizedName import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.otp.OtpElement +import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.utils.IOActionTask import com.kunzisoft.keepass.view.ProtectedFieldView import kotlinx.coroutines.flow.MutableStateFlow @@ -37,7 +42,7 @@ import kotlinx.coroutines.flow.StateFlow import java.util.UUID -class EntryViewModel: ViewModel() { +class EntryViewModel(application: Application): AndroidViewModel(application) { var mainEntryId: NodeId? = null private set @@ -50,6 +55,8 @@ class EntryViewModel: ViewModel() { var entryLoaded = false private set + private var mClipboardHelper: ClipboardHelper = ClipboardHelper(application) + val entryInfoHistory : LiveData get() = _entryInfoHistory private val _entryInfoHistory = MutableLiveData() @@ -136,6 +143,14 @@ class EntryViewModel: ViewModel() { mEntryState.value = EntryState.RequestUnprotectField(fieldView) } + fun requestCopyField(field: Field, fieldView: ProtectedFieldView) { + // Only request the User Verification if the field is protected and not shown + if (field.protectedValue.isProtected && fieldView.isCurrentlyProtected()) + mEntryState.value = EntryState.RequestCopyProtectedField(field) + else + copyToClipboard(field) + } + fun onOtpElementUpdated(optElement: OtpElement?) { _onOtpElementUpdated.value = optElement } @@ -156,6 +171,18 @@ class EntryViewModel: ViewModel() { _sectionSelected.value = section } + fun copyToClipboard(field: Field) { + mClipboardHelper.timeoutCopyToClipboard( + TemplateField.getLocalizedName(getApplication(), field.name), + field.protectedValue.stringValue, + field.protectedValue.isProtected + ) + } + + fun copyToClipboard(text: String) { + mClipboardHelper.timeoutCopyToClipboard(text, text) + } + fun actionPerformed() { mEntryState.value = EntryState.Loading } @@ -186,6 +213,9 @@ class EntryViewModel: ViewModel() { data class RequestUnprotectField( val protectedFieldView: ProtectedFieldView ): EntryState() + data class RequestCopyProtectedField( + val field: Field + ): EntryState() } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/UserVerificationViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/UserVerificationViewModel.kt index fa5ac00c7..2e84f5a1d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/UserVerificationViewModel.kt +++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/UserVerificationViewModel.kt @@ -15,16 +15,17 @@ class UserVerificationViewModel: ViewModel() { private val mUiState = MutableStateFlow(UIState.Loading) val userVerificationState: StateFlow = mUiState - var dataToVerify: UserVerificationData = UserVerificationData() + var dataToVerify: UserVerificationData? = null fun checkMainCredential(checkString: String) { // Check the password part - if (dataToVerify.database?.checkKey(getCheckKey(checkString)) == true) - onUserVerificationSucceeded(dataToVerify) + val data = dataToVerify + if (data?.database?.checkKey(getCheckKey(checkString)) == true) + onUserVerificationSucceeded(data) else { onUserVerificationFailed(dataToVerify, InvalidCredentialsDatabaseException()) } - dataToVerify = UserVerificationData() + dataToVerify = null } fun onUserVerificationSucceeded(dataToVerify: UserVerificationData) { @@ -32,7 +33,7 @@ class UserVerificationViewModel: ViewModel() { } fun onUserVerificationFailed( - dataToVerify: UserVerificationData = UserVerificationData(), + dataToVerify: UserVerificationData? = null, error: Throwable? = null ) { this.dataToVerify = dataToVerify @@ -45,9 +46,11 @@ class UserVerificationViewModel: ViewModel() { sealed class UIState { object Loading: UIState() - data class OnUserVerificationSucceeded(val dataToVerify: UserVerificationData): UIState() + data class OnUserVerificationSucceeded( + val dataToVerify: UserVerificationData + ): UIState() data class OnUserVerificationCanceled( - val dataToVerify: UserVerificationData, + val dataToVerify: UserVerificationData?, val error: Throwable? ): UIState() }