Merge branch 'release/4.2.2'

This commit is contained in:
J-Jamet
2025-10-28 17:44:43 +01:00
43 changed files with 332 additions and 219 deletions

View File

@@ -1,3 +1,9 @@
KeePassDX(4.2.2)
* Fix database merge algorithm #2223
* Fix save search info #2243
* Fix Play Service as privileged app for Passkey Cross Device Authentication #2244
* Small fixes
KeePassDX(4.2.1) KeePassDX(4.2.1)
* Fix Magikeyboard autosearch #2233 * Fix Magikeyboard autosearch #2233
* Fix database merge #2223 * Fix database merge #2223

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 35 targetSdkVersion 35
versionCode = 146 versionCode = 147
versionName = "4.2.1" versionName = "4.2.2"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"

View File

@@ -125,6 +125,8 @@ class EntryActivity : DatabaseLockActivity() {
private var mBackgroundColor: Int? = null private var mBackgroundColor: Int? = null
private var mForegroundColor: Int? = null private var mForegroundColor: Int? = null
override fun manageDatabaseInfo(): Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@@ -157,6 +157,8 @@ class EntryEditActivity : DatabaseLockActivity(),
} }
} }
override fun manageDatabaseInfo(): Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_entry_edit) setContentView(R.layout.activity_entry_edit)

View File

@@ -94,6 +94,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
override fun manageDatabaseInfo(): Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -250,13 +252,13 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
database, database,
false false
) )
coordinatorLayout.showActionErrorIfNeeded(result)
} }
ACTION_DATABASE_LOAD_TASK -> { ACTION_DATABASE_LOAD_TASK -> {
launchGroupActivityIfLoaded(database) launchGroupActivityIfLoaded(database)
} }
} }
} }
coordinatorLayout.showActionErrorIfNeeded(result)
} }
/** /**

View File

@@ -274,6 +274,8 @@ class GroupActivity : DatabaseLockActivity(),
mGroupEditViewModel.selectIcon(icon) mGroupEditViewModel.selectIcon(icon)
} }
override fun manageDatabaseInfo(): Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -871,8 +873,9 @@ class GroupActivity : DatabaseLockActivity(),
entryVersioned, entryVersioned,
searchInfo.toRegisterInfo() searchInfo.toRegisterInfo()
) )
} else {
entrySelectedForKeyboardSelection(database, entryVersioned)
} }
entrySelectedForKeyboardSelection(database, entryVersioned)
} }
TypeMode.PASSKEY -> { TypeMode.PASSKEY -> {
entrySelectedForPasskeySelection(database, entryVersioned) entrySelectedForPasskeySelection(database, entryVersioned)
@@ -887,8 +890,9 @@ class GroupActivity : DatabaseLockActivity(),
entryVersioned, entryVersioned,
searchInfo.toRegisterInfo() searchInfo.toRegisterInfo()
) )
} else {
entrySelectedForAutofillSelection(database, entryVersioned)
} }
entrySelectedForAutofillSelection(database, entryVersioned)
} }
} }
loadGroup() loadGroup()

View File

@@ -78,6 +78,8 @@ class IconPickerActivity : DatabaseLockActivity() {
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
override fun manageDatabaseInfo(): Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@@ -45,6 +45,8 @@ class ImageViewerActivity : DatabaseLockActivity() {
private lateinit var imageView: ImageView private lateinit var imageView: ImageView
private lateinit var progressView: View private lateinit var progressView: View
override fun manageDatabaseInfo(): Boolean = false
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@@ -28,6 +28,8 @@ class KeyGeneratorActivity : DatabaseLockActivity() {
private lateinit var validationButton: View private lateinit var validationButton: View
private var lockView: View? = null private var lockView: View? = null
override fun manageDatabaseInfo(): Boolean = true
private val keyGeneratorViewModel: KeyGeneratorViewModel by viewModels() private val keyGeneratorViewModel: KeyGeneratorViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -124,6 +124,8 @@ class MainCredentialActivity : DatabaseModeActivity() {
private var mReadOnly: Boolean = false private var mReadOnly: Boolean = false
private var mForceReadOnly: Boolean = false private var mForceReadOnly: Boolean = false
override fun manageDatabaseInfo(): Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@@ -18,11 +18,11 @@ import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Co
import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.DatabaseTaskProvider.Companion.startDatabaseService import com.kunzisoft.keepass.database.DatabaseTaskProvider.Companion.startDatabaseService
import com.kunzisoft.keepass.database.ProgressMessage
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
import com.kunzisoft.keepass.tasks.ProgressTaskViewModel
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -32,6 +32,7 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
protected val mDatabase: ContextualDatabase? protected val mDatabase: ContextualDatabase?
get() = mDatabaseViewModel.database get() = mDatabaseViewModel.database
private val progressTaskViewModel: ProgressTaskViewModel by viewModels()
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
@@ -81,13 +82,13 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
) )
} }
is DatabaseViewModel.ActionState.OnDatabaseActionStarted -> { is DatabaseViewModel.ActionState.OnDatabaseActionStarted -> {
showDialog(uiState.progressMessage) progressTaskViewModel.start(uiState.progressMessage)
} }
is DatabaseViewModel.ActionState.OnDatabaseActionUpdated -> { is DatabaseViewModel.ActionState.OnDatabaseActionUpdated -> {
showDialog(uiState.progressMessage) progressTaskViewModel.update(uiState.progressMessage)
} }
is DatabaseViewModel.ActionState.OnDatabaseActionStopped -> { is DatabaseViewModel.ActionState.OnDatabaseActionStopped -> {
// nothing here, wait for the action to finish progressTaskViewModel.stop()
} }
is DatabaseViewModel.ActionState.OnDatabaseActionFinished -> { is DatabaseViewModel.ActionState.OnDatabaseActionFinished -> {
onDatabaseActionFinished( onDatabaseActionFinished(
@@ -95,12 +96,24 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
uiState.actionTask, uiState.actionTask,
uiState.result uiState.result
) )
stopDialog() progressTaskViewModel.stop()
} }
} }
} }
} }
} }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
progressTaskViewModel.progressTaskState.collect { state ->
when (state) {
ProgressTaskViewModel.ProgressTaskState.Start ->
showDialog()
ProgressTaskViewModel.ProgressTaskState.Stop ->
stopDialog()
}
}
}
}
lifecycleScope.launch { lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) { repeatOnLifecycle(Lifecycle.State.RESUMED) {
mDatabaseViewModel.databaseState.collect { database -> mDatabaseViewModel.databaseState.collect { database ->
@@ -194,7 +207,7 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
} }
} }
private fun showDialog(progressMessage: ProgressMessage) { private fun showDialog() {
lifecycleScope.launch { lifecycleScope.launch {
if (showDatabaseDialog()) { if (showDatabaseDialog()) {
if (progressTaskDialogFragment == null) { if (progressTaskDialogFragment == null) {
@@ -208,12 +221,6 @@ abstract class DatabaseActivity : StylishActivity(), DatabaseRetrieval {
PROGRESS_TASK_DIALOG_TAG PROGRESS_TASK_DIALOG_TAG
) )
} }
progressTaskDialogFragment?.apply {
updateTitle(progressMessage.titleId)
updateMessage(progressMessage.messageId)
updateWarning(progressMessage.warningId)
setCancellable(progressMessage.cancelable)
}
} }
} }
} }

View File

@@ -370,9 +370,11 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.lock) { _, _ -> .setPositiveButton(R.string.lock) { _, _ ->
sendBroadcast(Intent(LOCK_ACTION)) sendBroadcast(Intent(LOCK_ACTION))
finish()
}.create().show() }.create().show()
} else { } else {
sendBroadcast(Intent(LOCK_ACTION)) sendBroadcast(Intent(LOCK_ACTION))
finish()
} }
} }

View File

@@ -70,13 +70,11 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
autofillLauncherViewModel.manageRegistrationResult(it) autofillLauncherViewModel.manageRegistrationResult(it)
} }
override fun applyCustomStyle(): Boolean { override fun applyCustomStyle(): Boolean = false
return false
}
override fun finishActivityIfReloadRequested(): Boolean { override fun finishActivityIfReloadRequested(): Boolean = true
return true
} override fun manageDatabaseInfo(): Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// To apply the bypass https://github.com/Kunzisoft/KeePassDX/issues/2238 // To apply the bypass https://github.com/Kunzisoft/KeePassDX/issues/2238

View File

@@ -56,6 +56,8 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
override fun finishActivityIfReloadRequested() = false override fun finishActivityIfReloadRequested() = false
override fun manageDatabaseInfo(): Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
entrySelectionViewModel.initialize() entrySelectionViewModel.initialize()

View File

@@ -121,7 +121,6 @@ class DatabaseTaskProvider(
} }
private fun initServiceConnection() { private fun initServiceConnection() {
actionTaskListener?.onActionStopped()
if (serviceConnection == null) { if (serviceConnection == null) {
serviceConnection = object : ServiceConnection { serviceConnection = object : ServiceConnection {
override fun onBindingDied(name: ComponentName?) { override fun onBindingDied(name: ComponentName?) {

View File

@@ -4,7 +4,7 @@ import androidx.annotation.StringRes
data class ProgressMessage( data class ProgressMessage(
@StringRes @StringRes
var titleId: Int, var titleId: Int? = null,
@StringRes @StringRes
var messageId: Int? = null, var messageId: Int? = null,
@StringRes @StringRes

View File

@@ -165,7 +165,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
} }
} }
if (attachmentNotificationList.isEmpty()) { if (attachmentNotificationList.isEmpty()) {
stopSelf() stopService()
} }
} }
} }

View File

@@ -62,7 +62,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
sendBroadcast(Intent(LOCK_ACTION)) sendBroadcast(Intent(LOCK_ACTION))
} }
// Stop the service // Stop the service
stopSelf() stopService()
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

View File

@@ -262,11 +262,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
) )
} }
} else { } else {
/* Do not stopped here, service cannot be connected
mActionTaskListeners.forEach { actionTaskListener -> mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onActionStopped( actionTaskListener.onActionStopped(
database database
) )
} }*/
} }
} }
} }
@@ -338,7 +339,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
val intentAction = intent?.action val intentAction = intent?.action
if (intentAction == null && !database.loaded) { if (intentAction == null && !database.loaded) {
stopSelf() stopService()
} }
val actionRunnable: ActionRunnable? = when (intentAction) { val actionRunnable: ActionRunnable? = when (intentAction) {
@@ -447,10 +448,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
TimeoutHelper.releaseTemporarilyDisableTimeout() TimeoutHelper.releaseTemporarilyDisableTimeout()
// Stop service after save if user remove task // Stop service after save if user remove task
if (save && mTaskRemovedRequested) { if (save && mTaskRemovedRequested) {
actionOnLock() stopService()
} else if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) { } else if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
if (!database.loaded) { if (!database.loaded) {
stopSelf() stopService()
} else { } else {
// Restart the service to open lock notification // Restart the service to open lock notification
try { try {
@@ -535,11 +536,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
val notificationBuilder = buildNewNotification().apply { val notificationBuilder = buildNewNotification().apply {
setSmallIcon(iconId) setSmallIcon(iconId)
intent?.let { val titleId = mProgressMessage.titleId?.let {
setContentTitle(getString( intent?.getIntExtra(DATABASE_TASK_TITLE_KEY, it)
intent.getIntExtra(DATABASE_TASK_TITLE_KEY, mProgressMessage.titleId)) } ?: R.string.app_name
) setContentTitle(getString(titleId))
}
setAutoCancel(false) setAutoCancel(false)
setContentIntent(null) setContentIntent(null)
} }
@@ -673,7 +673,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
updateMessage(R.string.decrypting_db) updateMessage(R.string.decrypting_db)
} }
override fun actionOnLock() { override fun stopService() {
if (!TimeoutHelper.temporarilyDisableLock) { if (!TimeoutHelper.temporarilyDisableLock) {
closeDatabase(mDatabase) closeDatabase(mDatabase)
// Remove the database during the lock // Remove the database during the lock
@@ -685,7 +685,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
// Remove the lock timer (no more needed if it exists) // Remove the lock timer (no more needed if it exists)
TimeoutHelper.cancelLockTimer(this) TimeoutHelper.cancelLockTimer(this)
// Service is stopped after receive the broadcast // Service is stopped after receive the broadcast
super.actionOnLock() super.stopService()
} }
} }
@@ -729,7 +729,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
// Close channels // Close channels
closeChallengeResponse() closeChallengeResponse()
// Restore previous message // Restore previous message
mProgressMessage = previousMessage mProgressMessage = previousMessage.apply {
cancelable = null
}
notifyProgressMessage() notifyProgressMessage()
} }
return response return response

View File

@@ -55,7 +55,7 @@ class KeyboardEntryNotificationService : LockNotificationService() {
sendBroadcast(Intent(LOCK_ACTION)) sendBroadcast(Intent(LOCK_ACTION))
} }
// Stop the service // Stop the service
stopSelf() stopService()
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.services package com.kunzisoft.keepass.services
import android.content.Intent import android.content.Intent
import androidx.core.app.ServiceCompat
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.LockReceiver import com.kunzisoft.keepass.utils.LockReceiver
import com.kunzisoft.keepass.utils.registerLockReceiver import com.kunzisoft.keepass.utils.registerLockReceiver
@@ -29,13 +28,7 @@ import com.kunzisoft.keepass.utils.unregisterLockReceiver
abstract class LockNotificationService : NotificationService() { abstract class LockNotificationService : NotificationService() {
private var mLockReceiver: LockReceiver = LockReceiver { private var mLockReceiver: LockReceiver = LockReceiver {
actionOnLock() stopService()
}
protected open fun actionOnLock() {
// Stop the service in all cases
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
} }
override fun onCreate() { override fun onCreate() {
@@ -46,7 +39,7 @@ abstract class LockNotificationService : NotificationService() {
override fun onTaskRemoved(rootIntent: Intent?) { override fun onTaskRemoved(rootIntent: Intent?) {
if (!TimeoutHelper.temporarilyDisableLock) { if (!TimeoutHelper.temporarilyDisableLock) {
actionOnLock() stopService()
} }
super.onTaskRemoved(rootIntent) super.onTaskRemoved(rootIntent)
} }

View File

@@ -17,6 +17,7 @@ import android.util.TypedValue
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.Stylish import com.kunzisoft.keepass.activities.stylish.Stylish
@@ -114,6 +115,12 @@ abstract class NotificationService : Service() {
} }
} }
protected open fun stopService() {
// Stop the service in all cases
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
stopSelf()
}
protected fun defineTimerJob(builder: NotificationCompat.Builder, protected fun defineTimerJob(builder: NotificationCompat.Builder,
type: NotificationServiceType, type: NotificationServiceType,
timeoutMilliseconds: Long, timeoutMilliseconds: Long,

View File

@@ -13,6 +13,8 @@ abstract class ExternalSettingsActivity : DatabaseModeActivity() {
private var lockView: FloatingActionButton? = null private var lockView: FloatingActionButton? = null
override fun manageDatabaseInfo(): Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@@ -27,32 +27,27 @@ import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
open class ProgressTaskDialogFragment : DialogFragment() { open class ProgressTaskDialogFragment : DialogFragment() {
@StringRes
private var title = UNDEFINED
@StringRes
private var message = UNDEFINED
@StringRes
private var warning = UNDEFINED
private var cancellable: (() -> Unit)? = null
private var titleView: TextView? = null private var titleView: TextView? = null
private var messageView: TextView? = null private var messageView: TextView? = null
private var warningView: TextView? = null private var warningView: TextView? = null
private var cancelButton: Button? = null private var cancelButton: Button? = null
private var progressView: ProgressBar? = null private var progressView: ProgressBar? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { private val progressTaskViewModel: ProgressTaskViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
try { try {
activity?.let { activity?.let {
val builder = AlertDialog.Builder(it) val builder = AlertDialog.Builder(it)
@@ -71,68 +66,44 @@ open class ProgressTaskDialogFragment : DialogFragment() {
cancelButton = root.findViewById(R.id.progress_dialog_cancel) cancelButton = root.findViewById(R.id.progress_dialog_cancel)
progressView = root.findViewById(R.id.progress_dialog_bar) progressView = root.findViewById(R.id.progress_dialog_bar)
updateTitle(title)
updateMessage(message)
updateWarning(warning)
setCancellable(cancellable)
isCancelable = false isCancelable = false
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
progressTaskViewModel.progressMessageState.collect { state ->
updateView(titleView,
state.titleId?.let { title -> getString(title) })
updateView(messageView,
state.messageId?.let { message -> getString(message) })
updateView(warningView,
state.warningId?.let { warning -> getString(warning) })
cancelButton?.isVisible = state.cancelable != null
cancelButton?.setOnClickListener {
state.cancelable?.invoke()
}
}
}
}
return builder.create() return builder.create()
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to create progress dialog") Log.e(TAG, "Unable to create progress dialog", e)
} }
return super.onCreateDialog(savedInstanceState) return super.onCreateDialog(savedInstanceState)
} }
fun setTitle(@StringRes titleId: Int) { private fun updateView(textView: TextView?, value: String?) {
this.title = titleId if (value == null) {
} textView?.visibility = View.GONE
} else {
private fun updateView(textView: TextView?, @StringRes resId: Int) { textView?.text = value
activity?.lifecycleScope?.launch { textView?.visibility = View.VISIBLE
if (resId == UNDEFINED) {
textView?.visibility = View.GONE
} else {
textView?.setText(resId)
textView?.visibility = View.VISIBLE
}
} }
} }
private fun updateCancelable() {
activity?.lifecycleScope?.launch {
cancelButton?.isVisible = cancellable != null
cancelButton?.setOnClickListener {
cancellable?.invoke()
}
}
}
fun updateTitle(@StringRes resId: Int?) {
this.title = resId ?: UNDEFINED
updateView(titleView, title)
}
fun updateMessage(@StringRes resId: Int?) {
this.message = resId ?: UNDEFINED
updateView(messageView, message)
}
fun updateWarning(@StringRes resId: Int?) {
this.warning = resId ?: UNDEFINED
updateView(warningView, warning)
}
fun setCancellable(cancellable: (() -> Unit)?) {
this.cancellable = cancellable
updateCancelable()
}
companion object { companion object {
private val TAG = ProgressTaskDialogFragment::class.java.simpleName private val TAG = ProgressTaskDialogFragment::class.java.simpleName
const val PROGRESS_TASK_DIALOG_TAG = "progressDialogFragment" const val PROGRESS_TASK_DIALOG_TAG = "progressDialogFragment"
const val UNDEFINED = -1
} }
} }

View File

@@ -0,0 +1,33 @@
package com.kunzisoft.keepass.tasks
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.database.ProgressMessage
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class ProgressTaskViewModel: ViewModel() {
private val mProgressMessageState = MutableStateFlow(ProgressMessage())
val progressMessageState: StateFlow<ProgressMessage> = mProgressMessageState
private val mProgressTaskState = MutableStateFlow<ProgressTaskState>(ProgressTaskState.Stop)
val progressTaskState: StateFlow<ProgressTaskState> = mProgressTaskState
fun update(value: ProgressMessage) {
mProgressMessageState.value = value
}
fun start(value: ProgressMessage) {
mProgressTaskState.value = ProgressTaskState.Start
update(value)
}
fun stop() {
mProgressTaskState.value = ProgressTaskState.Stop
}
sealed class ProgressTaskState {
object Start: ProgressTaskState()
object Stop: ProgressTaskState()
}
}

View File

@@ -98,25 +98,43 @@ object AppUtil {
} }
val processedPackageNames = mutableSetOf<String>() val processedPackageNames = mutableSetOf<String>()
for (resolveInfo in resolveInfoList) { for (resolveInfo in resolveInfoList) {
val packageName = resolveInfo.activityInfo.packageName val packageName = resolveInfo.activityInfo.packageName
if (packageName != null && !processedPackageNames.contains(packageName)) { if (packageName != null && !processedPackageNames.contains(packageName)) {
try { buildAndroidPrivilegedApp(packageManager, packageName)?.let { privilegedApp ->
val packageInfo = packageManager.getPackageInfo( browserList.add(privilegedApp)
packageName, processedPackageNames.add(packageName)
PackageManager.GET_SIGNING_CERTIFICATES
)
val signatureFingerprints = packageInfo.signingInfo?.getAllFingerprints()
signatureFingerprints?.let {
browserList.add(AndroidPrivilegedApp(packageName, signatureFingerprints))
processedPackageNames.add(packageName)
}
} catch (e: Exception) {
Log.e(AppUtil::class.simpleName, "Error processing package: $packageName", e)
} }
} }
} }
// Add the Play Service
val gServices = "com.google.android.gms"
buildAndroidPrivilegedApp(packageManager, gServices)?.let { privilegedApp ->
browserList.add(privilegedApp)
processedPackageNames.add(gServices)
}
return browserList.distinctBy { it.packageName } // Ensure uniqueness just in case return browserList.distinctBy { it.packageName } // Ensure uniqueness just in case
} }
@RequiresApi(Build.VERSION_CODES.P)
private fun buildAndroidPrivilegedApp(
packageManager: PackageManager,
packageName: String
): AndroidPrivilegedApp? {
return try {
val packageInfo = packageManager.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES
)
val signatureFingerprints = packageInfo.signingInfo?.getAllFingerprints()
signatureFingerprints?.let {
AndroidPrivilegedApp(packageName, signatureFingerprints)
}
} catch (e: Exception) {
Log.e(AppUtil::class.simpleName, "Error processing package: $packageName", e)
null
}
}
} }

View File

@@ -666,4 +666,41 @@
<string name="passkeys_close_database_title">Sulge andmebaas</string> <string name="passkeys_close_database_title">Sulge andmebaas</string>
<string name="passkeys_privileged_apps_ask_title">Rakendust ei õnnestu tuvastada</string> <string name="passkeys_privileged_apps_ask_title">Rakendust ei õnnestu tuvastada</string>
<string name="warning_overwrite_data_title">Kas soovid olemasolevad andmed üle kirjutada?</string> <string name="warning_overwrite_data_title">Kas soovid olemasolevad andmed üle kirjutada?</string>
<string name="warning_overwrite_data_description">Selle toiminguga asendad kirje olemasolevad andmed, aga kui ajaloo logimine on kasutusel, siis saad vanu kirjeid ka hiljem näha.</string>
<string name="credential_provider">kasutajanime/salasõna automaattäite teenus</string>
<string name="passkeys">WebAuthn pääsuvõtmed</string>
<string name="passkeys_explanation_summary">Seadista WebAuthn pääsuvõtmed, mis võimaldavad kiiret ja turvalist salasõnadeta ligipääsu</string>
<string name="passkeys_preference_title">Pääsuvõtmete seadistused</string>
<string name="passkeys_close_database_summary">Peale pääsuvõtme valimist sulge andmebaas</string>
<string name="passkeys_privileged_apps_title">Eesõigustega rakendused</string>
<string name="passkeys_privileged_apps_summary">Halda brausereid sinu loodud eesõigustega rakenduste loendis</string>
<string name="passkeys_privileged_apps_explanation">HOIATUS: Eesõigustega rakendus toimib lüüsina autentimise algallikaga suhtlemisel. Turvaprobleemide vältimiseks palun taga, et kasutad õiget rakendust.</string>
<string name="passkeys_privileged_apps_ask_message">%1$s kaset pääsuvõtmega autentimiseks.\n\nKas lisame ta eesõigustega rakenduste loendisse?</string>
<string name="passkeys_missing_signature_app_ask_title">Allkiri on puudu</string>
<string name="passkeys_missing_signature_app_ask_explanation">HOAITUS: See pääsuluba on loodud teise kliendi poolt või on allkiri kustutatud. Turvaprobleemide vältimiseks palun kontrolli rakendus, milles soovi end tuvastada on sama teenuse osa ning sisuliselt õige.\n\nKui rakendus on veebibrauser, siis ära lisa allkirja kirje juurde vaid seadistustes leiduva eesõigustega rakenduste loendisse.</string>
<string name="passkeys_missing_signature_app_ask_message">%1$s on antud kontekstis tundmatu ja mittetunnustatud ning ta proovib autentimist olemasoleva pääsuvõtmega.</string>
<string name="passkeys_missing_signature_app_ask_question">Kas soovid lisada rakenduse allkirja pääsuvõtme kirjele?</string>
<string name="passkeys_auto_select_title">Automaatne valik</string>
<string name="passkeys_auto_select_summary">Vali automaatselt vaid siis, kui vaid üks kirje ja andmebaas on avatud ning vaid siis, kui rakendus ühildub</string>
<string name="passkeys_backup_eligibility_title">Varunduse kõlblikkus</string>
<string name="passkeys_backup_eligibility_summary">Otsusta loomise hetkel, kas avaliku võtme allikat on lubatud varundada</string>
<string name="passkeys_backup_state_title">Varunduse olek</string>
<string name="passkeys_backup_state_summary">Anna märku, kas autentimisandmed on varundatud ja kaitstud üksiku seadme kaotsimineku puhul</string>
<string name="credential_provider_service_subtitle">Pääsuvõtmed - automaattäite teenusepakkuja</string>
<string name="passkey">WebAuthn pääsuvõti</string>
<string name="passkey_service_name">KeePassDX autentimisteenusepakkuja</string>
<string name="passkey_creation_description">Salvesta pääsuvõti uue kirjena</string>
<string name="passkey_update_description">Uuenda pääsuvõtit: %1$s</string>
<string name="passkey_selection_username">Pääsuvõtit ei leidu</string>
<string name="passkey_selection_description">Vali olemasolev pääsuvõti</string>
<string name="passkey_database_username">KeePassDX-i andmebaas</string>
<string name="passkey_locked_database_description">Vali lukustuse eemaldamiseks</string>
<string name="passkey_username">Pääsuvõtme kasutajanimi</string>
<string name="passkey_private_key">Pääsuvõtme privaatvõti</string>
<string name="passkey_credential_id">Pääsuvõtme autentimisühiku tunnus</string>
<string name="passkey_user_handle">Pääsuvõtme kasutaja võrgutunnus</string>
<string name="passkey_relying_party">Pääsuvõtit edastav osapool</string>
<string name="passkey_backup_eligibility">Pääsuvõtme kõlblikkus varundamiseks</string>
<string name="passkey_backup_state">Pääsuvõtme varunduse olek</string>
<string name="error_passkey_result">Pääsuvõtme väljastamine vastuseks ei õnnestu</string>
</resources> </resources>

View File

@@ -19,7 +19,7 @@
--><resources> --><resources>
<string name="feedback">피드백</string> <string name="feedback">피드백</string>
<string name="homepage">홈페이지</string> <string name="homepage">홈페이지</string>
<string name="about_description">KeePass 암호 관리자의 Android 구현</string> <string name="about_description">KeePass 암호 관리자의 Android 구현체.</string>
<string name="accept">확인</string> <string name="accept">확인</string>
<string name="add_entry">항목 추가</string> <string name="add_entry">항목 추가</string>
<string name="edit_entry">항목 수정</string> <string name="edit_entry">항목 수정</string>
@@ -40,14 +40,13 @@
<string name="clipboard_error_clear">클립보드를 비울 수 없음</string> <string name="clipboard_error_clear">클립보드를 비울 수 없음</string>
<string name="clipboard_timeout">클립보드 시간 초과</string> <string name="clipboard_timeout">클립보드 시간 초과</string>
<string name="clipboard_timeout_summary">클립보드 저장이 유지될 시간 (장치가 지원한다면)</string> <string name="clipboard_timeout_summary">클립보드 저장이 유지될 시간 (장치가 지원한다면)</string>
<string name="select_to_copy">%1$s 을(를) 클립보드에 복사하려면 선택하십시오.</string> <string name="select_to_copy">%1$s 을(를) 클립보드에 복사하</string>
<string name="retrieving_db_key">데이터베이스 키를 검색하는 중…</string> <string name="retrieving_db_key">데이터베이스 키를 검색하는 중…</string>
<string name="database">데이터베이스</string> <string name="database">데이터베이스</string>
<string name="decrypting_db">데이터베이스 컨텐츠 암호 해독 중…</string> <string name="decrypting_db">데이터베이스 컨텐츠 암호 해독 중…</string>
<string name="default_checkbox">기본 데이터베이스로 사용</string> <string name="default_checkbox">기본 데이터베이스로 사용</string>
<string name="digits">단위</string> <string name="digits">단위</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft는 보증이 적용되지 않습니다; 이것은 자유 소프트웨어이며, 광고가 없습니다. <string name="html_about_licence">KeePassDX © %1$d Kunzisoft는 보증이 적용되지 않습니다; 이것은 &lt;strong&gt;자유 소프트웨어&lt;/strong&gt;이며, &lt;strong&gt;광고가 없습니다&lt;/strong&gt;. \n이 것은 보증 없이 있는 그대로, &lt;strong&gt;GPL 버전 3&lt;/strong&gt; 라이선스로 제공됩니다.</string>
\n이 것은 보증 없이 있는 그대로, GPL 버전 3하에 제공됩니다.</string>
<string name="entry_accessed">접근됨</string> <string name="entry_accessed">접근됨</string>
<string name="entry_cancel">취소</string> <string name="entry_cancel">취소</string>
<string name="entry_notes">노트</string> <string name="entry_notes">노트</string>
@@ -81,22 +80,22 @@
<string name="field_name">필드 이름</string> <string name="field_name">필드 이름</string>
<string name="field_value">필드 값</string> <string name="field_value">필드 값</string>
<string name="file_not_found_content">파일을 찾을 수 없습니다. 파일 탐색기에서 열리는지 확인해 주세요.</string> <string name="file_not_found_content">파일을 찾을 수 없습니다. 파일 탐색기에서 열리는지 확인해 주세요.</string>
<string name="file_browser">파일 탐색기</string> <string name="file_browser">파일 관리자</string>
<string name="generate_password">비밀번호 생성</string> <string name="generate_password">비밀번호 생성</string>
<string name="hint_conf_pass">비밀번호 확인</string> <string name="hint_conf_pass">패스워드 확인</string>
<string name="hint_generated_password">생성된 비밀번호</string> <string name="hint_generated_password">생성된 패스워드</string>
<string name="hint_group_name">그룹 이름</string> <string name="hint_group_name">그룹 이름</string>
<string name="hint_keyfile">키 파일</string> <string name="hint_keyfile">키 파일</string>
<string name="hint_length">길이</string> <string name="hint_length">길이</string>
<string name="hint_pass">비밀번호</string> <string name="hint_pass">패스워드</string>
<string name="password">비밀번호</string> <string name="password">비밀번호</string>
<string name="invalid_credentials">비밀번호나 키 파일을 읽을 수 없습니다.</string> <string name="invalid_credentials">패스워드나 키 파일을 읽을 수 없습니다.</string>
<string name="invalid_algorithm">잘못된 알고리즘입니다.</string> <string name="invalid_algorithm">잘못된 알고리즘입니다.</string>
<string name="invalid_db_sig">데이터베이스 형식을 인식할 수 없습니다.</string> <string name="invalid_db_sig">데이터베이스 형식을 인식할 수 없습니다.</string>
<string name="keyfile_is_empty">이 키 파일은 비어 있습니다.</string> <string name="keyfile_is_empty">이 키 파일은 비어 있습니다.</string>
<string name="length">길이</string> <string name="length">길이</string>
<string name="list_entries_show_username_title">아이디 보이기</string> <string name="list_entries_show_username_title">아이디 보이기</string>
<string name="list_entries_show_username_summary">엔트리 목록에 아이디 보이기</string> <string name="list_entries_show_username_summary">항목 리스트에 사용자 이름을 표시합니다</string>
<string name="list_size_title">리스트 항목 크기</string> <string name="list_size_title">리스트 항목 크기</string>
<string name="list_size_summary">요소 목록 텍스트 크기</string> <string name="list_size_summary">요소 목록 텍스트 크기</string>
<string name="loading_database">데이터베이스 로딩 중…</string> <string name="loading_database">데이터베이스 로딩 중…</string>
@@ -105,7 +104,7 @@
<string name="hide_password_summary">기본 비밀번호를 (***) 로 가리기</string> <string name="hide_password_summary">기본 비밀번호를 (***) 로 가리기</string>
<string name="about">정보</string> <string name="about">정보</string>
<string name="menu_change_key_settings">마스터 키 바꾸기</string> <string name="menu_change_key_settings">마스터 키 바꾸기</string>
<string name="copy_field">%1$s 복사됨</string> <string name="copy_field">%1$s의 사본</string>
<string name="settings">설정</string> <string name="settings">설정</string>
<string name="menu_app_settings">앱 설정</string> <string name="menu_app_settings">앱 설정</string>
<string name="menu_form_filling_settings">폼 채우기</string> <string name="menu_form_filling_settings">폼 채우기</string>
@@ -129,8 +128,8 @@
<string name="never">절대 하지 않음</string> <string name="never">절대 하지 않음</string>
<string name="no_results">검색 결과가 없음</string> <string name="no_results">검색 결과가 없음</string>
<string name="no_url_handler">이 URL을 열기 위해 웹 브라우저를 설치하십시오.</string> <string name="no_url_handler">이 URL을 열기 위해 웹 브라우저를 설치하십시오.</string>
<string name="select_database_file">가지고 있는 데이터베이스 열기</string> <string name="select_database_file">기존 저장소 열기</string>
<string name="create_keepass_file">데이터베이스 생성</string> <string name="create_keepass_file">저장소 생성</string>
<string name="progress_create">새 데이터베이스 생성 중…</string> <string name="progress_create">새 데이터베이스 생성 중…</string>
<string name="progress_title">작업 중…</string> <string name="progress_title">작업 중…</string>
<string name="protection">보호</string> <string name="protection">보호</string>
@@ -138,7 +137,7 @@
<string name="read_only_warning">KeePassDX는 데이터베이스를 수정하기 위해 쓰기 권한이 필요합니다.</string> <string name="read_only_warning">KeePassDX는 데이터베이스를 수정하기 위해 쓰기 권한이 필요합니다.</string>
<string name="content_description_remove_from_list">삭제</string> <string name="content_description_remove_from_list">삭제</string>
<string name="root">루트</string> <string name="root">루트</string>
<string name="encryption_explanation">데이터베이스 암호화 알고리즘이 모든 데이터에 적용됩니다.</string> <string name="encryption_explanation">데이터베이스 암호화 알고리즘이 모든 데이터에 적용됩니다</string>
<string name="memory_usage">메모리 사용량</string> <string name="memory_usage">메모리 사용량</string>
<string name="memory_usage_explanation">바이너리 바이트 단위의 메모리 용량이 키 파생 기능에 사용됩니다.</string> <string name="memory_usage_explanation">바이너리 바이트 단위의 메모리 용량이 키 파생 기능에 사용됩니다.</string>
<string name="saving_database">데이터베이스 저장 중…</string> <string name="saving_database">데이터베이스 저장 중…</string>
@@ -250,10 +249,10 @@
<string name="error_field_name_already_exists">그 필드 이름은 이미 존재합니다.</string> <string name="error_field_name_already_exists">그 필드 이름은 이미 존재합니다.</string>
<string name="inherited">상속</string> <string name="inherited">상속</string>
<string name="auto_type">자동입력</string> <string name="auto_type">자동입력</string>
<string name="warning_database_already_opened">데이터베이스가 이미 열려있습니다. 새 것을 열려면 지금 것을 먼저 닫아주세요.</string> <string name="warning_database_already_opened">데이터베이스가 이미 열려있습니다. 새 것을 열려면 지금 것을 먼저 닫아주세요</string>
<string name="hide_broken_locations_summary">최근 데이터베이스 목록에서 파손된 링크를 감춤</string> <string name="hide_broken_locations_summary">최근 데이터베이스 목록에서 파손된 링크를 감춤</string>
<string name="import_app_properties_summary">앱 속성을 내보낼 파일 선택</string> <string name="import_app_properties_summary">가져올 앱 설정 파일 선택</string>
<string name="success_import_app_properties">속성을 가져왔습니다.</string> <string name="success_import_app_properties">설정 가져옴</string>
<string name="kdf_explanation">암호화 알고리즘 용 키를 생성하기 위해, 마스터키는 임의의 솔트(salt) 키 파생 함수를 사용하여 변환됩니다.</string> <string name="kdf_explanation">암호화 알고리즘 용 키를 생성하기 위해, 마스터키는 임의의 솔트(salt) 키 파생 함수를 사용하여 변환됩니다.</string>
<string name="warning">경고</string> <string name="warning">경고</string>
<string name="error_challenge_already_requested">이미 요청된 시도입니다.</string> <string name="error_challenge_already_requested">이미 요청된 시도입니다.</string>
@@ -262,14 +261,14 @@
<string name="error_response_already_provided">이미 응답했습니다.</string> <string name="error_response_already_provided">이미 응답했습니다.</string>
<string name="error_location_unknown">데이터 베이스 위치를 알 수 없어 데이터 베이스 액션을 수행할 수 없습니다.</string> <string name="error_location_unknown">데이터 베이스 위치를 알 수 없어 데이터 베이스 액션을 수행할 수 없습니다.</string>
<string name="error_driver_required">%1$s 를 위한 드라이버가 요구됩니다.</string> <string name="error_driver_required">%1$s 를 위한 드라이버가 요구됩니다.</string>
<string name="error_unable_merge_database_kdb">데이터베이스 V1로부터 병합할 수 없습니다.</string> <string name="error_unable_merge_database_kdb">kdb 데이터베이스 파일과 병합할 수 없습니다.</string>
<string name="error_hardware_key_unsupported">하드웨어 키는 지원하지 않습니다.</string> <string name="error_hardware_key_unsupported">하드웨어 키는 지원하지 않습니다.</string>
<string name="error_empty_key">키는 반드시 입력해야 합니다.</string> <string name="error_empty_key">키는 반드시 입력해야 합니다.</string>
<string name="hint_icon_name">아이콘명</string> <string name="hint_icon_name">아이콘명</string>
<string name="passphrase">암호문</string> <string name="passphrase">암호문</string>
<string name="colorize_password_title">비밀번호에 색상 부여</string> <string name="colorize_password_title">비밀번호에 색상 부여</string>
<string name="colorize_password_summary">타입에 따라 비밀번호 문자에 색상을 부여</string> <string name="colorize_password_summary">타입에 따라 비밀번호 문자에 색상을 부여</string>
<string name="export_app_properties_title">속성을 내보내기</string> <string name="export_app_properties_title">설정 내보내기</string>
<string name="uppercase">대문자</string> <string name="uppercase">대문자</string>
<string name="menu_security_settings">보안 설정</string> <string name="menu_security_settings">보안 설정</string>
<string name="menu_reload_database">데이터 다시 읽기</string> <string name="menu_reload_database">데이터 다시 읽기</string>
@@ -280,7 +279,7 @@
<string name="save_mode">저장 모드</string> <string name="save_mode">저장 모드</string>
<string name="selection_mode">선택 모드</string> <string name="selection_mode">선택 모드</string>
<string name="remember_hardware_key_title">하드웨어 키를 기억</string> <string name="remember_hardware_key_title">하드웨어 키를 기억</string>
<string name="export_app_properties_summary">속성을 내보 파일 생성</string> <string name="export_app_properties_summary">설정을 내보내기 위해 파일 생성</string>
<string name="error_import_app_properties">앱 속성을 가져오던 중 오류 발생.</string> <string name="error_import_app_properties">앱 속성을 가져오던 중 오류 발생.</string>
<string name="contains_duplicate_uuid">데이터베이스가 중복된 UUID를 포함하고 있습니다.</string> <string name="contains_duplicate_uuid">데이터베이스가 중복된 UUID를 포함하고 있습니다.</string>
<string name="remember_database_locations_summary">데이터 베이스가 저장된 곳을 추적</string> <string name="remember_database_locations_summary">데이터 베이스가 저장된 곳을 추적</string>
@@ -311,7 +310,7 @@
<string name="menu_restore_entry_history">이력을 복구</string> <string name="menu_restore_entry_history">이력을 복구</string>
<string name="subdomain_search_summary">보조 도메인 제한하에 웹 도메인을 검색</string> <string name="subdomain_search_summary">보조 도메인 제한하에 웹 도메인을 검색</string>
<string name="contains_duplicate_uuid_procedure">중복에 대해 새로운 UUID를 생성하여 문제를 해결하고 진행할까요\?</string> <string name="contains_duplicate_uuid_procedure">중복에 대해 새로운 UUID를 생성하여 문제를 해결하고 진행할까요\?</string>
<string name="success_export_app_properties">속성을 내보냈습니다.</string> <string name="success_export_app_properties">설정 내보냄</string>
<string name="parallelism">병렬 처리</string> <string name="parallelism">병렬 처리</string>
<string name="command_execution">명령 실행중…</string> <string name="command_execution">명령 실행중…</string>
<string name="menu_master_key_settings">마스터 키 설정</string> <string name="menu_master_key_settings">마스터 키 설정</string>
@@ -321,14 +320,14 @@
<string name="menu_device_unlock_settings">고급 잠금 해제</string> <string name="menu_device_unlock_settings">고급 잠금 해제</string>
<string name="search_mode">검색 모드</string> <string name="search_mode">검색 모드</string>
<string name="rounds_explanation">추가적인 암호화 차수를 설정함으로써 무차별 대입 공격(brute force attack)에 대한 방어를 강화할 수 있습니다. 대신 읽기/저장시 느려질 수 있습니다.</string> <string name="rounds_explanation">추가적인 암호화 차수를 설정함으로써 무차별 대입 공격(brute force attack)에 대한 방어를 강화할 수 있습니다. 대신 읽기/저장시 느려질 수 있습니다.</string>
<string name="do_not_kill_app">앱을 강제 종료하지 마세요.</string> <string name="do_not_kill_app">앱을 강제 종료하지 마세요</string>
<string name="sort_recycle_bin_bottom">휴지통을 바닥에</string> <string name="sort_recycle_bin_bottom">휴지통을 바닥에</string>
<string name="warning_database_link_revoked">파일에 대한 접근이 파일 관리자에 의해 철회되었습니다.</string> <string name="warning_database_link_revoked">파일에 대한 접근이 파일 관리자에 의해 철회되었습니다</string>
<string name="hide_broken_locations_title">파손된 데이터페이스 링크를 감춤</string> <string name="hide_broken_locations_title">파손된 데이터페이스 링크를 감춤</string>
<string name="import_app_properties_title">속성을 가져오기</string> <string name="import_app_properties_title">설정 가져오기</string>
<string name="error_export_app_properties">앱 속성을 내보내던 중 오류 발생.</string> <string name="error_export_app_properties">앱 속성을 내보내던 중 오류 발생.</string>
<string name="rounds">변환 차수</string> <string name="rounds">변환 차수</string>
<string name="parallelism_explanation">키 파생 함수에 사용되는 병렬 처리 수준 (즉, 스레드의 갯수)</string> <string name="parallelism_explanation">키 파생 함수에 사용되는 병렬 처리 수준(즉, 스레드의 갯수).</string>
<string name="html_about_privacy">&lt;strong&gt;사용자 데이터를 받아오지 않습니다.&lt;/strong&gt; 어떤 서버에도 연결하지 않고 로컬로만 동작하며 사용자의 사생활(프라이버시)를 최우선시합니다.</string> <string name="html_about_privacy">&lt;strong&gt;사용자 데이터를 받아오지 않습니다.&lt;/strong&gt; 어떤 서버에도 연결하지 않고 로컬로만 동작하며 사용자의 사생활(프라이버시)를 최우선시합니다.</string>
<string name="error_rebuild_list">목록 재구축을 알맞게 할 수 없습니다.</string> <string name="error_rebuild_list">목록 재구축을 알맞게 할 수 없습니다.</string>
<string name="error_file_to_big">업로드하려는 파일이 너무 큽니다.</string> <string name="error_file_to_big">업로드하려는 파일이 너무 큽니다.</string>
@@ -339,50 +338,50 @@
<string name="show_otp_token_summary">항목 목록에서 OTP 토큰을 표시</string> <string name="show_otp_token_summary">항목 목록에서 OTP 토큰을 표시</string>
<string name="show_uuid_title">UUID를 표시</string> <string name="show_uuid_title">UUID를 표시</string>
<string name="show_uuid_summary">항목이나 그룹에 연결된 UUID를 표시</string> <string name="show_uuid_summary">항목이나 그룹에 연결된 UUID를 표시</string>
<string name="creating_database">데이터 베이스 생성</string> <string name="creating_database">데이터베이스 생성중…</string>
<string name="menu_merge_database">데이터 병합</string> <string name="menu_merge_database">데이터 병합</string>
<string name="menu_merge_from">... 로부터 병합</string> <string name="menu_merge_from">로부터 병합</string>
<string name="menu_save_copy_to">... 에 복사본 저장</string> <string name="menu_save_copy_to">에 복사본 저장</string>
<string name="menu_keystore_remove_key">고급 잠금 해제 키 삭제</string> <string name="menu_keystore_remove_key">디바이스 잠금 해제 키 삭제</string>
<string name="sort_last_access_time">접근</string> <string name="sort_last_access_time">접근</string>
<string name="invalid_db_same_uuid">%1$s 와 동일한 UUID %2$s 가 이미 존재합니다.</string> <string name="invalid_db_same_uuid">%1$s 와 동일한 UUID %2$s 가 이미 존재합니다.</string>
<string name="underline">밑줄</string> <string name="underline">밑줄</string>
<string name="unsupported_db_version">지원하지 않는 데이터베이스 버전입니다.</string> <string name="unsupported_db_version">지원하지 않는 데이터베이스 버전입니다.</string>
<string name="permission">허용</string> <string name="permission">권한</string>
<string name="menu_appearance_settings">테마</string> <string name="menu_appearance_settings">테마</string>
<string name="biometric">생체 인증</string> <string name="biometric">생체 인증</string>
<string name="device_credential">기기 자격 증명</string> <string name="device_credential">기기 자격 증명</string>
<string name="generate_keyfile">키 파일 생성하기</string> <string name="generate_keyfile">키 파일 생성하기</string>
<string name="menu_form_filling_settings_summary">키보드, 자동 완성, 클립보드</string> <string name="menu_form_filling_settings_summary">키보드, 자동 완성, 클립보드</string>
<string name="menu_app_settings_summary">검색, 잠금, 기록, 속성</string> <string name="menu_app_settings_summary">검색, 잠금, 기록, 속성</string>
<string name="merge_success">병합 성공습니다</string> <string name="merge_success">병합 성공적으로 완료되었습니다</string>
<string name="version_label">버전 %1$s</string> <string name="version_label">버전 %1$s</string>
<string name="encrypted_value_stored">암호화된 비밀번호가 저장되었습니다</string> <string name="encrypted_value_stored">암호화된 비밀번호가 저장되었습니다</string>
<string name="info">정보</string> <string name="info">정보</string>
<string name="build_label">빌드 %1$s</string> <string name="build_label">빌드 %1$s</string>
<string name="configure_biometric">생체 인식 또는 장치 자격 증명이 등록되지 않았습니다.</string> <string name="configure_biometric">생체 인식 또는 장치 자격 증명이 등록되지 않았습니다.</string>
<string name="properties">속성</string> <string name="properties">속성</string>
<string name="menu_appearance_settings_summary">테마, 색상, 속성</string> <string name="menu_appearance_settings_summary">테마, 색상, 아이콘, 글꼴, 속성</string>
<string name="autofill">자동 입력</string> <string name="autofill">자동 입력</string>
<string name="credential_before_click_device_unlock_button">비밀번호 입력 후 이 버튼을 눌러주세요</string> <string name="credential_before_click_device_unlock_button">비밀번호 입력 후 이 버튼을 눌러주세요.</string>
<string name="unavailable">사용할 수 없습니다</string> <string name="unavailable">사용 불가</string>
<string name="database_history">기록</string> <string name="database_history">이력</string>
<string name="type">유형</string> <string name="type">유형</string>
<string name="configure">설정</string> <string name="configure">설정</string>
<string name="biometric_security_update_required">등록된 생체정보를 업데이트해 주세요.</string> <string name="biometric_security_update_required">등록된 생체정보를 업데이트해 주세요.</string>
<string name="autofill_select_entry">항목 선택하세요</string> <string name="autofill_select_entry">항목 선택</string>
<string name="clipboard">클립보드</string> <string name="clipboard">클립보드</string>
<string name="clipboard_notifications_title">클립보드 알림</string> <string name="clipboard_notifications_title">클립보드 알림</string>
<string name="lock_database_screen_off_summary">화면이 꺼지면 몇 초 후에 데이터베이스를 잠급니다.</string> <string name="lock_database_screen_off_summary">화면이 꺼지면 몇 초 후에 데이터베이스를 잠급니다</string>
<string name="lock_database_back_root_title">\'­뒤로가기\'를 눌러 잠금</string> <string name="lock_database_back_root_title">\'­뒤로가기\'를 눌러 잠금</string>
<string name="unavailable_feature_text">이 기능을 사용할 수 없습니다.</string> <string name="unavailable_feature_text">이 기능을 사용할 수 없습니다.</string>
<string name="lock_database_show_button_summary">UI에 잠금버튼을 표시합니다</string> <string name="lock_database_show_button_summary">UI에 잠금버튼을 표시합니다</string>
<string name="lock_database_back_root_summary">사용자가 뒤로 가기 버튼을 누르면 데이터베이스를 잠급니다</string> <string name="lock_database_back_root_summary">데이터베이스 루트 화면에 있는 경우 \'뒤로\' 버튼을 눌러 데이터베이스를 잠그십시오</string>
<string name="lock_database_show_button_title">잠금 버튼 표시</string> <string name="lock_database_show_button_title">잠금 버튼 표시</string>
<string name="unlock">잠금 해제</string> <string name="unlock">잠금 해제</string>
<string name="lock">잠금</string> <string name="lock">잠금</string>
<string name="autofill_sign_in_prompt">KeePassDX로 로그인</string> <string name="autofill_sign_in_prompt">KeePassDX로 로그인</string>
<string name="password_size_title">생성된 비밀번호 크기</string> <string name="password_size_title">생성된 패스워드 길이</string>
<string name="autofill_preference_title">자동 완성 설정</string> <string name="autofill_preference_title">자동 완성 설정</string>
<string name="set_credential_provider_service_title">기본 자동완성 서비스 설정</string> <string name="set_credential_provider_service_title">기본 자동완성 서비스 설정</string>
<string name="lock_database_screen_off_title">화면 꺼짐 시 잠금</string> <string name="lock_database_screen_off_title">화면 꺼짐 시 잠금</string>

View File

@@ -475,7 +475,7 @@
<string name="autofill_ask_to_save_data_summary">Запрашивать сохранение данных после завершения заполнения формы</string> <string name="autofill_ask_to_save_data_summary">Запрашивать сохранение данных после завершения заполнения формы</string>
<string name="autofill_ask_to_save_data_title">Запрос сохранения данных</string> <string name="autofill_ask_to_save_data_title">Запрос сохранения данных</string>
<string name="autofill_close_database_summary">Закрывать базу после выбора автозаполнения</string> <string name="autofill_close_database_summary">Закрывать базу после выбора автозаполнения</string>
<string name="autofill_close_database_title">Закрыть базу</string> <string name="autofill_close_database_title">Закрывать базу</string>
<string name="notification">Уведомление</string> <string name="notification">Уведомление</string>
<string name="biometric_security_update_required">Требуется обновление биометрической системы безопасности.</string> <string name="biometric_security_update_required">Требуется обновление биометрической системы безопасности.</string>
<string name="warning_empty_recycle_bin">Удалить всё содержимое корзины безвозвратно\?</string> <string name="warning_empty_recycle_bin">Удалить всё содержимое корзины безвозвратно\?</string>
@@ -585,7 +585,7 @@
<string name="show_otp_token_summary">Показывать токены OTP в списке записей</string> <string name="show_otp_token_summary">Показывать токены OTP в списке записей</string>
<string name="menu_external_icon">Внешний значок</string> <string name="menu_external_icon">Внешний значок</string>
<string name="autofill_select_entry">Выбор записи…</string> <string name="autofill_select_entry">Выбор записи…</string>
<string name="autofill_manual_selection_summary">Показывать функцию, позволяющую пользователю вручную выбирать запись из базы</string> <string name="autofill_manual_selection_summary">Показывать элемент управления для ручного выбора пользователем записи из базы</string>
<string name="autofill_manual_selection_title">Ручной выбор</string> <string name="autofill_manual_selection_title">Ручной выбор</string>
<string name="hint_icon_name">Название значка</string> <string name="hint_icon_name">Название значка</string>
<string name="permission">Разрешение</string> <string name="permission">Разрешение</string>

View File

@@ -569,7 +569,7 @@
<string name="icon_pack_choose_summary">Paketë ikonash të përdorura te aplikacioni</string> <string name="icon_pack_choose_summary">Paketë ikonash të përdorura te aplikacioni</string>
<string name="hide_expired_entries_summary">Zërat e skaduar nuk shfaqen</string> <string name="hide_expired_entries_summary">Zërat e skaduar nuk shfaqen</string>
<string name="set_credential_provider_service_title">Caktoni shërbim parazgjedhje vetëplotësimesh</string> <string name="set_credential_provider_service_title">Caktoni shërbim parazgjedhje vetëplotësimesh</string>
<string name="autofill_explanation_summary">Aktivizoni vetëplotësimet, për të plotësuar shpejt formularë në aplikacione të tjerë</string> <string name="autofill_explanation_summary">Formësoni vetëplotësimet, për të plotësuar shpejt formularë në aplikacione të tjerë</string>
<string name="autofill_preference_title">Rregullime vetëplotësimi</string> <string name="autofill_preference_title">Rregullime vetëplotësimi</string>
<string name="password_size_summary">Cakton madhësinë parazgjedhje për fjalëkalimet e prodhuar</string> <string name="password_size_summary">Cakton madhësinë parazgjedhje për fjalëkalimet e prodhuar</string>
<string name="list_password_generator_options_summary">Caktoni shenja të lejuara për prodhuesin e fjalëkalimeve</string> <string name="list_password_generator_options_summary">Caktoni shenja të lejuara për prodhuesin e fjalëkalimeve</string>
@@ -623,14 +623,14 @@
<string name="education_field_copy_summary">Çkopjohet te fusha, mund të ngjitet kudo.\n\nPërdorni metodën mbushje formularësh, nëse parapëlqeni.</string> <string name="education_field_copy_summary">Çkopjohet te fusha, mund të ngjitet kudo.\n\nPërdorni metodën mbushje formularësh, nëse parapëlqeni.</string>
<string name="education_donation_summary">Ndihmoni të shtohet qëndrueshmëria, siguria dhe në shtimin e më tepër veçorive.</string> <string name="education_donation_summary">Ndihmoni të shtohet qëndrueshmëria, siguria dhe në shtimin e më tepër veçorive.</string>
<string name="html_text_ad_free">Ndryshe nga shumë aplikacione administrimi fjalëkalimesh, ky është &lt;strong&gt;pa reklama&lt;/strong&gt;, &lt;strong&gt;<em>software</em> i lirë në <em>copylef</em>&lt;/strong&gt; dhe sgrumbullon të dhëna personale në shërbyesit e tij, pavarësisht versionit që përdorni.</string> <string name="html_text_ad_free">Ndryshe nga shumë aplikacione administrimi fjalëkalimesh, ky është &lt;strong&gt;pa reklama&lt;/strong&gt;, &lt;strong&gt;<em>software</em> i lirë në <em>copylef</em>&lt;/strong&gt; dhe sgrumbullon të dhëna personale në shërbyesit e tij, pavarësisht versionit që përdorni.</string>
<string name="html_text_buy_pro">Duke blerë versionin Pro, do të mund të përdorni këtë &lt;strong&gt;stil pamor&lt;/strong&gt; dhe do të ndihmoni veçanërisht në &lt;strong&gt;realizimin e projekteve të bashkësisë.&lt;strong&gt;</string> <string name="html_text_buy_pro">Duke blerë versionin Pro, do të mund të përdorni këtë &lt;strong&gt;stil pamor&lt;/strong&gt; dhe do të ndihmoni veçanërisht në &lt;strong&gt;realizimin e projekteve të bashkësisë.&lt;/strong&gt;</string>
<string name="error_arc4">Smbulohet shifër “Arcfour stream”.</string> <string name="error_arc4">Smbulohet shifër “Arcfour stream”.</string>
<string name="warning_file_too_big">Një bazë të dhënash KeePass supozohet se përmban vetëm kartela të vockla mjetesh (b.f., kartela kyçesh PGP).\n\nMe këtë ngarkim baza juaj e të dhënave mund të bëhet shumë e madhe dhe të ulet funksionimi i saj.</string> <string name="warning_file_too_big">Një bazë të dhënash KeePass supozohet se përmban vetëm kartela të vockla mjetesh (b.f., kartela kyçesh PGP).\n\nMe këtë ngarkim baza juaj e të dhënave mund të bëhet shumë e madhe dhe të ulet funksionimi i saj.</string>
<string name="education_read_only_summary">Ndryshoni mënyrë hapjeje për këtë sesion. \n \n“Mbrojtur nga shkimi” parandalon ndryshime të paqëllimta te baza e të dhënave. \n“E ndryshueshme” ju lejon të shtoni, fshini ose ndryshoni krejt elementët, sipas dëshirës.</string> <string name="education_read_only_summary">Ndryshoni mënyrë hapjeje për këtë sesion. \n \n“Mbrojtur nga shkimi” parandalon ndryshime të paqëllimta te baza e të dhënave. \n“E ndryshueshme” ju lejon të shtoni, fshini ose ndryshoni krejt elementët, sipas dëshirës.</string>
<string name="html_text_dev_feature_encourage">po i nxisni zhvilluesit të krijojnë &lt;strong&gt;veçori të reja&lt;/strong&gt; dhe të &lt;strong&gt;ndreqin të meta&lt;/strong&gt; në përputhje me sugjerimet tuaja.</string> <string name="html_text_dev_feature_encourage">po i nxisni zhvilluesit të krijojnë &lt;strong&gt;veçori të reja&lt;/strong&gt; dhe të &lt;strong&gt;ndreqin të meta&lt;/strong&gt; në përputhje me sugjerimet tuaja.</string>
<string name="warning_large_keyfile">Nuk rekomandohet të shtohet një kartelë kyç e madhe, kjo mund të pengojë hapjen e bazës së të dhënave.</string> <string name="warning_large_keyfile">Nuk rekomandohet të shtohet një kartelë kyç e madhe, kjo mund të pengojë hapjen e bazës së të dhënave.</string>
<string name="education_entry_edit_summary">Përpunojeni zërin tuaj me fusha vetjake. Pool data can be referenced between different entry fields.</string> <string name="education_entry_edit_summary">Përpunojeni zërin tuaj me fusha vetjake. Pool data can be referenced between different entry fields.</string>
<string name="error_otp_type">Lloji OTP ekzistues snjihet nga ky formular, vlerësimi i tij mund të mos prodhojë më saktë token-in.</string> <string name="error_otp_type">Lloji OTP ekzistues snjihet nga ky formular dhe vlerësimi i tij mund të mos prodhojë më saktë token-in.</string>
<string name="warning_database_read_only">Akordo hyrje për shkrim kartelash, që të ruhen ndryshimet e bazës së të dhënave</string> <string name="warning_database_read_only">Akordo hyrje për shkrim kartelash, që të ruhen ndryshimet e bazës së të dhënave</string>
<string name="warning_database_info_changed_options">Përzieni të dhënat, mbishkruani ndryshimet që nga jashtë duke e ruajtur bazën e të dhënave, ose duke e ringarkuar me ndryshimet më të reja.</string> <string name="warning_database_info_changed_options">Përzieni të dhënat, mbishkruani ndryshimet që nga jashtë duke e ruajtur bazën e të dhënave, ose duke e ringarkuar me ndryshimet më të reja.</string>
<string name="warning_database_revoked">Hyrja te kartela e shfuqizuar nga përgjegjësi i kartelave, mbylleni bazën e të dhënave dhe rihapeni që prej vendndodhjes së saj.</string> <string name="warning_database_revoked">Hyrja te kartela e shfuqizuar nga përgjegjësi i kartelave, mbylleni bazën e të dhënave dhe rihapeni që prej vendndodhjes së saj.</string>

View File

@@ -433,6 +433,32 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
} }
} }
/**
* Return the real parent in database from a group defined as parent
* If the parent is null, simply return the root group
* Guaranteed that a return group is linked to the database tree through its ancestors
*/
private fun getAttachedParent(
group: GroupKDBX?
): GroupKDBX {
var realParent: GroupKDBX = database.rootGroup!!
group?.let { parent ->
val parentInDatabase = database.getGroupById(parent.nodeId)
if (parentInDatabase == null) {
realParent = GroupKDBX().apply {
updateWith(parent, updateParents = false)
}
database.addGroupTo(
realParent,
getAttachedParent(parent.parent)
)
} else {
realParent = parentInDatabase
}
}
return realParent
}
/** /**
* Utility method to merge a KDBX entry * Utility method to merge a KDBX entry
*/ */
@@ -443,10 +469,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
databaseToMerge.getEntryById(entryId)?.let { srcEntryToMerge -> databaseToMerge.getEntryById(entryId)?.let { srcEntryToMerge ->
// Retrieve parent in current database // Retrieve parent in current database
var parentEntryToMerge: GroupKDBX? = null val parentEntryToMerge: GroupKDBX = getAttachedParent(srcEntryToMerge.parent)
srcEntryToMerge.parent?.nodeId?.let {
parentEntryToMerge = database.getGroupById(it)
}
val entryToMerge = EntryKDBX().apply { val entryToMerge = EntryKDBX().apply {
updateWith(srcEntryToMerge, copyHistory = true, updateParents = false) updateWith(srcEntryToMerge, copyHistory = true, updateParents = false)
} }
@@ -478,8 +501,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
// If it's a deleted object, but another instance was updated // If it's a deleted object, but another instance was updated
// If entry parent to add exists and in current database // If entry parent to add exists and in current database
if ((deletedObject == null if ((deletedObject == null
|| deletedObject.deletionTime.isBefore(entryToMerge.lastModificationTime)) || deletedObject.deletionTime.isBefore(entryToMerge.lastModificationTime))) {
&& parentEntryToMerge != null) {
database.addEntryTo(entryToMerge, parentEntryToMerge) database.addEntryTo(entryToMerge, parentEntryToMerge)
} }
} else { } else {
@@ -488,51 +510,46 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
// Merge by modification time // Merge by modification time
if (entry.lastModificationTime.isBefore(entryToMerge.lastModificationTime)) { if (entry.lastModificationTime.isBefore(entryToMerge.lastModificationTime)) {
// Update entry with databaseEntryToMerge and merge history // Update entry with databaseEntryToMerge and merge history
addHistory(entry, entryToMerge) entryToMerge.addHistoryFrom(entry)
if (parentEntryToMerge == entry.parent) { entry.updateWith(entryToMerge, copyHistory = true, updateParents = false)
entry.updateWith(entryToMerge, copyHistory = true, updateParents = false) // Move the current entry to the verified location
} else { database.removeEntryFrom(entry, entry.parent)
database.removeEntryFrom(entry, entry.parent) database.addEntryTo(entry, parentEntryToMerge)
if (parentEntryToMerge != null) {
database.addEntryTo(entryToMerge, parentEntryToMerge)
}
}
} else if (entry.lastModificationTime.isAfter(entryToMerge.lastModificationTime)) { } else if (entry.lastModificationTime.isAfter(entryToMerge.lastModificationTime)) {
addHistory(entryToMerge, entry) // Don't touch the location but update the entry history
entry.addHistoryFrom(entryToMerge)
} else if (entry.lastModificationTime.isEquals(entryToMerge.lastModificationTime)) { } else if (entry.lastModificationTime.isEquals(entryToMerge.lastModificationTime)) {
// If it's the same modification time, simply move entry to the right location // If it's the same modification time, simply move entry to the right location,
parentEntryToMerge?.let { // Current entry and entry to merge are normally the same
database.removeEntryFrom(entry, entry.parent) database.removeEntryFrom(entry, entry.parent)
database.addEntryTo(entryToMerge, parentEntryToMerge) database.addEntryTo(entry, parentEntryToMerge)
}
} }
} }
} }
} }
/** /**
* Utility method to merge an history from an [entryA] to an [entryB], * Utility method to merge an history from an [entryA]
* [entryB] is modified
*/ */
private fun addHistory(entryA: EntryKDBX, entryB: EntryKDBX) { private fun EntryKDBX.addHistoryFrom(entryA: EntryKDBX) {
// Keep entry as history if already not present // Keep entry as history if already not present
entryA.history.forEach { history -> entryA.history.forEach { history ->
// If history not present // If history not present
if (!entryB.history.any { if (!this.history.any {
it.lastModificationTime == history.lastModificationTime it.lastModificationTime == history.lastModificationTime
}) { }) {
entryB.addEntryToHistory(history) this.addEntryToHistory(history)
} }
} }
// Last entry not present // Last entry not present
if (entryB.history.find { if (this.history.find {
it.lastModificationTime == entryA.lastModificationTime it.lastModificationTime == entryA.lastModificationTime
} == null) { } == null) {
val history = EntryKDBX().apply { val history = EntryKDBX().apply {
updateWith(entryA, copyHistory = false, updateParents = false) updateWith(entryA, copyHistory = false, updateParents = false)
parent = null parent = null
} }
entryB.addEntryToHistory(history) this.addEntryToHistory(history)
} }
} }
@@ -546,10 +563,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
databaseToMerge.getGroupById(groupId)?.let { srcGroupToMerge -> databaseToMerge.getGroupById(groupId)?.let { srcGroupToMerge ->
// Retrieve parent in current database // Retrieve parent in current database
var parentGroupToMerge: GroupKDBX? = null val parentGroupToMerge: GroupKDBX = getAttachedParent(srcGroupToMerge.parent)
srcGroupToMerge.parent?.nodeId?.let {
parentGroupToMerge = database.getGroupById(it)
}
val groupToMerge = GroupKDBX().apply { val groupToMerge = GroupKDBX().apply {
updateWith(srcGroupToMerge, updateParents = false) updateWith(srcGroupToMerge, updateParents = false)
} }
@@ -557,8 +571,7 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
if (group == null) { if (group == null) {
// If group parent to add exists and in current database // If group parent to add exists and in current database
if ((deletedObject == null if ((deletedObject == null
|| deletedObject.deletionTime.isBefore(groupToMerge.lastModificationTime)) || deletedObject.deletionTime.isBefore(groupToMerge.lastModificationTime))) {
&& parentGroupToMerge != null) {
database.addGroupTo(groupToMerge, parentGroupToMerge) database.addGroupTo(groupToMerge, parentGroupToMerge)
} }
} else { } else {
@@ -566,20 +579,16 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
mergeCustomData(group.customData, groupToMerge.customData) mergeCustomData(group.customData, groupToMerge.customData)
// Merge by modification time // Merge by modification time
if (group.lastModificationTime.isBefore(groupToMerge.lastModificationTime)) { if (group.lastModificationTime.isBefore(groupToMerge.lastModificationTime)) {
if (parentGroupToMerge == group.parent) { group.updateWith(groupToMerge, updateParents = false)
group.updateWith(groupToMerge, false) // Update the current group location to the verified one
} else { database.removeGroupFrom(group, group.parent)
database.removeGroupFrom(group, group.parent) database.addGroupTo(group, parentGroupToMerge)
if (parentGroupToMerge != null) { } else if (group.lastModificationTime.isAfter(group.lastModificationTime)) {
database.addGroupTo(groupToMerge, parentGroupToMerge) // Don't touch the location
}
}
} else if (group.lastModificationTime.isEquals(groupToMerge.lastModificationTime)) { } else if (group.lastModificationTime.isEquals(groupToMerge.lastModificationTime)) {
// If it's the same modification time, simply move group to the right location // If it's the same modification time, simply move group to the right location
parentGroupToMerge?.let { database.removeGroupFrom(group, group.parent)
database.removeGroupFrom(group, group.parent) database.addGroupTo(group, parentGroupToMerge)
database.addGroupTo(groupToMerge, parentGroupToMerge)
}
} }
} }
} }

View File

@@ -159,5 +159,7 @@ object AppOriginEntryField {
*/ */
fun Field.isWebDomain(): Boolean { fun Field.isWebDomain(): Boolean {
return this.name.startsWith(WEB_DOMAIN_FIELD_NAME) return this.name.startsWith(WEB_DOMAIN_FIELD_NAME)
|| this.name.contains("_$WEB_DOMAIN_FIELD_NAME")
|| this.name.contains("${WEB_DOMAIN_FIELD_NAME}_")
} }
} }

View File

@@ -1 +1 @@
KeePassDX مدير كلمات سر مفتوح المصدر KeePassDX

View File

@@ -0,0 +1,4 @@
* Fix database merge algorithm #2223
* Fix save search info #2243
* Fix Play Service as privileged app for Passkey Cross Device Authentication #2244
* Small fixes

View File

@@ -1 +1 @@
KeePassDX - Contraseñas FOSS KeePassDX Pass(key/word) Vault

View File

@@ -0,0 +1,4 @@
* Correction de l'algorithme de fusion des bases de données #2223
* Correction de la sauvegarde des infos de recherche #2243
* Correction Play Service comme appli privilégiée pour l'authentification Passkey multi-appareils #2244
* Small fixes

View File

@@ -1 +1 @@
KeePassDX FOSS trezor za lozinke KeePassDX Pass(key/word) Vault

View File

@@ -1 +1 @@
KeePassDX Pengaman Sandi FOSS KeePassDX Pass(key/word) Vault

View File

@@ -1 +1 @@
Cassaforte di pass(key/word) KeePassDX KeePassDX Pass(key/word) Vault

View File

@@ -1 +1 @@
KeePassDX - Безбедносен софтвер за лозинки и е слободен софтвер KeePassDX Pass(key/word) Vault

View File

@@ -1 +1 @@
KeePassDX - менеджер паролей/ключей KeePassDX - менеджер паролей

View File

@@ -1 +1 @@
KeePassDX - Parola/Anahtar Kasası KeePassDX Pass(key/word) Vault

View File

@@ -1 +1 @@
KeePassDX - FOSS 密碼管理器 KeePassDX Pass(key/word) Vault