mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
feat: Warning when overwriting existing passkey #2124
This commit is contained in:
@@ -41,6 +41,9 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
@@ -99,6 +102,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 kotlinx.coroutines.launch
|
||||
import java.util.EnumSet
|
||||
import java.util.UUID
|
||||
|
||||
@@ -154,9 +158,6 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
// To ask data lost only one time
|
||||
private var backPressedAlreadyApproved = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_entry_edit)
|
||||
@@ -408,6 +409,31 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
mEntryEditViewModel.uiState.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun viewToInvalidateTimeout(): View? {
|
||||
@@ -422,7 +448,7 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
super.onDatabaseRetrieved(database)
|
||||
mAllowCustomFields = database?.allowEntryCustomFields() == true
|
||||
mAllowOTP = database?.allowOTP == true
|
||||
mEntryEditViewModel.loadDatabase(database)
|
||||
mEntryEditViewModel.loadTemplateEntry(database)
|
||||
mTemplatesSelectorAdapter?.apply {
|
||||
iconDrawableFactory = mDatabase?.iconDrawableFactory
|
||||
notifyDataSetChanged()
|
||||
@@ -751,13 +777,13 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
}
|
||||
|
||||
private fun onApprovedBackPressed(approved: () -> Unit) {
|
||||
if (!backPressedAlreadyApproved) {
|
||||
if (mEntryEditViewModel.backPressedAlreadyApproved.not()) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.discard_changes)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.discard) { _, _ ->
|
||||
mAttachmentFileBinderManager?.stopUploadAllAttachments()
|
||||
backPressedAlreadyApproved = true
|
||||
mEntryEditViewModel.backPressedAlreadyApproved = true
|
||||
approved.invoke()
|
||||
}.create().show()
|
||||
} else {
|
||||
|
||||
@@ -19,30 +19,39 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.utils
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
/**
|
||||
* Class to invoke action in a separate IO thread
|
||||
*/
|
||||
class IOActionTask<T>(
|
||||
private val action: () -> T ,
|
||||
private val afterActionListener: ((T?) -> Unit)? = null) {
|
||||
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
private val action: () -> T,
|
||||
private val onActionComplete: ((T?) -> Unit)? = null,
|
||||
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main),
|
||||
private val exceptionHandler: CoroutineExceptionHandler? = null
|
||||
) {
|
||||
fun execute() {
|
||||
mainScope.launch {
|
||||
scope.launch(exceptionHandler ?: EmptyCoroutineContext) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val asyncResult: Deferred<T?> = async {
|
||||
try {
|
||||
action.invoke()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
exceptionHandler?.let {
|
||||
action.invoke()
|
||||
} ?: try {
|
||||
action.invoke()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
afterActionListener?.invoke(asyncResult.await())
|
||||
onActionComplete?.invoke(asyncResult.await())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.kunzisoft.keepass.viewmodels
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
@@ -19,6 +20,8 @@ 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 kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
@@ -32,6 +35,10 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
private var mIsTemplate: Boolean = false
|
||||
private val mTempAttachments = mutableListOf<EntryAttachmentState>()
|
||||
|
||||
// To show dialog only one time
|
||||
var backPressedAlreadyApproved = false
|
||||
var warningOverwriteDataAlreadyApproved = false
|
||||
|
||||
val templatesEntry : LiveData<TemplatesEntry?> get() = _templatesEntry
|
||||
private val _templatesEntry = MutableLiveData<TemplatesEntry?>()
|
||||
|
||||
@@ -71,7 +78,10 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
|
||||
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
|
||||
|
||||
fun loadDatabase(database: ContextualDatabase?) {
|
||||
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||
val uiState: StateFlow<UIState> = mUiState
|
||||
|
||||
fun loadTemplateEntry(database: ContextualDatabase?) {
|
||||
loadTemplateEntry(database, mEntryId, mParentId, mRegisterInfo)
|
||||
}
|
||||
|
||||
@@ -88,7 +98,8 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
database?.let {
|
||||
mEntryId?.let {
|
||||
IOActionTask(
|
||||
{
|
||||
scope = viewModelScope,
|
||||
action = {
|
||||
// Create an Entry copy to modify from the database entry
|
||||
mEntry = database.getEntryById(it)
|
||||
// Retrieve the parent
|
||||
@@ -107,16 +118,20 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
)
|
||||
}
|
||||
},
|
||||
{ templatesEntry ->
|
||||
onActionComplete = { templatesEntry ->
|
||||
mEntryId = null
|
||||
_templatesEntry.value = templatesEntry
|
||||
if (templatesEntry?.overwrittenData == true) {
|
||||
mUiState.value = UIState.ShowOverwriteMessage
|
||||
}
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
|
||||
mParentId?.let {
|
||||
IOActionTask(
|
||||
{
|
||||
scope = viewModelScope,
|
||||
action = {
|
||||
mParent = database.getGroupById(it)
|
||||
mParent?.let { parentGroup ->
|
||||
mEntry = database.createEntry()?.apply {
|
||||
@@ -146,7 +161,7 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
)
|
||||
}
|
||||
},
|
||||
{ templatesEntry ->
|
||||
onActionComplete = { templatesEntry ->
|
||||
mParentId = null
|
||||
_templatesEntry.value = templatesEntry
|
||||
}
|
||||
@@ -165,6 +180,7 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
val entryTemplate = entry?.let { database.getTemplate(it) }
|
||||
?: Template.STANDARD
|
||||
var entryInfo: EntryInfo? = null
|
||||
var overwrittenData = false
|
||||
// Decode the entry / load entry info
|
||||
entry?.let {
|
||||
database.decodeEntryWithTemplateConfiguration(it).let { entry ->
|
||||
@@ -172,13 +188,19 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
entry.getEntryInfo(database, true).let { tempEntryInfo ->
|
||||
// Retrieve data from registration
|
||||
registerInfo?.let { regInfo ->
|
||||
tempEntryInfo.saveRegisterInfo(database, regInfo)
|
||||
overwrittenData = tempEntryInfo.saveRegisterInfo(database, regInfo)
|
||||
}
|
||||
entryInfo = tempEntryInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
return TemplatesEntry(isTemplate, templates, entryTemplate, entryInfo)
|
||||
return TemplatesEntry(
|
||||
isTemplate,
|
||||
templates,
|
||||
entryTemplate,
|
||||
entryInfo,
|
||||
overwrittenData
|
||||
)
|
||||
}
|
||||
|
||||
fun changeTemplate(template: Template) {
|
||||
@@ -193,7 +215,8 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
|
||||
fun saveEntryInfo(database: ContextualDatabase?, entry: Entry?, parent: Group?, entryInfo: EntryInfo) {
|
||||
IOActionTask(
|
||||
{
|
||||
scope = viewModelScope,
|
||||
action = {
|
||||
removeTempAttachmentsNotCompleted(entryInfo)
|
||||
entry?.let { oldEntry ->
|
||||
// Create a clone
|
||||
@@ -223,7 +246,7 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
EntrySave(oldEntry, newEntry, parent)
|
||||
}
|
||||
},
|
||||
{ entrySave ->
|
||||
onActionComplete = { entrySave ->
|
||||
entrySave?.let {
|
||||
_onEntrySaved.value = it
|
||||
}
|
||||
@@ -315,10 +338,13 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
_onBinaryPreviewLoaded.value = AttachmentPosition(entryAttachmentState, viewPosition)
|
||||
}
|
||||
|
||||
data class TemplatesEntry(val isTemplate: Boolean,
|
||||
val templates: List<Template>,
|
||||
val defaultTemplate: Template,
|
||||
val entryInfo: EntryInfo?)
|
||||
data class TemplatesEntry(
|
||||
val isTemplate: Boolean,
|
||||
val templates: List<Template>,
|
||||
val defaultTemplate: Template,
|
||||
val entryInfo: EntryInfo?,
|
||||
val overwrittenData: Boolean = false
|
||||
)
|
||||
data class EntryUpdate(val database: ContextualDatabase?, val entry: Entry?, val parent: Group?)
|
||||
data class EntrySave(val oldEntry: Entry, val newEntry: Entry, val parent: Group?)
|
||||
data class FieldEdition(val oldField: Field?, val newField: Field?)
|
||||
@@ -326,6 +352,11 @@ 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()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = EntryEditViewModel::class.java.name
|
||||
}
|
||||
|
||||
@@ -389,6 +389,8 @@
|
||||
<string name="warning_keyfile_integrity">The hash of the file is not guaranteed because Android can change its data on the fly. Change the file extension to .bin for correct integrity.</string>
|
||||
<string name="warning_database_notification_permission">The notification permission allows you to display the status of the database and lock it with an easily accessible button.\n\nIf you do not activate this permission, the database open in the background will not be visible if another application is in the foreground.</string>
|
||||
<string name="warning_copy_permission">The notification permission is needed to use the clipboard notification feature.</string>
|
||||
<string name="warning_overwrite_data_title">Overwrite existing data?</string>
|
||||
<string name="warning_overwrite_data_description">This action will replace the existing data in the entry, you can retrieve the old data if the history is enabled.</string>
|
||||
<string name="later">Later</string>
|
||||
<string name="ask">Ask</string>
|
||||
<string name="merge_success">Merge successfully completed</string>
|
||||
|
||||
Reference in New Issue
Block a user